blob: c5e9e233b7c9e69348307806ffda178f153c5648
1 | /** |
2 | * utils.c - Part of the Linux-NTFS project. |
3 | * |
4 | * Copyright (c) 2002-2005 Richard Russon |
5 | * Copyright (c) 2003-2006 Anton Altaparmakov |
6 | * Copyright (c) 2003 Lode Leroy |
7 | * Copyright (c) 2005-2007 Yura Pakhuchiy |
8 | * |
9 | * A set of shared functions for ntfs utilities |
10 | * |
11 | * This program is free software; you can redistribute it and/or modify |
12 | * it under the terms of the GNU General Public License as published by |
13 | * the Free Software Foundation; either version 2 of the License, or |
14 | * (at your option) any later version. |
15 | * |
16 | * This program is distributed in the hope that it will be useful, |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | * GNU General Public License for more details. |
20 | * |
21 | * You should have received a copy of the GNU General Public License |
22 | * along with this program (in the main directory of the Linux-NTFS |
23 | * distribution in the file COPYING); if not, write to the Free Software |
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
25 | */ |
26 | |
27 | #ifdef HAVE_CONFIG_H |
28 | #include "config.h" |
29 | #endif |
30 | |
31 | #ifdef HAVE_STDIO_H |
32 | #include <stdio.h> |
33 | #endif |
34 | #ifdef HAVE_STDARG_H |
35 | #include <stdarg.h> |
36 | #endif |
37 | #ifdef HAVE_ERRNO_H |
38 | #include <errno.h> |
39 | #endif |
40 | #ifdef HAVE_SYS_TYPES_H |
41 | #include <sys/types.h> |
42 | #endif |
43 | #ifdef HAVE_SYS_STAT_H |
44 | #include <sys/stat.h> |
45 | #endif |
46 | #ifdef HAVE_UNISTD_H |
47 | #include <unistd.h> |
48 | #endif |
49 | #ifdef HAVE_STRING_H |
50 | #include <string.h> |
51 | #endif |
52 | #ifdef HAVE_LOCALE_H |
53 | #include <locale.h> |
54 | #endif |
55 | #ifdef HAVE_LIBINTL_H |
56 | #include <libintl.h> |
57 | #endif |
58 | #ifdef HAVE_STDLIB_H |
59 | #include <stdlib.h> |
60 | #endif |
61 | #ifdef HAVE_LIMITS_H |
62 | #include <limits.h> |
63 | #endif |
64 | #ifdef HAVE_CTYPE_H |
65 | #include <ctype.h> |
66 | #endif |
67 | |
68 | #include "utils.h" |
69 | #include "types.h" |
70 | #include "volume.h" |
71 | #include "debug.h" |
72 | #include "dir.h" |
73 | /* #include "version.h" */ |
74 | #include "logging.h" |
75 | #include "misc.h" |
76 | |
77 | const char *ntfs_bugs = "Developers' email address: "NTFS_DEV_LIST"\n"; |
78 | const char *ntfs_gpl = "This program is free software, released under the GNU " |
79 | "General Public License\nand you are welcome to redistribute it under " |
80 | "certain conditions. It comes with\nABSOLUTELY NO WARRANTY; for " |
81 | "details read the GNU General Public License to be\nfound in the file " |
82 | "\"COPYING\" distributed with this program, or online at:\n" |
83 | "http://www.gnu.org/copyleft/gpl.html\n"; |
84 | |
85 | static const char *invalid_ntfs_msg = |
86 | "The device '%s' doesn't have a valid NTFS.\n" |
87 | "Maybe you selected the wrong device? Or the whole disk instead of a\n" |
88 | "partition (e.g. /dev/hda, not /dev/hda1)? Or the other way around?\n"; |
89 | |
90 | static const char *corrupt_volume_msg = |
91 | "NTFS is inconsistent. Run chkdsk /f on Windows then reboot it TWICE!\n" |
92 | "The usage of the /f parameter is very IMPORTANT! No modification was\n" |
93 | "made to NTFS by this software.\n"; |
94 | |
95 | static const char *hibernated_volume_msg = |
96 | "The NTFS partition is hibernated. Please resume Windows and turned it \n" |
97 | "off properly, so mounting could be done safely.\n"; |
98 | |
99 | static const char *unclean_journal_msg = |
100 | "Access is denied because the NTFS journal file is unclean. Choices are:\n" |
101 | " A) Shutdown Windows properly.\n" |
102 | " B) Click the 'Safely Remove Hardware' icon in the Windows taskbar\n" |
103 | " notification area before disconnecting the device.\n" |
104 | " C) Use 'Eject' from Windows Explorer to safely remove the device.\n" |
105 | " D) If you ran chkdsk previously then boot Windows again which will\n" |
106 | " automatically initialize the journal.\n" |
107 | " E) Submit 'force' option (WARNING: This solution it not recommended).\n" |
108 | " F) ntfsmount: Mount the volume read-only by using the 'ro' mount option.\n"; |
109 | |
110 | static const char *opened_volume_msg = |
111 | "Access is denied because the NTFS volume is already exclusively opened.\n" |
112 | "The volume may be already mounted, or another software may use it which\n" |
113 | "could be identified for example by the help of the 'fuser' command.\n"; |
114 | |
115 | static const char *dirty_volume_msg = |
116 | "Volume is scheduled for check.\n" |
117 | "Please boot into Windows TWICE, or use the 'force' option.\n" |
118 | "NOTE: If you had not scheduled check and last time accessed this volume\n" |
119 | "using ntfsmount and shutdown system properly, then init scripts in your\n" |
120 | "distribution are broken. Please report to your distribution developers\n" |
121 | "(NOT to us!) that init scripts kill ntfsmount or mount.ntfs-fuse during\n" |
122 | "shutdown instead of proper umount.\n"; |
123 | |
124 | static const char *fakeraid_msg = |
125 | "You seem to have a SoftRAID/FakeRAID hardware and must use an activated,\n" |
126 | "different device under /dev/mapper, (e.g. /dev/mapper/nvidia_eahaabcc1)\n" |
127 | "to mount NTFS. Please see the 'dmraid' documentation for help.\n"; |
128 | |
129 | /** |
130 | * utils_set_locale |
131 | */ |
132 | int utils_set_locale(void) |
133 | { |
134 | const char *locale; |
135 | |
136 | locale = setlocale(LC_ALL, ""); |
137 | if (!locale) { |
138 | locale = setlocale(LC_ALL, NULL); |
139 | ntfs_log_error("Failed to set locale, using default '%s'.\n", |
140 | locale); |
141 | return 1; |
142 | } else { |
143 | return 0; |
144 | } |
145 | } |
146 | |
147 | /** |
148 | * linux-ntfs's ntfs_mbstoucs has different semantics, so we emulate it with |
149 | * ntfs-3g's. |
150 | */ |
151 | int ntfs_mbstoucs_libntfscompat(const char *ins, |
152 | ntfschar **outs, int outs_len) |
153 | { |
154 | if(!outs) { |
155 | errno = EINVAL; |
156 | return -1; |
157 | } |
158 | else if(*outs != NULL) { |
159 | /* Note: libntfs's mbstoucs implementation allows the caller to |
160 | * specify a preallocated buffer while libntfs-3g's always |
161 | * allocates the output buffer. |
162 | */ |
163 | ntfschar *tmpstr = NULL; |
164 | int tmpstr_len; |
165 | |
166 | tmpstr_len = ntfs_mbstoucs(ins, &tmpstr); |
167 | if(tmpstr_len >= 0) { |
168 | if((tmpstr_len + 1) > outs_len) { |
169 | /* Doing a realloc instead of reusing tmpstr |
170 | * because it emulates libntfs's mbstoucs more |
171 | * closely. */ |
172 | ntfschar *re_outs = realloc(*outs, |
173 | sizeof(ntfschar)*(tmpstr_len + 1)); |
174 | if(!re_outs) |
175 | tmpstr_len = -1; |
176 | else |
177 | *outs = re_outs; |
178 | } |
179 | |
180 | if(tmpstr_len >= 0) { |
181 | /* The extra character is the \0 terminator. */ |
182 | memcpy(*outs, tmpstr, |
183 | sizeof(ntfschar)*(tmpstr_len + 1)); |
184 | } |
185 | |
186 | free(tmpstr); |
187 | } |
188 | |
189 | return tmpstr_len; |
190 | } |
191 | else |
192 | return ntfs_mbstoucs(ins, outs); |
193 | } |
194 | |
195 | /** |
196 | * utils_valid_device - Perform some safety checks on the device, before start |
197 | * @name: Full pathname of the device/file to work with |
198 | * @force: Continue regardless of problems |
199 | * |
200 | * Check that the name refers to a device and that is isn't already mounted. |
201 | * These checks can be overridden by using the force option. |
202 | * |
203 | * Return: 1 Success, we can continue |
204 | * 0 Error, we cannot use this device |
205 | */ |
206 | int utils_valid_device(const char *name, int force) |
207 | { |
208 | unsigned long mnt_flags = 0; |
209 | struct stat st; |
210 | |
211 | #if defined(HAVE_WINDOWS_H) | defined(__CYGWIN32__) |
212 | /* FIXME: This doesn't work for Cygwin, so just return success. */ |
213 | return 1; |
214 | #endif |
215 | if (!name) { |
216 | errno = EINVAL; |
217 | return 0; |
218 | } |
219 | |
220 | if (stat(name, &st) == -1) { |
221 | if (errno == ENOENT) |
222 | ntfs_log_error("The device %s doesn't exist\n", name); |
223 | else |
224 | ntfs_log_perror("Error getting information about %s", |
225 | name); |
226 | return 0; |
227 | } |
228 | |
229 | /* Make sure the file system is not mounted. */ |
230 | if (ntfs_check_if_mounted(name, &mnt_flags)) { |
231 | ntfs_log_perror("Failed to determine whether %s is mounted", |
232 | name); |
233 | if (!force) { |
234 | ntfs_log_error("Use the force option to ignore this " |
235 | "error.\n"); |
236 | return 0; |
237 | } |
238 | ntfs_log_warning("Forced to continue.\n"); |
239 | } else if (mnt_flags & NTFS_MF_MOUNTED) { |
240 | if (!force) { |
241 | ntfs_log_error("%s", opened_volume_msg); |
242 | ntfs_log_error("You can use force option to avoid this " |
243 | "check, but this is not recommended\n" |
244 | "and may lead to data corruption.\n"); |
245 | return 0; |
246 | } |
247 | ntfs_log_warning("Forced to continue.\n"); |
248 | } |
249 | |
250 | return 1; |
251 | } |
252 | |
253 | /** |
254 | * utils_mount_volume - Mount an NTFS volume |
255 | */ |
256 | ntfs_volume * utils_mount_volume(const char *device, unsigned long flags) |
257 | { |
258 | ntfs_volume *vol; |
259 | |
260 | if (!device) { |
261 | errno = EINVAL; |
262 | return NULL; |
263 | } |
264 | |
265 | /* Porting notes: |
266 | * |
267 | * libntfs-3g does not have the 'force' flag in ntfs_mount_flags. |
268 | * The 'force' flag in libntfs bypasses two safety checks when mounting |
269 | * read/write: |
270 | * 1. Do not mount when the VOLUME_IS_DIRTY flag in |
271 | * VOLUME_INFORMATION is set. |
272 | * 2. Do not mount when the logfile is unclean. |
273 | * |
274 | * libntfs-3g only has safety check number 2. The dirty flag is simply |
275 | * ignored because we are confident that we can handle a dirty volume. |
276 | * So we treat NTFS_MNT_RECOVER like NTFS_MNT_FORCE, knowing that the |
277 | * first check is always bypassed. |
278 | */ |
279 | |
280 | if (!utils_valid_device(device, flags & NTFS_MNT_RECOVER)) |
281 | return NULL; |
282 | |
283 | vol = ntfs_mount(device, flags); |
284 | if (!vol) { |
285 | ntfs_log_perror("Failed to mount '%s'", device); |
286 | if (errno == EINVAL) |
287 | ntfs_log_error(invalid_ntfs_msg, device); |
288 | else if (errno == EIO) |
289 | ntfs_log_error("%s", corrupt_volume_msg); |
290 | else if (errno == EPERM) |
291 | ntfs_log_error("%s", hibernated_volume_msg); |
292 | else if (errno == EOPNOTSUPP) |
293 | ntfs_log_error("%s", unclean_journal_msg); |
294 | else if (errno == EBUSY) |
295 | ntfs_log_error("%s", opened_volume_msg); |
296 | else if (errno == ENXIO) |
297 | ntfs_log_error("%s", fakeraid_msg); |
298 | return NULL; |
299 | } |
300 | |
301 | /* Porting notes: |
302 | * libntfs-3g does not record whether the volume log file was dirty |
303 | * before mount, so we can only warn if the VOLUME_IS_DIRTY flag is set |
304 | * in VOLUME_INFORMATION. */ |
305 | if (vol->flags & VOLUME_IS_DIRTY) { |
306 | if (!(flags & NTFS_MNT_RECOVER)) { |
307 | ntfs_log_error("%s", dirty_volume_msg); |
308 | ntfs_umount(vol, FALSE); |
309 | return NULL; |
310 | } |
311 | ntfs_log_error("WARNING: Dirty volume mount was forced by the " |
312 | "'force' mount option.\n"); |
313 | } |
314 | return vol; |
315 | } |
316 | |
317 | /** |
318 | * utils_parse_size - Convert a string representing a size |
319 | * @value: String to be parsed |
320 | * @size: Parsed size |
321 | * @scale: Whether or not to allow a suffix to scale the value |
322 | * |
323 | * Read a string and convert it to a number. Strings may be suffixed to scale |
324 | * them. Any number without a suffix is assumed to be in bytes. |
325 | * |
326 | * Suffix Description Multiple |
327 | * [tT] Terabytes 10^12 |
328 | * [gG] Gigabytes 10^9 |
329 | * [mM] Megabytes 10^6 |
330 | * [kK] Kilobytes 10^3 |
331 | * |
332 | * Notes: |
333 | * Only the first character of the suffix is read. |
334 | * The multipliers are decimal thousands, not binary: 1000, not 1024. |
335 | * If parse_size fails, @size will not be changed |
336 | * |
337 | * Return: 1 Success |
338 | * 0 Error, the string was malformed |
339 | */ |
340 | int utils_parse_size(const char *value, s64 *size, BOOL scale) |
341 | { |
342 | long long result; |
343 | char *suffix = NULL; |
344 | |
345 | if (!value || !size) { |
346 | errno = EINVAL; |
347 | return 0; |
348 | } |
349 | |
350 | ntfs_log_debug("Parsing size '%s'.\n", value); |
351 | |
352 | result = strtoll(value, &suffix, 0); |
353 | if (result < 0 || errno == ERANGE) { |
354 | ntfs_log_error("Invalid size '%s'.\n", value); |
355 | return 0; |
356 | } |
357 | |
358 | if (!suffix) { |
359 | ntfs_log_error("Internal error, strtoll didn't return a suffix.\n"); |
360 | return 0; |
361 | } |
362 | |
363 | if (scale) { |
364 | switch (suffix[0]) { |
365 | case 't': case 'T': result *= 1000; |
366 | case 'g': case 'G': result *= 1000; |
367 | case 'm': case 'M': result *= 1000; |
368 | case 'k': case 'K': result *= 1000; |
369 | case '-': case 0: |
370 | break; |
371 | default: |
372 | ntfs_log_error("Invalid size suffix '%s'. Use T, G, M, or K.\n", suffix); |
373 | return 0; |
374 | } |
375 | } else { |
376 | if ((suffix[0] != '-') && (suffix[0] != 0)) { |
377 | ntfs_log_error("Invalid number '%.*s'.\n", (int)(suffix - value + 1), value); |
378 | return 0; |
379 | } |
380 | } |
381 | |
382 | ntfs_log_debug("Parsed size = %lld.\n", result); |
383 | *size = result; |
384 | return 1; |
385 | } |
386 | |
387 | /** |
388 | * utils_parse_range - Convert a string representing a range of numbers |
389 | * @string: The string to be parsed |
390 | * @start: The beginning of the range will be stored here |
391 | * @finish: The end of the range will be stored here |
392 | * |
393 | * Read a string of the form n-m. If the lower end is missing, zero will be |
394 | * substituted. If the upper end is missing LONG_MAX will be used. If the |
395 | * string cannot be parsed correctly, @start and @finish will not be changed. |
396 | * |
397 | * Return: 1 Success, a valid string was found |
398 | * 0 Error, the string was not a valid range |
399 | */ |
400 | int utils_parse_range(const char *string, s64 *start, s64 *finish, BOOL scale) |
401 | { |
402 | s64 a, b; |
403 | char *middle; |
404 | |
405 | if (!string || !start || !finish) { |
406 | errno = EINVAL; |
407 | return 0; |
408 | } |
409 | |
410 | middle = strchr(string, '-'); |
411 | if (string == middle) { |
412 | ntfs_log_debug("Range has no beginning, defaulting to 0.\n"); |
413 | a = 0; |
414 | } else { |
415 | if (!utils_parse_size(string, &a, scale)) |
416 | return 0; |
417 | } |
418 | |
419 | if (middle) { |
420 | if (middle[1] == 0) { |
421 | b = LONG_MAX; // XXX ULLONG_MAX |
422 | ntfs_log_debug("Range has no end, defaulting to %lld.\n", b); |
423 | } else { |
424 | if (!utils_parse_size(middle+1, &b, scale)) |
425 | return 0; |
426 | } |
427 | } else { |
428 | b = a; |
429 | } |
430 | |
431 | ntfs_log_debug("Range '%s' = %lld - %lld\n", string, a, b); |
432 | |
433 | *start = a; |
434 | *finish = b; |
435 | return 1; |
436 | } |
437 | |
438 | /** |
439 | * find_attribute - Find an attribute of the given type |
440 | * @type: An attribute type, e.g. AT_FILE_NAME |
441 | * @ctx: A search context, created using ntfs_get_attr_search_ctx |
442 | * |
443 | * Using the search context to keep track, find the first/next occurrence of a |
444 | * given attribute type. |
445 | * |
446 | * N.B. This will return a pointer into @mft. As long as the search context |
447 | * has been created without an inode, it won't overflow the buffer. |
448 | * |
449 | * Return: Pointer Success, an attribute was found |
450 | * NULL Error, no matching attributes were found |
451 | */ |
452 | ATTR_RECORD * find_attribute(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx) |
453 | { |
454 | if (!ctx) { |
455 | errno = EINVAL; |
456 | return NULL; |
457 | } |
458 | |
459 | if (ntfs_attr_lookup(type, NULL, 0, 0, 0, NULL, 0, ctx) != 0) { |
460 | ntfs_log_debug("find_attribute didn't find an attribute of type: 0x%02x.\n", type); |
461 | return NULL; /* None / no more of that type */ |
462 | } |
463 | |
464 | ntfs_log_debug("find_attribute found an attribute of type: 0x%02x.\n", type); |
465 | return ctx->attr; |
466 | } |
467 | |
468 | /** |
469 | * find_first_attribute - Find the first attribute of a given type |
470 | * @type: An attribute type, e.g. AT_FILE_NAME |
471 | * @mft: A buffer containing a raw MFT record |
472 | * |
473 | * Search through a raw MFT record for an attribute of a given type. |
474 | * The return value is a pointer into the MFT record that was supplied. |
475 | * |
476 | * N.B. This will return a pointer into @mft. The pointer won't stray outside |
477 | * the buffer, since we created the search context without an inode. |
478 | * |
479 | * Return: Pointer Success, an attribute was found |
480 | * NULL Error, no matching attributes were found |
481 | */ |
482 | ATTR_RECORD * find_first_attribute(const ATTR_TYPES type, MFT_RECORD *mft) |
483 | { |
484 | ntfs_attr_search_ctx *ctx; |
485 | ATTR_RECORD *rec; |
486 | |
487 | if (!mft) { |
488 | errno = EINVAL; |
489 | return NULL; |
490 | } |
491 | |
492 | ctx = ntfs_attr_get_search_ctx(NULL, mft); |
493 | if (!ctx) { |
494 | ntfs_log_error("Couldn't create a search context.\n"); |
495 | return NULL; |
496 | } |
497 | |
498 | rec = find_attribute(type, ctx); |
499 | ntfs_attr_put_search_ctx(ctx); |
500 | if (rec) |
501 | ntfs_log_debug("find_first_attribute: found attr of type 0x%02x.\n", type); |
502 | else |
503 | ntfs_log_debug("find_first_attribute: didn't find attr of type 0x%02x.\n", type); |
504 | return rec; |
505 | } |
506 | |
507 | /** |
508 | * utils_inode_get_name |
509 | * |
510 | * using inode |
511 | * get filename |
512 | * add name to list |
513 | * get parent |
514 | * if parent is 5 (/) stop |
515 | * get inode of parent |
516 | */ |
517 | #define max_path 20 |
518 | int utils_inode_get_name(ntfs_inode *inode, char *buffer, int bufsize) |
519 | { |
520 | // XXX option: names = posix/win32 or dos |
521 | // flags: path, filename, or both |
522 | |
523 | |
524 | ntfs_volume *vol; |
525 | ntfs_attr_search_ctx *ctx; |
526 | ATTR_RECORD *rec; |
527 | FILE_NAME_ATTR *attr; |
528 | int name_space; |
529 | MFT_REF parent = FILE_root; |
530 | char *names[max_path + 1];// XXX ntfs_malloc? and make max bigger? |
531 | int i, len, offset = 0; |
532 | |
533 | if (!inode || !buffer) { |
534 | errno = EINVAL; |
535 | return 0; |
536 | } |
537 | |
538 | vol = inode->vol; |
539 | |
540 | //ntfs_log_debug("sizeof(char*) = %d, sizeof(names) = %d\n", sizeof(char*), sizeof(names)); |
541 | memset(names, 0, sizeof(names)); |
542 | |
543 | for (i = 0; i < max_path; i++) { |
544 | |
545 | ctx = ntfs_attr_get_search_ctx(inode, NULL); |
546 | if (!ctx) { |
547 | ntfs_log_error("Couldn't create a search context.\n"); |
548 | return 0; |
549 | } |
550 | |
551 | //ntfs_log_debug("i = %d, inode = %p (%lld)\n", i, inode, inode->mft_no); |
552 | |
553 | name_space = 4; |
554 | while ((rec = find_attribute(AT_FILE_NAME, ctx))) { |
555 | /* We know this will always be resident. */ |
556 | attr = (FILE_NAME_ATTR *) ((char *) rec + le16_to_cpu(rec->value_offset)); |
557 | |
558 | if (attr->file_name_type > name_space) { //XXX find the ... |
559 | continue; |
560 | } |
561 | |
562 | name_space = attr->file_name_type; |
563 | parent = le64_to_cpu(attr->parent_directory); |
564 | |
565 | if (names[i]) { |
566 | free(names[i]); |
567 | names[i] = NULL; |
568 | } |
569 | |
570 | if (ntfs_ucstombs(attr->file_name, attr->file_name_length, |
571 | &names[i], 0) < 0) { |
572 | char *temp; |
573 | ntfs_log_error("Couldn't translate filename to current locale.\n"); |
574 | temp = ntfs_malloc(30); |
575 | if (!temp) |
576 | return 0; |
577 | snprintf(temp, 30, "<MFT%llu>", (unsigned |
578 | long long)inode->mft_no); |
579 | names[i] = temp; |
580 | } |
581 | |
582 | //ntfs_log_debug("names[%d] %s\n", i, names[i]); |
583 | //ntfs_log_debug("parent = %lld\n", MREF(parent)); |
584 | } |
585 | |
586 | ntfs_attr_put_search_ctx(ctx); |
587 | |
588 | if (i > 0) /* Don't close the original inode */ |
589 | ntfs_inode_close(inode); |
590 | |
591 | if (MREF(parent) == FILE_root) { /* The root directory, stop. */ |
592 | //ntfs_log_debug("inode 5\n"); |
593 | break; |
594 | } |
595 | |
596 | inode = ntfs_inode_open(vol, parent); |
597 | if (!inode) { |
598 | ntfs_log_error("Couldn't open inode %llu.\n", |
599 | (unsigned long long)MREF(parent)); |
600 | break; |
601 | } |
602 | } |
603 | |
604 | if (i >= max_path) { |
605 | /* If we get into an infinite loop, we'll end up here. */ |
606 | ntfs_log_error("The directory structure is too deep (over %d) nested directories.\n", max_path); |
607 | return 0; |
608 | } |
609 | |
610 | /* Assemble the names in the correct order. */ |
611 | for (i = max_path; i >= 0; i--) { |
612 | if (!names[i]) |
613 | continue; |
614 | |
615 | len = snprintf(buffer + offset, bufsize - offset, "%c%s", PATH_SEP, names[i]); |
616 | if (len >= (bufsize - offset)) { |
617 | ntfs_log_error("Pathname was truncated.\n"); |
618 | break; |
619 | } |
620 | |
621 | offset += len; |
622 | } |
623 | |
624 | /* Free all the allocated memory */ |
625 | for (i = 0; i < max_path; i++) |
626 | free(names[i]); |
627 | |
628 | ntfs_log_debug("Pathname: %s\n", buffer); |
629 | |
630 | return 1; |
631 | } |
632 | #undef max_path |
633 | |
634 | /** |
635 | * utils_attr_get_name |
636 | */ |
637 | int utils_attr_get_name(ntfs_volume *vol, ATTR_RECORD *attr, char *buffer, int bufsize) |
638 | { |
639 | int len, namelen; |
640 | char *name; |
641 | ATTR_DEF *attrdef; |
642 | |
643 | // flags: attr, name, or both |
644 | if (!attr || !buffer) { |
645 | errno = EINVAL; |
646 | return 0; |
647 | } |
648 | |
649 | attrdef = ntfs_attr_find_in_attrdef(vol, attr->type); |
650 | if (attrdef) { |
651 | name = NULL; |
652 | namelen = ntfs_ucsnlen(attrdef->name, sizeof(attrdef->name)); |
653 | if (ntfs_ucstombs(attrdef->name, namelen, &name, 0) < 0) { |
654 | ntfs_log_error("Couldn't translate attribute type to " |
655 | "current locale.\n"); |
656 | // <UNKNOWN>? |
657 | return 0; |
658 | } |
659 | len = snprintf(buffer, bufsize, "%s", name); |
660 | } else { |
661 | ntfs_log_error("Unknown attribute type 0x%02x\n", attr->type); |
662 | len = snprintf(buffer, bufsize, "<UNKNOWN>"); |
663 | } |
664 | |
665 | if (len >= bufsize) { |
666 | ntfs_log_error("Attribute type was truncated.\n"); |
667 | return 0; |
668 | } |
669 | |
670 | if (!attr->name_length) { |
671 | return 0; |
672 | } |
673 | |
674 | buffer += len; |
675 | bufsize -= len; |
676 | |
677 | name = NULL; |
678 | namelen = attr->name_length; |
679 | if (ntfs_ucstombs((ntfschar *)((char *)attr + attr->name_offset), |
680 | namelen, &name, 0) < 0) { |
681 | ntfs_log_error("Couldn't translate attribute name to current " |
682 | "locale.\n"); |
683 | // <UNKNOWN>? |
684 | len = snprintf(buffer, bufsize, "<UNKNOWN>"); |
685 | return 0; |
686 | } |
687 | |
688 | len = snprintf(buffer, bufsize, "(%s)", name); |
689 | free(name); |
690 | |
691 | if (len >= bufsize) { |
692 | ntfs_log_error("Attribute name was truncated.\n"); |
693 | return 0; |
694 | } |
695 | |
696 | return 0; |
697 | } |
698 | |
699 | /** |
700 | * utils_cluster_in_use - Determine if a cluster is in use |
701 | * @vol: An ntfs volume obtained from ntfs_mount |
702 | * @lcn: The Logical Cluster Number to test |
703 | * |
704 | * The metadata file $Bitmap has one binary bit representing each cluster on |
705 | * disk. The bit will be set for each cluster that is in use. The function |
706 | * reads the relevant part of $Bitmap into a buffer and tests the bit. |
707 | * |
708 | * This function has a static buffer in which it caches a section of $Bitmap. |
709 | * If the lcn, being tested, lies outside the range, the buffer will be |
710 | * refreshed. @bmplcn stores offset to the first bit (in bits) stored in the |
711 | * buffer. |
712 | * |
713 | * NOTE: Be very carefull with shifts by 3 everywhere in this function. |
714 | * |
715 | * Return: 1 Cluster is in use |
716 | * 0 Cluster is free space |
717 | * -1 Error occurred |
718 | */ |
719 | int utils_cluster_in_use(ntfs_volume *vol, long long lcn) |
720 | { |
721 | static unsigned char buffer[512]; |
722 | static long long bmplcn = -(sizeof(buffer) << 3); |
723 | int byte, bit; |
724 | ntfs_attr *attr; |
725 | |
726 | if (!vol) { |
727 | errno = EINVAL; |
728 | return -1; |
729 | } |
730 | |
731 | /* Does lcn lie in the section of $Bitmap we already have cached? */ |
732 | if ((lcn < bmplcn) |
733 | || (lcn >= (long long)(bmplcn + (sizeof(buffer) << 3)))) { |
734 | ntfs_log_debug("Bit lies outside cache.\n"); |
735 | attr = ntfs_attr_open(vol->lcnbmp_ni, AT_DATA, AT_UNNAMED, 0); |
736 | if (!attr) { |
737 | ntfs_log_perror("Couldn't open $Bitmap"); |
738 | return -1; |
739 | } |
740 | |
741 | /* Mark the buffer as in use, in case the read is shorter. */ |
742 | memset(buffer, 0xFF, sizeof(buffer)); |
743 | bmplcn = lcn & (~((sizeof(buffer) << 3) - 1)); |
744 | |
745 | if (ntfs_attr_pread(attr, (bmplcn >> 3), sizeof(buffer), |
746 | buffer) < 0) { |
747 | ntfs_log_perror("Couldn't read $Bitmap"); |
748 | ntfs_attr_close(attr); |
749 | return -1; |
750 | } |
751 | |
752 | ntfs_log_debug("Reloaded bitmap buffer.\n"); |
753 | ntfs_attr_close(attr); |
754 | } |
755 | |
756 | bit = 1 << (lcn & 7); |
757 | byte = (lcn >> 3) & (sizeof(buffer) - 1); |
758 | ntfs_log_debug("cluster = %lld, bmplcn = %lld, byte = %d, bit = %d, " |
759 | "in use %d\n", lcn, bmplcn, byte, bit, buffer[byte] & |
760 | bit); |
761 | |
762 | return (buffer[byte] & bit); |
763 | } |
764 | |
765 | /** |
766 | * utils_mftrec_in_use - Determine if a MFT Record is in use |
767 | * @vol: An ntfs volume obtained from ntfs_mount |
768 | * @mref: MFT Reference (inode number) |
769 | * |
770 | * The metadata file $BITMAP has one binary bit representing each record in the |
771 | * MFT. The bit will be set for each record that is in use. The function |
772 | * reads the relevant part of $BITMAP into a buffer and tests the bit. |
773 | * |
774 | * This function has a static buffer in which it caches a section of $BITMAP. |
775 | * If the mref, being tested, lies outside the range, the buffer will be |
776 | * refreshed. |
777 | * |
778 | * Return: 1 MFT Record is in use |
779 | * 0 MFT Record is unused |
780 | * -1 Error occurred |
781 | */ |
782 | int utils_mftrec_in_use(ntfs_volume *vol, MFT_REF mref) |
783 | { |
784 | static u8 buffer[512]; |
785 | static s64 bmpmref = -(sizeof(buffer) << 3) - 1; /* Which bit of $BITMAP is in the buffer */ |
786 | int byte, bit; |
787 | |
788 | ntfs_log_trace("Entering.\n"); |
789 | |
790 | if (!vol) { |
791 | errno = EINVAL; |
792 | return -1; |
793 | } |
794 | |
795 | /* Does mref lie in the section of $Bitmap we already have cached? */ |
796 | if (((s64)MREF(mref) < bmpmref) |
797 | || ((s64)MREF(mref) >= (s64)(bmpmref + (sizeof(buffer) << 3)))) { |
798 | ntfs_log_debug("Bit lies outside cache.\n"); |
799 | |
800 | /* Mark the buffer as not in use, in case the read is shorter. */ |
801 | memset(buffer, 0, sizeof(buffer)); |
802 | bmpmref = mref & (~((sizeof(buffer) << 3) - 1)); |
803 | |
804 | if (ntfs_attr_pread(vol->mftbmp_na, (bmpmref>>3), sizeof(buffer), buffer) < 0) { |
805 | ntfs_log_perror("Couldn't read $MFT/$BITMAP"); |
806 | return -1; |
807 | } |
808 | |
809 | ntfs_log_debug("Reloaded bitmap buffer.\n"); |
810 | } |
811 | |
812 | bit = 1 << (mref & 7); |
813 | byte = (mref >> 3) & (sizeof(buffer) - 1); |
814 | ntfs_log_debug("cluster = %lld, bmpmref = %lld, byte = %d, bit = %d, in use %d\n", mref, bmpmref, byte, bit, buffer[byte] & bit); |
815 | |
816 | return (buffer[byte] & bit); |
817 | } |
818 | |
819 | /** |
820 | * __metadata |
821 | */ |
822 | static int __metadata(ntfs_volume *vol, u64 num) |
823 | { |
824 | if (num <= FILE_UpCase) |
825 | return 1; |
826 | if (!vol) |
827 | return -1; |
828 | if ((vol->major_ver == 3) && (num == FILE_Extend)) |
829 | return 1; |
830 | |
831 | return 0; |
832 | } |
833 | |
834 | /** |
835 | * utils_is_metadata - Determine if an inode represents a metadata file |
836 | * @inode: An ntfs inode to be tested |
837 | * |
838 | * A handful of files in the volume contain filesystem data - metadata. |
839 | * They can be identified by their inode number (offset in MFT/$DATA) or by |
840 | * their parent. |
841 | * |
842 | * Return: 1 inode is a metadata file |
843 | * 0 inode is not a metadata file |
844 | * -1 Error occurred |
845 | */ |
846 | int utils_is_metadata(ntfs_inode *inode) |
847 | { |
848 | ntfs_volume *vol; |
849 | ATTR_RECORD *rec; |
850 | FILE_NAME_ATTR *attr; |
851 | MFT_RECORD *file; |
852 | u64 num; |
853 | |
854 | if (!inode) { |
855 | errno = EINVAL; |
856 | return -1; |
857 | } |
858 | |
859 | vol = inode->vol; |
860 | if (!vol) |
861 | return -1; |
862 | |
863 | num = inode->mft_no; |
864 | if (__metadata(vol, num) == 1) |
865 | return 1; |
866 | |
867 | file = inode->mrec; |
868 | if (file && (file->base_mft_record != 0)) { |
869 | num = MREF_LE(file->base_mft_record); |
870 | if (__metadata(vol, num) == 1) |
871 | return 1; |
872 | } |
873 | |
874 | rec = find_first_attribute(AT_FILE_NAME, inode->mrec); |
875 | if (!rec) |
876 | return -1; |
877 | |
878 | /* We know this will always be resident. */ |
879 | attr = (FILE_NAME_ATTR *)((char *)rec + le16_to_cpu(rec->value_offset)); |
880 | |
881 | num = MREF_LE(attr->parent_directory); |
882 | if ((num != FILE_root) && (__metadata(vol, num) == 1)) |
883 | return 1; |
884 | |
885 | return 0; |
886 | } |
887 | |
888 | /** |
889 | * utils_dump_mem - Display a block of memory in hex and ascii |
890 | * @buf: Buffer to be displayed |
891 | * @start: Offset into @buf to start from |
892 | * @length: Number of bytes to display |
893 | * @flags: Options to change the style of the output |
894 | * |
895 | * Display a block of memory in a tradition hex-dump manner. |
896 | * Optionally the ascii part can be turned off. |
897 | * |
898 | * The flags, described fully in utils.h, default to 0 (DM_DEFAULTS). |
899 | * Examples are: DM_INDENT (indent the output by one tab); DM_RED (colour the |
900 | * output); DM_NO_ASCII (only print the hex values). |
901 | */ |
902 | void utils_dump_mem(void *buf, int start, int length, int flags) |
903 | { |
904 | int off, i, s, e, col; |
905 | u8 *mem = buf; |
906 | |
907 | s = start & ~15; // round down |
908 | e = (start + length + 15) & ~15; // round up |
909 | |
910 | for (off = s; off < e; off += 16) { |
911 | col = 30; |
912 | if (flags & DM_RED) |
913 | col += 1; |
914 | if (flags & DM_GREEN) |
915 | col += 2; |
916 | if (flags & DM_BLUE) |
917 | col += 4; |
918 | if (flags & DM_INDENT) |
919 | ntfs_log_debug("\t"); |
920 | if (flags & DM_BOLD) |
921 | ntfs_log_debug("\e[01m"); |
922 | if (flags & (DM_RED | DM_BLUE | DM_GREEN | DM_BOLD)) |
923 | ntfs_log_debug("\e[%dm", col); |
924 | if (off == s) |
925 | ntfs_log_debug("%6.6x ", start); |
926 | else |
927 | ntfs_log_debug("%6.6x ", off); |
928 | |
929 | for (i = 0; i < 16; i++) { |
930 | if ((i == 8) && (!(flags & DM_NO_DIVIDER))) |
931 | ntfs_log_debug(" -"); |
932 | if (((off+i) >= start) && ((off+i) < (start+length))) |
933 | ntfs_log_debug(" %02X", mem[off+i]); |
934 | else |
935 | ntfs_log_debug(" "); |
936 | } |
937 | if (!(flags & DM_NO_ASCII)) { |
938 | ntfs_log_debug(" "); |
939 | for (i = 0; i < 16; i++) { |
940 | if (((off+i) < start) || ((off+i) >= (start+length))) |
941 | ntfs_log_debug(" "); |
942 | else if (isprint(mem[off + i])) |
943 | ntfs_log_debug("%c", mem[off + i]); |
944 | else |
945 | ntfs_log_debug("."); |
946 | } |
947 | } |
948 | if (flags & (DM_RED | DM_BLUE | DM_GREEN | DM_BOLD)) |
949 | ntfs_log_debug("\e[0m"); |
950 | ntfs_log_debug("\n"); |
951 | } |
952 | } |
953 | |
954 | |
955 | /** |
956 | * mft_get_search_ctx |
957 | */ |
958 | struct mft_search_ctx * mft_get_search_ctx(ntfs_volume *vol) |
959 | { |
960 | struct mft_search_ctx *ctx; |
961 | |
962 | if (!vol) { |
963 | errno = EINVAL; |
964 | return NULL; |
965 | } |
966 | |
967 | ctx = (struct mft_search_ctx*)calloc(1, sizeof *ctx); |
968 | |
969 | ctx->mft_num = -1; |
970 | ctx->vol = vol; |
971 | |
972 | return ctx; |
973 | } |
974 | |
975 | /** |
976 | * mft_put_search_ctx |
977 | */ |
978 | void mft_put_search_ctx(struct mft_search_ctx *ctx) |
979 | { |
980 | if (!ctx) |
981 | return; |
982 | if (ctx->inode) |
983 | ntfs_inode_close(ctx->inode); |
984 | free(ctx); |
985 | } |
986 | |
987 | /** |
988 | * mft_next_record |
989 | */ |
990 | int mft_next_record(struct mft_search_ctx *ctx) |
991 | { |
992 | s64 nr_mft_records; |
993 | ATTR_RECORD *attr10 = NULL; |
994 | ATTR_RECORD *attr20 = NULL; |
995 | ATTR_RECORD *attr80 = NULL; |
996 | ntfs_attr_search_ctx *attr_ctx; |
997 | |
998 | if (!ctx) { |
999 | errno = EINVAL; |
1000 | return -1; |
1001 | } |
1002 | |
1003 | if (ctx->inode) { |
1004 | ntfs_inode_close(ctx->inode); |
1005 | ctx->inode = NULL; |
1006 | } |
1007 | |
1008 | nr_mft_records = ctx->vol->mft_na->initialized_size >> |
1009 | ctx->vol->mft_record_size_bits; |
1010 | |
1011 | for (ctx->mft_num++; (s64)ctx->mft_num < nr_mft_records; ctx->mft_num++) { |
1012 | int in_use; |
1013 | |
1014 | ctx->flags_match = 0; |
1015 | in_use = utils_mftrec_in_use(ctx->vol, (MFT_REF) ctx->mft_num); |
1016 | if (in_use == -1) { |
1017 | ntfs_log_error("Error reading inode %llu. Aborting.\n", |
1018 | (unsigned long long)ctx->mft_num); |
1019 | return -1; |
1020 | } |
1021 | |
1022 | if (in_use) { |
1023 | ctx->flags_match |= FEMR_IN_USE; |
1024 | |
1025 | ctx->inode = ntfs_inode_open(ctx->vol, (MFT_REF) ctx->mft_num); |
1026 | if (ctx->inode == NULL) { |
1027 | ntfs_log_error("Error reading inode %llu.\n", (unsigned |
1028 | long long) ctx->mft_num); |
1029 | continue; |
1030 | } |
1031 | |
1032 | attr10 = find_first_attribute(AT_STANDARD_INFORMATION, ctx->inode->mrec); |
1033 | attr20 = find_first_attribute(AT_ATTRIBUTE_LIST, ctx->inode->mrec); |
1034 | attr80 = find_first_attribute(AT_DATA, ctx->inode->mrec); |
1035 | |
1036 | if (attr10) |
1037 | ctx->flags_match |= FEMR_BASE_RECORD; |
1038 | else |
1039 | ctx->flags_match |= FEMR_NOT_BASE_RECORD; |
1040 | |
1041 | if (attr20) |
1042 | ctx->flags_match |= FEMR_BASE_RECORD; |
1043 | |
1044 | if (attr80) |
1045 | ctx->flags_match |= FEMR_FILE; |
1046 | |
1047 | if (ctx->flags_search & FEMR_DIR) { |
1048 | attr_ctx = ntfs_attr_get_search_ctx(ctx->inode, NULL); |
1049 | if (attr_ctx) { |
1050 | if (ntfs_attr_lookup(AT_INDEX_ROOT, NTFS_INDEX_I30, 4, 0, 0, NULL, 0, attr_ctx) == 0) |
1051 | ctx->flags_match |= FEMR_DIR; |
1052 | |
1053 | ntfs_attr_put_search_ctx(attr_ctx); |
1054 | } else { |
1055 | ntfs_log_error("Couldn't create a search context.\n"); |
1056 | return -1; |
1057 | } |
1058 | } |
1059 | |
1060 | switch (utils_is_metadata(ctx->inode)) { |
1061 | case 1: ctx->flags_match |= FEMR_METADATA; break; |
1062 | case 0: ctx->flags_match |= FEMR_NOT_METADATA; break; |
1063 | default: |
1064 | ctx->flags_match |= FEMR_NOT_METADATA; break; |
1065 | //ntfs_log_error("Error reading inode %lld.\n", ctx->mft_num); |
1066 | //return -1; |
1067 | } |
1068 | |
1069 | } else { // !in_use |
1070 | ntfs_attr *mft; |
1071 | |
1072 | ctx->flags_match |= FEMR_NOT_IN_USE; |
1073 | |
1074 | ctx->inode = (ntfs_inode*)calloc(1, sizeof(*ctx->inode)); |
1075 | if (!ctx->inode) { |
1076 | ntfs_log_error("Out of memory. Aborting.\n"); |
1077 | return -1; |
1078 | } |
1079 | |
1080 | ctx->inode->mft_no = ctx->mft_num; |
1081 | ctx->inode->vol = ctx->vol; |
1082 | ctx->inode->mrec = ntfs_malloc(ctx->vol->mft_record_size); |
1083 | if (!ctx->inode->mrec) { |
1084 | free(ctx->inode); // == ntfs_inode_close |
1085 | return -1; |
1086 | } |
1087 | |
1088 | mft = ntfs_attr_open(ctx->vol->mft_ni, AT_DATA, |
1089 | AT_UNNAMED, 0); |
1090 | if (!mft) { |
1091 | ntfs_log_perror("Couldn't open $MFT/$DATA"); |
1092 | // free / close |
1093 | return -1; |
1094 | } |
1095 | |
1096 | if (ntfs_attr_pread(mft, ctx->vol->mft_record_size * ctx->mft_num, ctx->vol->mft_record_size, ctx->inode->mrec) < ctx->vol->mft_record_size) { |
1097 | ntfs_log_perror("Couldn't read MFT Record %llu", |
1098 | (unsigned long long) ctx->mft_num); |
1099 | // free / close |
1100 | ntfs_attr_close(mft); |
1101 | return -1; |
1102 | } |
1103 | |
1104 | ntfs_attr_close(mft); |
1105 | } |
1106 | |
1107 | if (ctx->flags_match & ctx->flags_search) { |
1108 | break; |
1109 | } |
1110 | |
1111 | if (ntfs_inode_close(ctx->inode)) { |
1112 | ntfs_log_error("Error closing inode %llu.\n", |
1113 | (unsigned long long)ctx->mft_num); |
1114 | return -errno; |
1115 | } |
1116 | |
1117 | ctx->inode = NULL; |
1118 | } |
1119 | |
1120 | return (ctx->inode == NULL); |
1121 | } |
1122 | |
1123 | #ifdef HAVE_WINDOWS_H |
1124 | |
1125 | /* |
1126 | * Translate formats for older Windows |
1127 | * |
1128 | * Up to Windows XP, msvcrt.dll does not support long long format |
1129 | * specifications (%lld, %llx, etc). We have to translate them |
1130 | * to %I64. |
1131 | */ |
1132 | |
1133 | char *ntfs_utils_reformat(char *out, int sz, const char *fmt) |
1134 | { |
1135 | const char *f; |
1136 | char *p; |
1137 | int i; |
1138 | enum { F_INIT, F_PERCENT, F_FIRST } state; |
1139 | |
1140 | i = 0; |
1141 | f = fmt; |
1142 | p = out; |
1143 | state = F_INIT; |
1144 | while (*f && ((i + 3) < sz)) { |
1145 | switch (state) { |
1146 | case F_INIT : |
1147 | if (*f == '%') |
1148 | state = F_PERCENT; |
1149 | *p++ = *f++; |
1150 | i++; |
1151 | break; |
1152 | case F_PERCENT : |
1153 | if (*f == 'l') { |
1154 | state = F_FIRST; |
1155 | f++; |
1156 | } else { |
1157 | if (((*f < '0') || (*f > '9')) |
1158 | && (*f != '*') && (*f != '-')) |
1159 | state = F_INIT; |
1160 | *p++ = *f++; |
1161 | i++; |
1162 | } |
1163 | break; |
1164 | case F_FIRST : |
1165 | if (*f == 'l') { |
1166 | *p++ = 'I'; |
1167 | *p++ = '6'; |
1168 | *p++ = '4'; |
1169 | f++; |
1170 | i += 3; |
1171 | } else { |
1172 | *p++ = 'l'; |
1173 | *p++ = *f++; |
1174 | i += 2; |
1175 | } |
1176 | state = F_INIT; |
1177 | break; |
1178 | } |
1179 | } |
1180 | *p++ = 0; |
1181 | return (out); |
1182 | } |
1183 | |
1184 | #endif |
1185 |