/* * Copyright (c) 2006-2008 Apple Inc. All rights reserved. * * This file contains Original Code and/or Modifications of Original Code as * defined in and that are subject to the Apple Public Source License Version * 2.0 (the 'License'). You may not use this file except in compliance with the * License. * * Please obtain a copy of the License at http://www.opensource.apple.com/apsl/ * and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR * A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations under the * License. */ #include #ifndef FSUC_GETUUID #define FSUC_GETUUID 'k' #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Define this if you want debug output to go to syslog. */ //#define NTFS_UTIL_DEBUG #ifdef NTFS_UTIL_DEBUG #include #endif /* NTFS_UTIL_DEBUG */ #include "ntfs.h" #include "ntfs_types.h" #include "ntfs_endian.h" #include "ntfs_layout.h" /* * The NTFS in-memory mount point structure. * * Adapted from ../kext/ntfs_volume.h. */ typedef struct { /* NTFS bootsector provided information. */ u32 sector_size; /* in bytes */ u32 cluster_size; /* in bytes */ u32 mft_record_size; /* in bytes */ u32 index_block_size; /* in bytes */ LCN mft_lcn; /* Cluster location of mft data. */ LCN mftmirr_lcn; /* Cluster location of copy of mft. */ int mftmirr_size; /* Size of mft mirror in mft records. */ LCN nr_clusters; /* Volume size in clusters == number of bits in lcn bitmap. */ } ntfs_volume; typedef struct { MFT_RECORD *m; ATTR_RECORD *a; } ntfs_attr_search_ctx; typedef struct { /* In memory vcn to lcn mapping structure element. */ VCN vcn; /* vcn = Starting virtual cluster number. */ LCN lcn; /* lcn = Starting logical cluster number. */ s64 length; /* Run length in clusters. */ } ntfs_rl_element; /* Runlist allocations happen in multiples of this value in bytes. */ #define NTFS_RL_ALLOC_BLOCK 1024 typedef enum { LCN_HOLE = -1, /* Keep this as highest value or die! */ LCN_RL_NOT_MAPPED = -2, LCN_ENOENT = -3, LCN_ENOMEM = -4, LCN_EIO = -5, } LCN_SPECIAL_VALUES; static void usage(const char *progname) __attribute__((noreturn)); static void usage(const char *progname) { fprintf(stderr, "usage: %s action_arg device_arg [mount_point_arg] [Flags]\n", progname); fprintf(stderr, "action_arg:\n"); fprintf(stderr, " -%c (Get UUID Key)\n", FSUC_GETUUID); fprintf(stderr, " -%c (Mount)\n", FSUC_MOUNT); fprintf(stderr, " -%c (Probe)\n", FSUC_PROBE); fprintf(stderr, " -%c (Unmount)\n", FSUC_UNMOUNT); fprintf(stderr, "device_arg:\n"); fprintf(stderr, " device we are acting upon (for example, 'disk0s2')\n"); fprintf(stderr, "mount_point_arg:\n"); fprintf(stderr, " required for Mount and Unmount\n"); fprintf(stderr, "Flags:\n"); fprintf(stderr, " required for Mount and Probe\n"); fprintf(stderr, " indicates removable or fixed (for example 'fixed')\n"); fprintf(stderr, " indicates readonly or writable (for example 'readonly')\n"); fprintf(stderr, "Flags (Mount only):\n"); fprintf(stderr, " indicates suid or nosuid (for example 'nosuid')\n"); fprintf(stderr, " indicates dev or nodev (for example 'nodev')\n"); fprintf(stderr, "Examples:\n"); fprintf(stderr, " %s -p disk0s2 fixed writable\n", progname); fprintf(stderr, " %s -m disk0s2 /my/hfs removable readonly nosuid nodev\n", progname); exit(FSUR_INVAL); } /** * ntfs_boot_sector_is_valid - check if @b contains a valid ntfs boot sector * @b: Boot sector of device @mp to check. * * Check whether the boot sector @b is a valid ntfs boot sector. * * Return TRUE if it is valid and FALSE if not. * * Copied from ../kext/ntfs_vfsops.c. */ static BOOL ntfs_boot_sector_is_valid(const NTFS_BOOT_SECTOR *b) { /* Check OEMidentifier is "NTFS " */ if (b->oem_id != magicNTFS) goto not_ntfs; /* * Check bytes per sector value is between 256 and * NTFS_MAX_SECTOR_SIZE. */ if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 || le16_to_cpu(b->bpb.bytes_per_sector) > NTFS_MAX_SECTOR_SIZE) goto not_ntfs; /* Check sectors per cluster value is valid. */ switch (b->bpb.sectors_per_cluster) { case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: break; default: goto not_ntfs; } /* Check the cluster size is not above the maximum (64kiB). */ if ((u32)le16_to_cpu(b->bpb.bytes_per_sector) * b->bpb.sectors_per_cluster > NTFS_MAX_CLUSTER_SIZE) goto not_ntfs; /* Check reserved/unused fields are really zero. */ if (le16_to_cpu(b->bpb.reserved_sectors) || le16_to_cpu(b->bpb.root_entries) || le16_to_cpu(b->bpb.sectors) || le16_to_cpu(b->bpb.sectors_per_fat) || le32_to_cpu(b->bpb.large_sectors) || b->bpb.fats) goto not_ntfs; /* * Check clusters per file mft record value is valid. It can be either * between -31 and -9 (in which case the actual mft record size is * -log2() of the absolute value) or a positive power of two. */ if ((u8)b->clusters_per_mft_record < 0xe1 || (u8)b->clusters_per_mft_record > 0xf7) switch (b->clusters_per_mft_record) { case 1: case 2: case 4: case 8: case 16: case 32: case 64: break; default: goto not_ntfs; } /* Check clusters per index block value is valid. */ if ((u8)b->clusters_per_index_block < 0xe1 || (u8)b->clusters_per_index_block > 0xf7) switch (b->clusters_per_index_block) { case 1: case 2: case 4: case 8: case 16: case 32: case 64: break; default: goto not_ntfs; } /* All checks passed, this boot sector is an NTFS boot sector. */ return TRUE; not_ntfs: return FALSE; } /** * ntfs_boot_sector_parse - parse the boot sector and store the data in @vol * @vol: volume structure to initialise with data from boot sector * @b: boot sector to parse * * Parse the ntfs boot sector @b and store all imporant information therein in * the ntfs_volume @vol. * * Return 0 on success and error code on error. The following error codes are * defined: * FSUR_UNRECOGNIZED - Boot sector is invalid/volume is not supported. * * Adapted from ../kext/ntfs_vfsops.c. */ static int ntfs_boot_sector_parse(ntfs_volume *vol, const NTFS_BOOT_SECTOR *b) { s64 ll; int clusters_per_mft_record; vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector); vol->cluster_size = vol->sector_size * b->bpb.sectors_per_cluster; if (vol->cluster_size < vol->sector_size) return FSUR_UNRECOGNIZED; clusters_per_mft_record = b->clusters_per_mft_record; if (clusters_per_mft_record > 0) vol->mft_record_size = vol->cluster_size * clusters_per_mft_record; else /* * When mft_record_size < cluster_size, clusters_per_mft_record * = -log2(mft_record_size) bytes. mft_record_size normaly is * 1024 bytes, which is encoded as 0xF6 (-10 in decimal). */ vol->mft_record_size = 1 << -clusters_per_mft_record; /* * Get the size of the volume in clusters and check for 64-bit-ness. * Windows currently only uses 32 bits to save the clusters so we do * the same as we do not really want to break compatibility. We could * perhaps add a mount option to allow this one day but it would render * such volumes incompatible with Windows. */ ll = sle64_to_cpu(b->number_of_sectors) / b->bpb.sectors_per_cluster; if ((u64)ll >= (u64)1 << 32) return FSUR_UNRECOGNIZED; vol->nr_clusters = ll; ll = sle64_to_cpu(b->mft_lcn); if (ll >= vol->nr_clusters) return FSUR_UNRECOGNIZED; vol->mft_lcn = ll; ll = sle64_to_cpu(b->mftmirr_lcn); if (ll >= vol->nr_clusters) return FSUR_UNRECOGNIZED; vol->mftmirr_lcn = ll; return 0; } /** * ntfs_mst_fixup_post_read - deprotect multi sector transfer protected data * @b: pointer to the data to deprotect * @size: size in bytes of @b * * Perform the necessary post read multi sector transfer fixup and detect the * presence of incomplete multi sector transfers. * * In the case of an incomplete transfer being detected, overwrite the magic of * the ntfs record header being processed with "BAAD" and abort processing. * * Return 0 on success and FSUR_IO_FAIL on error ("BAAD" magic will be * present). * * NOTE: We consider the absence / invalidity of an update sequence array to * mean that the structure is not protected at all and hence does not need to * be fixed up. Thus, we return success and not failure in this case. This is * in contrast to ntfs_mst_fixup_pre_write(), see below. * * Adapted from ../kext/ntfs_mst.c. */ static int ntfs_mst_fixup_post_read(NTFS_RECORD *b, const u32 size) { u16 *usa_pos, *data_pos; u16 usa_ofs, usa_count, usn; /* Setup the variables. */ usa_ofs = le16_to_cpu(b->usa_ofs); /* Decrement usa_count to get number of fixups. */ usa_count = le16_to_cpu(b->usa_count) - 1; /* Size and alignment checks. */ if (size & (NTFS_BLOCK_SIZE - 1) || usa_ofs & 1 || (u32)usa_ofs + ((u32)usa_count * 2) > size || (size >> NTFS_BLOCK_SIZE_SHIFT) != usa_count) return 0; /* Position of usn in update sequence array. */ usa_pos = (u16*)b + usa_ofs/sizeof(u16); /* * The update sequence number which has to be equal to each of the u16 * values before they are fixed up. Note no need to care for * endianness since we are comparing and moving data for on disk * structures which means the data is consistent. If it is consistenty * the wrong endianness it does not make any difference. */ usn = *usa_pos; /* Position in protected data of first u16 that needs fixing up. */ data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; /* Check for incomplete multi sector transfer(s). */ while (usa_count--) { if (*data_pos != usn) { /* * Incomplete multi sector transfer detected! )-: * Set the magic to "BAAD" and return failure. * Note that magic_BAAD is already little endian. */ b->magic = magic_BAAD; return FSUR_IO_FAIL; } data_pos += NTFS_BLOCK_SIZE/sizeof(u16); } /* Re-setup the variables. */ usa_count = le16_to_cpu(b->usa_count) - 1; data_pos = (u16*)b + NTFS_BLOCK_SIZE/sizeof(u16) - 1; /* Fixup all sectors. */ while (usa_count--) { /* * Increment position in usa and restore original data from * the usa into the data buffer. */ *data_pos = *(++usa_pos); /* Increment position in data as well. */ data_pos += NTFS_BLOCK_SIZE/sizeof(u16); } return 0; } /** * ntfs_attr_search_ctx_init - initialize an attribute search context * @ctx: attribute search context to initialize * @m: mft record with which to initialize the search context * * Initialize the attribute search context @ctx with @m. * * Adapted from ../kext/ntfs_attr.c. */ static void ntfs_attr_search_ctx_init(ntfs_attr_search_ctx *ctx, MFT_RECORD *m) { *ctx = (ntfs_attr_search_ctx) { .m = m, .a = (ATTR_RECORD*)((u8*)m + le16_to_cpu(m->attrs_offset)), }; } /** * ntfs_attr_find_in_mft_record - find attribute in mft record * @type: attribute type to find * @ctx: search context with mft record and attribute to search from * * ntfs_attr_find_in_mft_record() takes a search context @ctx as parameter and * searches the mft record specified by @ctx->m, beginning at @ctx->a, for an * attribute of @type, the attribute must be unnamed. * * If the attribute is found, ntfs_attr_find_in_mft_record() returns 0 and * @ctx->a is set to point to the found attribute. * * If the attribute is not found, FSUR_UNRECOGNIZED is returned and @ctx->a is * set to point to the attribute before which the attribute being searched for * would need to be inserted if such an action were to be desired. * * On actual error, ntfs_attr_find_in_mft_record() returns FSUR_IO_FAIL. In * this case @ctx->a is undefined and in particular do not rely on it not * having changed. * * Adapted from ../kext/ntfs_attr.c. */ static int ntfs_attr_find_in_mft_record(const ATTR_TYPE type, ntfs_attr_search_ctx *ctx) { ATTR_RECORD *a; a = ctx->a; for (;; a = (ATTR_RECORD*)((u8*)a + le32_to_cpu(a->length))) { if ((u8*)a < (u8*)ctx->m || (u8*)a > (u8*)ctx->m + le32_to_cpu(ctx->m->bytes_allocated)) break; ctx->a = a; if (le32_to_cpu(a->type) > le32_to_cpu(type) || a->type == AT_END) return FSUR_UNRECOGNIZED; if (!a->length) break; if (a->type != type) continue; /* The search failed if the found attribute is named. */ if (a->name_length) return FSUR_UNRECOGNIZED; return 0; } return FSUR_IO_FAIL; } /** * ntfs_mapping_pairs_decompress - convert mapping pairs array to runlist * @vol: ntfs volume on which the attribute resides * @a: attribute record whose mapping pairs array to decompress * @runlist: runlist in which to insert @a's runlist * * It is up to the caller to serialize access to the runlist @runlist. * * Decompress the attribute @a's mapping pairs array into a runlist. On * success, return the decompressed runlist in @runlist. * * If @runlist already contains a runlist, the decompressed runlist is inserted * into the appropriate place in @runlist and the resultant, combined runlist * is returned in @runlist. * * On error, return error code (FSUR_UNRECOGNIZED, FSUR_IO_FAIL, or FSUR_INVAL). * * Adapted from ../kext/ntfs_runlist.c. */ static int ntfs_mapping_pairs_decompress(ntfs_volume *vol, const ATTR_RECORD *a, ntfs_rl_element **runlist) { VCN vcn; /* Current vcn. */ LCN lcn; /* Current lcn. */ s64 deltaxcn; /* Change in [vl]cn. */ u8 *buf; /* Current position in mapping pairs array. */ u8 *a_end; /* End of attribute. */ ntfs_rl_element *rl; unsigned rlsize; /* Size of runlist buffer. */ int err; u16 rlpos; /* Current runlist position in units of runlist_elements. */ u8 b; /* Current byte offset in buf. */ /* Make sure @a exists and is non-resident. */ if (!a || !a->non_resident || sle64_to_cpu(a->lowest_vcn) < (VCN)0) return FSUR_INVAL; /* Start at vcn = lowest_vcn and lcn 0. */ vcn = sle64_to_cpu(a->lowest_vcn); lcn = 0; /* Get start of the mapping pairs array. */ buf = (u8*)a + le16_to_cpu(a->mapping_pairs_offset); a_end = (u8*)a + le32_to_cpu(a->length); if (buf < (u8*)a || buf > a_end) return FSUR_IO_FAIL; /* If the mapping pairs array is valid but empty, nothing to do. */ if (!vcn && !*buf) return FSUR_UNRECOGNIZED; /* Current position in runlist array. */ rlpos = 0; rlsize = NTFS_RL_ALLOC_BLOCK; /* Allocate NTFS_RL_ALLOC_BLOCK bytes for the runlist. */ rl = malloc(rlsize); if (!rl) return FSUR_INVAL; /* Insert unmapped starting element if necessary. */ if (vcn) { rl->vcn = 0; rl->lcn = LCN_RL_NOT_MAPPED; rl->length = vcn; rlpos++; } while (buf < a_end && *buf) { /* * Allocate more memory if needed, including space for the * not-mapped and terminator elements. */ if (((rlpos + 3) * sizeof(*rl)) > rlsize) { ntfs_rl_element *rl2; rl2 = malloc(rlsize + NTFS_RL_ALLOC_BLOCK); if (!rl2) { err = FSUR_INVAL; goto err; } memcpy(rl2, rl, rlsize); free(rl); rl = rl2; rlsize += NTFS_RL_ALLOC_BLOCK; } /* Enter the current vcn into the current runlist element. */ rl[rlpos].vcn = vcn; /* * Get the change in vcn, i.e. the run length in clusters. * Doing it this way ensures that we sign-extend negative * values. A negative run length does not make any sense, but * hey, I did not design NTFS... */ b = *buf & 0xf; if (b) { if (buf + b >= a_end) { err = FSUR_IO_FAIL; goto err; } for (deltaxcn = (s8)buf[b--]; b; b--) deltaxcn = (deltaxcn << 8) + buf[b]; } else /* The length entry is compulsory. */ deltaxcn = (s64)-1; /* * Assume a negative length to indicate data corruption and * hence clean-up and return NULL. */ if (deltaxcn < 0) { err = FSUR_IO_FAIL; goto err; } /* * Enter the current run length into the current runlist * element. */ rl[rlpos].length = deltaxcn; /* Increment the current vcn by the current run length. */ vcn += deltaxcn; /* * There might be no lcn change at all, as is the case for * sparse clusters on NTFS 3.0+, in which case we set the lcn * to LCN_HOLE. */ if (!(*buf & 0xf0)) rl[rlpos].lcn = LCN_HOLE; else { /* Get the lcn change which really can be negative. */ u8 b2 = *buf & 0xf; b = b2 + ((*buf >> 4) & 0xf); if (buf + b >= a_end) { err = FSUR_IO_FAIL; goto err; } for (deltaxcn = (s8)buf[b--]; b > b2; b--) deltaxcn = (deltaxcn << 8) + buf[b]; /* Change the current lcn to its new value. */ lcn += deltaxcn; /* Check lcn is not below -1. */ if (lcn < (LCN)-1) { err = FSUR_IO_FAIL; goto err; } /* Enter the current lcn into the runlist element. */ rl[rlpos].lcn = lcn; } /* Get to the next runlist element. */ rlpos++; /* Increment the buffer position to the next mapping pair. */ buf += (*buf & 0xf) + ((*buf >> 4) & 0xf) + 1; } if (buf >= a_end) { err = FSUR_IO_FAIL; goto err; } /* * If there is a highest_vcn specified, it must be equal to the final * vcn in the runlist - 1, or something has gone badly wrong. */ deltaxcn = sle64_to_cpu(a->highest_vcn); if (deltaxcn && vcn - 1 != deltaxcn) { err = FSUR_IO_FAIL; goto err; } /* Setup not mapped runlist element if this is the base extent. */ if (!a->lowest_vcn) { VCN max_cluster; max_cluster = ((sle64_to_cpu(a->allocated_size) + vol->cluster_size - 1) / vol->cluster_size) - 1; /* * A highest_vcn of zero means this is a single extent * attribute so simply terminate the runlist with LCN_ENOENT). */ if (deltaxcn) { /* * If there is a difference between the highest_vcn and * the highest cluster, the runlist is either corrupt * or, more likely, there are more extents following * this one. */ if (deltaxcn < max_cluster) { rl[rlpos].vcn = vcn; vcn += rl[rlpos].length = max_cluster - deltaxcn; rl[rlpos].lcn = LCN_RL_NOT_MAPPED; rlpos++; } else if (deltaxcn > max_cluster) { err = FSUR_IO_FAIL; goto err; } } rl[rlpos].lcn = LCN_ENOENT; } else /* Not the base extent. There may be more extents to follow. */ rl[rlpos].lcn = LCN_RL_NOT_MAPPED; /* Setup terminating runlist element. */ rl[rlpos].vcn = vcn; rl[rlpos].length = (s64)0; *runlist = rl; return 0; err: free(rl); return err; } /** * ntfs_rl_vcn_to_lcn - convert a vcn into a lcn given a runlist * @rl: runlist to use for conversion * @vcn: vcn to convert * @clusters: optional return pointer for the number of contiguous clusters * * Convert the virtual cluster number @vcn of an attribute into a logical * cluster number (lcn) of a device using the runlist @rl to map vcns to their * corresponding lcns. * * If @clusters is not NULL, on success (i.e. we return >= LCN_HOLE) we return * the number of contiguous clusters after the returned lcn in *@clusters. * * Since lcns must be >= 0, we use negative return codes with special meaning: * * Return code Meaning / Description * ================================================== * LCN_HOLE Hole / not allocated on disk. * LCN_RL_NOT_MAPPED This is part of the runlist which has not been * inserted into the runlist yet. * LCN_ENOENT There is no such vcn in the attribute. * * Locking: - The caller must have locked the runlist (for reading or writing). * - This function does not touch the lock. * - The runlist is not modified. * * Adapted from ../kext/ntfs_runlist.c. */ static LCN ntfs_rl_vcn_to_lcn(const ntfs_rl_element *rl, const VCN vcn, s64 *clusters) { unsigned i; /* * If @rl is NULL, assume that we have found an unmapped runlist. The * caller can then attempt to map it and fail appropriately if * necessary. */ if (!rl) return LCN_RL_NOT_MAPPED; /* Catch out of lower bounds vcn. */ if (vcn < rl[0].vcn) return LCN_ENOENT; for (i = 0; rl[i].length; i++) { if (vcn < rl[i + 1].vcn) { const s64 ofs = vcn - rl[i].vcn; if (clusters) *clusters = rl[i].length - ofs; if (rl[i].lcn >= (LCN)0) return rl[i].lcn + ofs; return rl[i].lcn; } } /* * Set *@clusters just in case rl[i].lcn is LCN_HOLE. That should * never happen since the terminator element should never be of type * LCN_HOLE but better be safe than sorry. */ if (clusters) *clusters = 0; /* * The terminator element is setup to the correct value, i.e. one of * LCN_HOLE, LCN_RL_NOT_MAPPED, or LCN_ENOENT. */ if (rl[i].lcn < (LCN)0) return rl[i].lcn; /* Just in case... We could replace this with panic() some day. */ return LCN_ENOENT; } /** * ntfs_pread - Read from a raw device file descriptor. * @f: file descriptor to read from * @buf: temporary buffer of size sector_size bytes * @sector_size: sector size in bytes * @dst: destination buffer to read into * @to_read: how many bytes to read into the destination buffer * @ofs: offset into the raw device at which to read * * We are working with the raw device thus we need to do sector aligned i/o. */ static ssize_t ntfs_pread(int f, u8 *buf, long sector_size, void *dst, ssize_t to_read, off_t ofs) { off_t io_pos; ssize_t buf_ofs, to_copy, copied; copied = 0; while (to_read > 0) { io_pos = ofs & ~((off_t)sector_size - 1); buf_ofs = ofs & ((off_t)sector_size - 1); to_copy = sector_size - buf_ofs; if (to_copy > to_read) to_copy = to_read; /* * Accept partial reads as long as they contain the data we * want. */ if (pread(f, buf, sector_size, io_pos) < buf_ofs + to_copy) { if (!copied) copied = -1; break; } memcpy((u8*)dst + copied, buf + buf_ofs, to_copy); to_read -= to_copy; copied += to_copy; } return copied; } /** * get_volume_mft_record - Get the mft record for $Volume system file. * @rdev: raw device containing NTFS volume * @vol: ntfs_volume structure to set up describing the NTFS volume * @mrec: pointer in which to return the mft record of $Volume * * Check whether the volume is an NTFS volume and if so load and return the * initialized ntfs_volume structure in @vol as well as the mft record for the * system file $Volume in @mrec and return FSUR_RECOGNIZED. The caller is * responsible for freeing the returned mft record @mrec using free(). * * If not an NTFS volume return FSUR_UNRECOGNIZED. * * On error return FSUR_INVAL or FSUR_IO_FAIL. */ static int get_volume_mft_record(char *rdev, ntfs_volume *vol, MFT_RECORD **mrec) { VCN vcn; LCN lcn; s64 clusters, io_size; void *buf; NTFS_BOOT_SECTOR *bs; MFT_RECORD *m; ntfs_rl_element *rl; long sector_size; unsigned vcn_ofs; int f, err, to_read; u32 dev_block_size; ntfs_attr_search_ctx ctx; /* * Read the boot sector. We are working with the raw device thus we * need to do sector aligned i/o. * * The maximum supported sector size for the NTFS driver is the system * page size thus query the system for the page size and use that for * the sector size. If the querying fails then use 32768 which is the * maximum value that can be set in the NTFS boot sector (it is stored * in a 16-bit unsigned variable). * * The minumum sector size is the ntfs block size (512 bytes). */ sector_size = sysconf(_SC_PAGE_SIZE); if (sector_size < 0) sector_size = 32768; if (sector_size < NTFS_BLOCK_SIZE) sector_size = NTFS_BLOCK_SIZE; f = open(rdev, O_RDONLY); if (f == -1) { return FSUR_IO_FAIL; } /* * Get the native block size of the device. If it is bigger than * @sector_size we need to do i/o in multiples of the native block * size. */ if (ioctl(f, DKIOCGETBLOCKSIZE, &dev_block_size) < 0) { err = FSUR_IO_FAIL; buf = NULL; goto err; } if (dev_block_size > (u32)sector_size) sector_size = dev_block_size; buf = malloc(sector_size); if (!buf) { err = FSUR_IO_FAIL; goto err; } /* * We can cope with partial reads as long as we have at least the boot * sector which fits inside the first NTFS_BLOCK_SIZE bytes. */ if (read(f, buf, sector_size) < NTFS_BLOCK_SIZE) { err = FSUR_IO_FAIL; goto err; } /* Check if the boot sector is a valid NTFS boot sector. */ bs = buf; if (!ntfs_boot_sector_is_valid(bs)) { err = FSUR_UNRECOGNIZED; goto err; } /* Parse the boot sector and initialize @vol with its information. */ err = ntfs_boot_sector_parse(vol, bs); if (err) goto err; m = malloc(vol->mft_record_size); if (!m) { err = FSUR_INVAL; goto err; } /* Load the mft record for $MFT. */ if (ntfs_pread(f, buf, sector_size, m, vol->mft_record_size, vol->mft_lcn * vol->cluster_size) != (ssize_t)vol->mft_record_size) { err = FSUR_IO_FAIL; goto free_err; } /* Apply the multi sector transfer protection fixups. */ err = ntfs_mst_fixup_post_read((NTFS_RECORD*)m, vol->mft_record_size); if (err) { err = FSUR_IO_FAIL; goto free_err; } /* Lookup $DATA attribute. */ ntfs_attr_search_ctx_init(&ctx, m); err = ntfs_attr_find_in_mft_record(AT_DATA, &ctx); if (err) goto free_err; /* Decompress the mapping pairs array of the attribute. */ rl = NULL; err = ntfs_mapping_pairs_decompress(vol, ctx.a, &rl); if (err) goto free_err; vcn = FILE_Volume * vol->mft_record_size; vcn_ofs = vcn & (vol->cluster_size - 1); vcn /= vol->cluster_size; to_read = vol->mft_record_size; /* * Determine location of $Volume mft record from mft data runlist and * read it into memory. We can safely assume that the first fragment * of $DATA must specify $Volume's location or the volume would not be * boot strappable. */ do { lcn = ntfs_rl_vcn_to_lcn(rl, vcn, &clusters); if (lcn < 0) { err = FSUR_IO_FAIL; goto rl_free_err; } io_size = (clusters * vol->cluster_size) - vcn_ofs; if (io_size > to_read) io_size = to_read; if (ntfs_pread(f, buf, sector_size, m, io_size, (lcn * vol->cluster_size) + vcn_ofs) != io_size) { err = FSUR_IO_FAIL; goto rl_free_err; } to_read -= io_size; vcn += clusters; vcn_ofs = 0; } while (to_read > 0); free(rl); /* Apply the multi sector transfer protection fixups. */ err = ntfs_mst_fixup_post_read((NTFS_RECORD*)m, vol->mft_record_size); if (err) { err = FSUR_IO_FAIL; goto free_err; } (void)close(f); free(buf); /* Finally got the mft record for $Volume. */ *mrec = m; return FSUR_RECOGNIZED; rl_free_err: free(rl); free_err: free(m); err: if (buf) free(buf); (void)close(f); return err; } /** * do_getuuid - Get the UUID key of an NTFS volume. * * If the volume is recognized as an NTFS volume look up its UUID key if it * exists and output it to stdout then return FSUR_RECOGNIZED. * * If there is no UUID then return FSUR_INVAL and do not output anything to * stdout. * * If the volume is not an NTFS volume return FSUR_UNRECOGNIZED. * * On error return FSUR_INVAL or FSUR_IO_FAIL. */ static int do_getuuid(char *rdev) { MFT_RECORD *m; ATTR_RECORD *a; OBJECT_ID_ATTR *obj_id; GUID *guid; unsigned obj_id_len; int err; ntfs_volume vol; ntfs_attr_search_ctx ctx; char uuid[37]; /* Obtain the mft record for $Volume. */ err = get_volume_mft_record(rdev, &vol, &m); if (err != FSUR_RECOGNIZED) goto err; /* Lookup $OBJECT_ID attribute. */ ntfs_attr_search_ctx_init(&ctx, m); err = ntfs_attr_find_in_mft_record(AT_OBJECT_ID, &ctx); if (err) { if (err != FSUR_UNRECOGNIZED) goto free_err; /* There is no volume UUID key which is fine. */ #ifdef NTFS_UTIL_DEBUG /* Log to syslog. */ openlog("ntfs.util", LOG_PID, LOG_DAEMON); syslog(LOG_NOTICE, "Volume does not have a UUID key.\n"); closelog(); #endif /* NTFS_UTIL_DEBUG */ err = FSUR_INVAL; goto free_err; } a = ctx.a; if (a->non_resident) { err = FSUR_IO_FAIL; goto free_err; } obj_id = (OBJECT_ID_ATTR*)((u8*)a + le16_to_cpu(a->value_offset)); obj_id_len = le32_to_cpu(a->value_length); if ((u8*)obj_id + obj_id_len > (u8*)m + vol.mft_record_size || (u8*)obj_id + obj_id_len > (u8*)a + le32_to_cpu(a->length)) { err = FSUR_IO_FAIL; goto free_err; } guid = &obj_id->object_id; /* Convert guid to utf8 uuid. */ if (snprintf(uuid, 37, "%08x-%04x-%04x-%02x%02x-" "%02x%02x%02x%02x%02x%02x", le32_to_cpu(guid->data1), le16_to_cpu(guid->data2), le16_to_cpu(guid->data3), guid->data4[0], guid->data4[1], guid->data4[2], guid->data4[3], guid->data4[4], guid->data4[5], guid->data4[6], guid->data4[7]) != 36) { err = FSUR_IO_FAIL; goto free_err; } /* Output utf8 uuid to stdout. */ #ifdef NTFS_UTIL_DEBUG /* Log to syslog. */ openlog("ntfs.util", LOG_PID, LOG_DAEMON); syslog(LOG_NOTICE, "Volume UUID Key: %s\n", uuid); closelog(); #endif /* NTFS_UTIL_DEBUG */ /* * If this fails it is not a problem and there is nothing wrong with * the volume so ignore the return value. */ (void)write(STDOUT_FILENO, uuid, 36); /* Finally done! */ err = FSUR_IO_SUCCESS; free_err: free(m); err: return err; } /* * Invalid NTFS filename characters are encodeded using the * SFM (Services for Macintosh) private use Unicode characters. * * These should only be used for SMB, MSDOS or NTFS. * * Illegal NTFS Char SFM Unicode Char * ---------------------------------------- * 0x01-0x1f 0xf001-0xf01f * '"' 0xf020 * '*' 0xf021 * '/' 0xf022 * '<' 0xf023 * '>' 0xf024 * '?' 0xf025 * '\' 0xf026 * '|' 0xf027 * ' ' 0xf028 (Only if last char of the name) * '.' 0xf029 (Only if last char of the name) * ---------------------------------------- * * Reference: http://support.microsoft.com/kb/q117258/ */ /* * In the Mac OS 9 days the colon was illegal in a file name. For that reason * SFM had no conversion for the colon. There is a conversion for the * slash. In Mac OS X the slash is illegal in a file name. So for us the colon * is a slash and a slash is a colon. So we can just replace the slash with the * colon in our tables and everything will just work. * * SFM conversion code adapted from xnu/bsd/vfs/vfs_utfconf.c. */ static u8 sfm2mac[0x30] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 00 - 07 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /* 08 - 0F */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* 10 - 17 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* 18 - 1F */ 0x22, 0x2a, 0x3a, 0x3c, 0x3e, 0x3f, 0x5c, 0x7c, /* 20 - 27 */ 0x20, 0x2e /* 28 - 29 */ }; /** * do_probe - Examine a volume to see if we recognize it as an NTFS volume. * * If the volume is recognized as an NTFS volume look up its volume label and * output it to stdout then return FSUR_RECOGNIZED. * * If the volume is not an NTFS volume return FSUR_UNRECOGNIZED. * * On error return FSUR_INVAL or FSUR_IO_FAIL. */ static int do_probe(char *rdev, const BOOL removable __attribute__((unused)), const BOOL writable __attribute__((unused))) { MFT_RECORD *m; ATTR_RECORD *a; ntfschar *uname; CFStringRef s; char *label; size_t label_len; unsigned uname_len, i; int err; CFIndex label_size; ntfs_volume vol; ntfs_attr_search_ctx ctx; /* Obtain the mft record for $Volume. */ err = get_volume_mft_record(rdev, &vol, &m); if (err != FSUR_RECOGNIZED) goto err; /* Lookup $VOLUME_NAME attribute. */ ntfs_attr_search_ctx_init(&ctx, m); err = ntfs_attr_find_in_mft_record(AT_VOLUME_NAME, &ctx); if (err) { if (err != FSUR_UNRECOGNIZED) goto free_err; /* There is no volume label which is fine. */ #ifdef NTFS_UTIL_DEBUG /* Log to syslog. */ openlog("ntfs.util", LOG_PID, LOG_DAEMON); syslog(LOG_NOTICE, "Volume is not labelled.\n"); closelog(); #endif /* NTFS_UTIL_DEBUG */ err = FSUR_RECOGNIZED; goto free_err; } a = ctx.a; if (a->non_resident) { err = FSUR_IO_FAIL; goto free_err; } uname = (ntfschar*)((u8*)a + le16_to_cpu(a->value_offset)); uname_len = le32_to_cpu(a->value_length); if ((u8*)uname + uname_len > (u8*)m + vol.mft_record_size || (u8*)uname + uname_len > (u8*)a + le32_to_cpu(a->length)) { err = FSUR_IO_FAIL; goto free_err; } /* Convert length to number of Unicode characters. */ uname_len /= sizeof(ntfschar); /* * Scan through the name looking for any Services For Macintosh encoded * characters and if any are found replace them with the decoded ones. * We need to do this as the Core Foundation string handling routines * are not aware of the SFM encodings. */ for (i = 0; i < uname_len; i++) { ntfschar c; c = le16_to_cpu(uname[i]); if ((c & 0xffc0) == 0xf000) { c &= 0x003f; if (c <= 0x29) uname[i] = cpu_to_le16(sfm2mac[c]); } } /* Convert volume name to utf8. */ s = CFStringCreateWithBytes(kCFAllocatorDefault, (UInt8*)uname, uname_len * sizeof(ntfschar), kCFStringEncodingUTF16LE, true); if (!s) { err = FSUR_IO_FAIL; goto free_err; } label_size = CFStringGetMaximumSizeOfFileSystemRepresentation(s); label = malloc(label_size); if (!label) { err = FSUR_IO_FAIL; goto s_free_err; } if (!CFStringGetFileSystemRepresentation(s, label, label_size)) { err = FSUR_IO_FAIL; goto label_free_err; } /* Output volume label (now in utf8) to stdout. */ label_len = strlen(label); if (label_len > (size_t)label_size) { err = FSUR_IO_FAIL; goto label_free_err; } #ifdef NTFS_UTIL_DEBUG /* Log to syslog. */ openlog("ntfs.util", LOG_PID, LOG_DAEMON); syslog(LOG_NOTICE, "Volume label: %s\n", label); closelog(); #endif /* NTFS_UTIL_DEBUG */ /* * If this fails it is not a problem and there is nothing wrong with * the volume so ignore the return value. */ (void)write(STDOUT_FILENO, label, label_len); /* Finally done! */ err = FSUR_RECOGNIZED; label_free_err: free(label); s_free_err: CFRelease(s); free_err: free(m); err: return err; } /** * do_exec - Execute an external command. */ static int do_exec(const char *progname, char *const args[]) { pid_t pid; union wait status; int err; pid = fork(); if (pid == -1) { fprintf(stderr, "%s: fork failed: %s\n", progname, strerror(errno)); return FSUR_INVAL; } if (!pid) { /* In child process, execute external command. */ (void)execv(args[0], args); /* We only get here if the execv() failed. */ err = errno; fprintf(stderr, "%s: execv %s failed: %s\n", progname, args[0], strerror(err)); exit(err); } /* In parent process, wait for exernal command to finish. */ if (wait4(pid, (int*)&status, 0, NULL) != pid) { fprintf(stderr, "%s: BUG executing %s command.\n", progname, args[0]); return FSUR_INVAL; } if (!WIFEXITED(status)) { fprintf(stderr, "%s: %s command aborted by signal %d.\n", progname, args[0], WTERMSIG(status)); return FSUR_INVAL; } err = WEXITSTATUS(status); if (err) { fprintf(stderr, "%s: %s command failed: %s\n", progname, args[0], strerror(err)); return FSUR_IO_FAIL; } return FSUR_IO_SUCCESS; } /** * do_mount - Mount a file system. */ static int do_mount(const char *progname, char *dev, char *mp, const BOOL removable __attribute__((unused)), const BOOL readonly, const BOOL nosuid, const BOOL nodev) { char *const kextargs[] = { "/sbin/kextload", "/System/Library/Extensions/ntfs.kext", NULL }; char *mountargs[] = { "/sbin/mount", "-w", "-o", "suid", "-o", "dev", "-t", "ntfs", dev, mp, NULL }; struct vfsconf vfc; if (!mp || !strlen(mp)) return FSUR_INVAL; if (readonly) mountargs[1] = "-r"; if (nosuid) mountargs[3] = "nosuid"; if (nodev) mountargs[5] = "nodev"; /* * If the kext is not loaded, load it now. Ignore any errors as the * mount will fail appropriately if the kext is not loaded. */ if (getvfsbyname("ntfs", &vfc)) (void)do_exec(progname, kextargs); return do_exec(progname, mountargs); } /** * do_unmount - Unmount a volume. */ static int do_unmount(const char *progname, char *mp) { char *const umountargs[] = { "/sbin/umount", mp, NULL }; if (!mp || !strlen(mp)) return FSUR_INVAL; return do_exec(progname, umountargs); } /** * main - Main function, parse arguments and cause required action to be taken. */ int main(int argc, char **argv) { char *progname, *dev, *mp = NULL; int err; char opt; BOOL removable, readonly, nosuid, nodev; char rawdev[MAXPATHLEN]; char blockdev[MAXPATHLEN]; struct stat sb; nodev = nosuid = readonly = removable = FALSE; /* Save & strip off program name. */ progname = argv[0]; argc--; argv++; #ifdef NTFS_UTIL_DEBUG /* Log to syslog. */ openlog("ntfs.util", LOG_PID, LOG_DAEMON); if (argc == 0) syslog(LOG_NOTICE, "Called without arguments!"); else if (argc == 1) syslog(LOG_NOTICE, "Called with %d arguments: %s", argc, argv[0]); else if (argc == 2) syslog(LOG_NOTICE, "Called with %d arguments: %s %s", argc, argv[0], argv[1]); else if (argc == 3) syslog(LOG_NOTICE, "Called with %d arguments: %s %s %s", argc, argv[0], argv[1], argv[2]); else if (argc == 4) syslog(LOG_NOTICE, "Called with %d arguments: %s %s %s %s", argc, argv[0], argv[1], argv[2], argv[3]); else if (argc == 5) syslog(LOG_NOTICE, "Called with %d arguments: %s %s %s %s %s", argc, argv[0], argv[1], argv[2], argv[3], argv[4]); else if (argc == 6) syslog(LOG_NOTICE, "Called with %d arguments: %s %s %s %s %s " "%s", argc, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); else syslog(LOG_NOTICE, "Called with %d arguments: %s %s %s %s %s " "%s %s", argc, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); closelog(); #endif /* NTFS_UTIL_DEBUG */ /* * We support probe, mount, and unmount all of which need the command * option and the device. */ if (argc < 2 || argv[0][0] != '-') usage(progname); opt = argv[0][1]; dev = argv[1]; argc -= 2; argv += 2; /* Check we have the right number of arguments. */ switch (opt) { case FSUC_GETUUID: /* For get UUID key do not need any more arguments. */ if (argc) usage(progname); break; case FSUC_PROBE: /* For probe need the two mountflags also. */ if (argc != 2) usage(progname); break; case FSUC_MOUNT: /* For mount need the mount point and four mountflags also. */ if (argc != 5) usage(progname); break; case FSUC_UNMOUNT: /* For unmount need the mount point also. */ if (argc != 1) usage(progname); break; default: /* Unsupported command. */ usage(progname); break; } /* Check the raw and block device special files exist. */ err = snprintf(rawdev, sizeof(rawdev), "/dev/r%s", dev); if (err >= (int)sizeof(rawdev)) { fprintf(stderr, "%s: Specified device name is too long.\n", progname); exit(FSUR_INVAL); } if (stat(rawdev, &sb)) { fprintf(stderr, "%s: stat %s failed, %s\n", progname, rawdev, strerror(errno)); exit(FSUR_INVAL); } err = snprintf(blockdev, sizeof(blockdev), "/dev/%s", dev); if (err >= (int)sizeof(blockdev)) { fprintf(stderr, "%s: Specified device name is too long.\n", progname); exit(FSUR_INVAL); } if (stat(blockdev, &sb)) { fprintf(stderr, "%s: stat %s failed, %s\n", progname, blockdev, strerror(errno)); exit(FSUR_INVAL); } /* Get the mount point for the mount and unmount cases. */ if (opt == FSUC_MOUNT || opt == FSUC_UNMOUNT) { mp = argv[0]; argc--; argv++; } /* Get the mount flags for the probe and mount cases. */ if (opt == FSUC_PROBE || opt == FSUC_MOUNT) { /* mountflag1: Removable or fixed. */ if (!strcmp(argv[0], DEVICE_REMOVABLE)) removable = TRUE; else if (strcmp(argv[0], DEVICE_FIXED)) usage(progname); /* mountflag2: Readonly or writable. */ if (!strcmp(argv[1], DEVICE_READONLY)) readonly = TRUE; else if (strcmp(argv[1], DEVICE_WRITABLE)) usage(progname); /* Only the mount command supports the third and fourth flag. */ if (opt == FSUC_MOUNT) { /* mountflag3: Nosuid or suid. */ if (!strcmp(argv[2], "nosuid")) nosuid = TRUE; else if (strcmp(argv[2], "suid")) usage(progname); /* mountflag4: Nodev or dev. */ if (!strcmp(argv[3], "nodev")) nodev = TRUE; else if (strcmp(argv[3], "dev")) usage(progname); } } /* Finally execute the required command. */ switch (opt) { case FSUC_GETUUID: err = do_getuuid(rawdev); break; case FSUC_PROBE: err = do_probe(rawdev, removable, readonly); break; case FSUC_MOUNT: err = do_mount(progname, blockdev, mp, removable, readonly, nosuid, nodev); break; case FSUC_UNMOUNT: err = do_unmount(progname, mp); break; default: /* Cannot happen... */ usage(progname); break; } return err; }