| /** |
| * mkntfs - Part of the Linux-NTFS project. |
| * |
| * Copyright (c) 2000-2011 Anton Altaparmakov |
| * Copyright (c) 2001-2005 Richard Russon |
| * Copyright (c) 2002-2006 Szabolcs Szakacsits |
| * Copyright (c) 2005 Erik Sornes |
| * Copyright (c) 2007 Yura Pakhuchiy |
| * Copyright (c) 2010 Jean-Pierre Andre |
| * |
| * This utility will create an NTFS 1.2 or 3.1 volume on a user |
| * specified (block) device. |
| * |
| * Some things (option handling and determination of mount status) have been |
| * adapted from e2fsprogs-1.19 and lib/ext2fs/ismounted.c and misc/mke2fs.c in |
| * particular. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program (in the main directory of the Linux-NTFS source |
| * in the file COPYING); if not, write to the Free Software Foundation, |
| * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #ifdef HAVE_STDIO_H |
| #include <stdio.h> |
| #endif |
| #ifdef HAVE_STDARG_H |
| #include <stdarg.h> |
| #endif |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| #ifdef HAVE_ERRNO_H |
| #include <errno.h> |
| #endif |
| #ifdef HAVE_TIME_H |
| #include <time.h> |
| #endif |
| #ifdef HAVE_SYS_STAT_H |
| #include <sys/stat.h> |
| #endif |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #ifdef HAVE_LIMITS_H |
| #include <limits.h> |
| #endif |
| #ifdef HAVE_LIBGEN_H |
| #include <libgen.h> |
| #endif |
| #ifdef ENABLE_UUID |
| #include <uuid/uuid.h> |
| #endif |
| |
| |
| #ifdef HAVE_GETOPT_H |
| #include <getopt.h> |
| #else |
| extern char *optarg; |
| extern int optind; |
| #endif |
| |
| #ifdef HAVE_LINUX_MAJOR_H |
| # include <linux/major.h> |
| # ifndef MAJOR |
| # define MAJOR(dev) ((dev) >> 8) |
| # define MINOR(dev) ((dev) & 0xff) |
| # endif |
| # ifndef IDE_DISK_MAJOR |
| # ifndef IDE0_MAJOR |
| # define IDE0_MAJOR 3 |
| # define IDE1_MAJOR 22 |
| # define IDE2_MAJOR 33 |
| # define IDE3_MAJOR 34 |
| # define IDE4_MAJOR 56 |
| # define IDE5_MAJOR 57 |
| # define IDE6_MAJOR 88 |
| # define IDE7_MAJOR 89 |
| # define IDE8_MAJOR 90 |
| # define IDE9_MAJOR 91 |
| # endif |
| # define IDE_DISK_MAJOR(M) \ |
| ((M) == IDE0_MAJOR || (M) == IDE1_MAJOR || \ |
| (M) == IDE2_MAJOR || (M) == IDE3_MAJOR || \ |
| (M) == IDE4_MAJOR || (M) == IDE5_MAJOR || \ |
| (M) == IDE6_MAJOR || (M) == IDE7_MAJOR || \ |
| (M) == IDE8_MAJOR || (M) == IDE9_MAJOR) |
| # endif |
| # ifndef SCSI_DISK_MAJOR |
| # ifndef SCSI_DISK0_MAJOR |
| # define SCSI_DISK0_MAJOR 8 |
| # define SCSI_DISK1_MAJOR 65 |
| # define SCSI_DISK7_MAJOR 71 |
| # endif |
| # define SCSI_DISK_MAJOR(M) \ |
| ((M) == SCSI_DISK0_MAJOR || \ |
| ((M) >= SCSI_DISK1_MAJOR && \ |
| (M) <= SCSI_DISK7_MAJOR)) |
| # endif |
| #endif |
| |
| #include "security.h" |
| #include "types.h" |
| #include "attrib.h" |
| #include "bitmap.h" |
| #include "bootsect.h" |
| #include "device.h" |
| #include "dir.h" |
| #include "mft.h" |
| #include "mst.h" |
| #include "runlist.h" |
| #include "utils.h" |
| #include "ntfstime.h" |
| #include "sd.h" |
| #include "boot.h" |
| #include "attrdef.h" |
| /* #include "version.h" */ |
| #include "logging.h" |
| #include "support.h" |
| #include "unistr.h" |
| #include "misc.h" |
| |
| #if defined(__sun) && defined (__SVR4) |
| #undef basename |
| #define basename(name) name |
| #endif |
| |
| typedef enum { WRITE_STANDARD, WRITE_BITMAP, WRITE_LOGFILE } WRITE_TYPE; |
| |
| #ifdef NO_NTFS_DEVICE_DEFAULT_IO_OPS |
| #error "No default device io operations! Cannot build mkntfs. \ |
| You need to run ./configure without the --disable-default-device-io-ops \ |
| switch if you want to be able to build the NTFS utilities." |
| #endif |
| |
| /* Page size on ia32. Can change to 8192 on Alpha. */ |
| #define NTFS_PAGE_SIZE 4096 |
| |
| static char EXEC_NAME[] = "mkntfs"; |
| |
| struct BITMAP_ALLOCATION { |
| struct BITMAP_ALLOCATION *next; |
| LCN lcn; /* first allocated cluster */ |
| s64 length; /* count of consecutive clusters */ |
| } ; |
| |
| /* Upcase $Info, used since Windows 8 */ |
| struct UPCASEINFO { |
| le32 len; |
| le32 filler; |
| le64 crc; |
| le32 osmajor; |
| le32 osminor; |
| le32 build; |
| le16 packmajor; |
| le16 packminor; |
| } ; |
| |
| /** |
| * global variables |
| */ |
| static u8 *g_buf = NULL; |
| static int g_mft_bitmap_byte_size = 0; |
| static u8 *g_mft_bitmap = NULL; |
| static int g_lcn_bitmap_byte_size = 0; |
| static int g_dynamic_buf_size = 0; |
| static u8 *g_dynamic_buf = NULL; |
| static struct UPCASEINFO *g_upcaseinfo = NULL; |
| static runlist *g_rl_mft = NULL; |
| static runlist *g_rl_mft_bmp = NULL; |
| static runlist *g_rl_mftmirr = NULL; |
| static runlist *g_rl_logfile = NULL; |
| static runlist *g_rl_boot = NULL; |
| static runlist *g_rl_bad = NULL; |
| static INDEX_ALLOCATION *g_index_block = NULL; |
| static ntfs_volume *g_vol = NULL; |
| static int g_mft_size = 0; |
| static long long g_mft_lcn = 0; /* lcn of $MFT, $DATA attribute */ |
| static long long g_mftmirr_lcn = 0; /* lcn of $MFTMirr, $DATA */ |
| static long long g_logfile_lcn = 0; /* lcn of $LogFile, $DATA */ |
| static int g_logfile_size = 0; /* in bytes, determined from volume_size */ |
| static long long g_mft_zone_end = 0; /* Determined from volume_size and mft_zone_multiplier, in clusters */ |
| static long long g_num_bad_blocks = 0; /* Number of bad clusters */ |
| static long long *g_bad_blocks = NULL; /* Array of bad clusters */ |
| |
| static struct BITMAP_ALLOCATION *g_allocation = NULL; /* Head of cluster allocations */ |
| |
| /** |
| * struct mkntfs_options |
| */ |
| static struct mkntfs_options { |
| char *dev_name; /* Name of the device, or file, to use */ |
| BOOL enable_compression; /* -C, enables compression of all files on the volume by default. */ |
| BOOL quick_format; /* -f or -Q, fast format, don't zero the volume first. */ |
| BOOL force; /* -F, force fs creation. */ |
| long heads; /* -H, number of heads on device */ |
| BOOL disable_indexing; /* -I, disables indexing of file contents on the volume by default. */ |
| BOOL no_action; /* -n, do not write to device, only display what would be done. */ |
| long long part_start_sect; /* -p, start sector of partition on parent device */ |
| long sector_size; /* -s, in bytes, power of 2, default is 512 bytes. */ |
| long sectors_per_track; /* -S, number of sectors per track on device */ |
| BOOL use_epoch_time; /* -T, fake the time to be 00:00:00 UTC, Jan 1, 1970. */ |
| long mft_zone_multiplier; /* -z, value from 1 to 4. Default is 1. */ |
| long long num_sectors; /* size of device in sectors */ |
| long cluster_size; /* -c, format with this cluster-size */ |
| BOOL with_uuid; /* -U, request setting an uuid */ |
| char *label; /* -L, volume label */ |
| } opts; |
| |
| |
| /** |
| * mkntfs_license |
| */ |
| static void mkntfs_license(void) |
| { |
| ntfs_log_info("%s", ntfs_gpl); |
| } |
| |
| /** |
| * mkntfs_usage |
| */ |
| static void mkntfs_usage(void) |
| { |
| ntfs_log_info("\nUsage: %s [options] device [number-of-sectors]\n" |
| "\n" |
| "Basic options:\n" |
| " -f, --fast Perform a quick format\n" |
| " -Q, --quick Perform a quick format\n" |
| " -L, --label STRING Set the volume label\n" |
| " -C, --enable-compression Enable compression on the volume\n" |
| " -I, --no-indexing Disable indexing on the volume\n" |
| " -n, --no-action Do not write to disk\n" |
| "\n" |
| "Advanced options:\n" |
| " -c, --cluster-size BYTES Specify the cluster size for the volume\n" |
| " -s, --sector-size BYTES Specify the sector size for the device\n" |
| " -p, --partition-start SECTOR Specify the partition start sector\n" |
| " -H, --heads NUM Specify the number of heads\n" |
| " -S, --sectors-per-track NUM Specify the number of sectors per track\n" |
| " -z, --mft-zone-multiplier NUM Set the MFT zone multiplier\n" |
| " -T, --zero-time Fake the time to be 00:00 UTC, Jan 1, 1970\n" |
| " -F, --force Force execution despite errors\n" |
| "\n" |
| "Output options:\n" |
| " -q, --quiet Quiet execution\n" |
| " -v, --verbose Verbose execution\n" |
| " --debug Very verbose execution\n" |
| "\n" |
| "Help options:\n" |
| " -V, --version Display version\n" |
| " -l, --license Display licensing information\n" |
| " -h, --help Display this help\n" |
| "\n", basename(EXEC_NAME)); |
| ntfs_log_info("%s%s\n", ntfs_bugs, ntfs_home); |
| } |
| |
| /** |
| * mkntfs_version |
| */ |
| static void mkntfs_version(void) |
| { |
| ntfs_log_info("\n%s v%s (libntfs-3g)\n\n", EXEC_NAME, VERSION); |
| ntfs_log_info("Create an NTFS volume on a user specified (block) " |
| "device.\n\n"); |
| ntfs_log_info("Copyright (c) 2000-2007 Anton Altaparmakov\n"); |
| ntfs_log_info("Copyright (c) 2001-2005 Richard Russon\n"); |
| ntfs_log_info("Copyright (c) 2002-2006 Szabolcs Szakacsits\n"); |
| ntfs_log_info("Copyright (c) 2005 Erik Sornes\n"); |
| ntfs_log_info("Copyright (c) 2007 Yura Pakhuchiy\n"); |
| ntfs_log_info("Copyright (c) 2010-2012 Jean-Pierre Andre\n"); |
| ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home); |
| } |
| |
| /* |
| * crc64, adapted from http://rpm5.org/docs/api/digest_8c-source.html |
| * ECMA-182 polynomial, see |
| * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-182.pdf |
| */ |
| /* make sure the needed types are defined */ |
| #undef byte |
| #undef uint32_t |
| #undef uint64_t |
| #define byte u8 |
| #define uint32_t u32 |
| #define uint64_t u64 |
| static uint64_t crc64(uint64_t crc, const byte * data, size_t size) |
| /*@*/ |
| { |
| static uint64_t polynomial = 0x9a6c9329ac4bc9b5ULL; |
| static uint64_t xorout = 0xffffffffffffffffULL; |
| static uint64_t table[256]; |
| |
| crc ^= xorout; |
| |
| if (data == NULL) { |
| /* generate the table of CRC remainders for all possible bytes */ |
| uint64_t c; |
| uint32_t i, j; |
| for (i = 0; i < 256; i++) { |
| c = i; |
| for (j = 0; j < 8; j++) { |
| if (c & 1) |
| c = polynomial ^ (c >> 1); |
| else |
| c = (c >> 1); |
| } |
| table[i] = c; |
| } |
| } else |
| while (size) { |
| crc = table[(crc ^ *data) & 0xff] ^ (crc >> 8); |
| size--; |
| data++; |
| } |
| |
| crc ^= xorout; |
| |
| return crc; |
| } |
| |
| /* |
| * Mark a run of clusters as allocated |
| * |
| * Returns FALSE if unsuccessful |
| */ |
| |
| static BOOL bitmap_allocate(LCN lcn, s64 length) |
| { |
| BOOL done; |
| struct BITMAP_ALLOCATION *p; |
| struct BITMAP_ALLOCATION *q; |
| struct BITMAP_ALLOCATION *newall; |
| |
| done = TRUE; |
| if (length) { |
| p = g_allocation; |
| q = (struct BITMAP_ALLOCATION*)NULL; |
| /* locate the first run which starts beyond the requested lcn */ |
| while (p && (p->lcn <= lcn)) { |
| q = p; |
| p = p->next; |
| } |
| /* make sure the requested lcns were not allocated */ |
| if ((q && ((q->lcn + q->length) > lcn)) |
| || (p && ((lcn + length) > p->lcn))) { |
| ntfs_log_error("Bitmap allocation error\n"); |
| done = FALSE; |
| } |
| if (q && ((q->lcn + q->length) == lcn)) { |
| /* extend current run, no overlapping possible */ |
| q->length += length; |
| } else { |
| newall = (struct BITMAP_ALLOCATION*) |
| ntfs_malloc(sizeof(struct BITMAP_ALLOCATION)); |
| if (newall) { |
| newall->lcn = lcn; |
| newall->length = length; |
| newall->next = p; |
| if (q) q->next = newall; |
| else g_allocation = newall; |
| } else { |
| done = FALSE; |
| ntfs_log_perror("Not enough memory"); |
| } |
| } |
| } |
| return (done); |
| } |
| |
| /* |
| * Mark a run of cluster as not allocated |
| * |
| * Returns FALSE if unsuccessful |
| * (freeing free clusters is not considered as an error) |
| */ |
| |
| static BOOL bitmap_deallocate(LCN lcn, s64 length) |
| { |
| BOOL done; |
| struct BITMAP_ALLOCATION *p; |
| struct BITMAP_ALLOCATION *q; |
| LCN first, last; |
| s64 begin_length, end_length; |
| |
| done = TRUE; |
| if (length) { |
| p = g_allocation; |
| q = (struct BITMAP_ALLOCATION*)NULL; |
| /* locate a run which has a common portion */ |
| while (p) { |
| first = (p->lcn > lcn ? p->lcn : lcn); |
| last = ((p->lcn + p->length) < (lcn + length) |
| ? p->lcn + p->length : lcn + length); |
| if (first < last) { |
| /* get the parts which must be kept */ |
| begin_length = first - p->lcn; |
| end_length = p->lcn + p->length - last; |
| /* delete the entry */ |
| if (q) |
| q->next = p->next; |
| else |
| g_allocation = p->next; |
| free(p); |
| /* reallocate the beginning and the end */ |
| if (begin_length |
| && !bitmap_allocate(first - begin_length, |
| begin_length)) |
| done = FALSE; |
| if (end_length |
| && !bitmap_allocate(last, end_length)) |
| done = FALSE; |
| /* restart a full search */ |
| p = g_allocation; |
| q = (struct BITMAP_ALLOCATION*)NULL; |
| } else { |
| q = p; |
| p = p->next; |
| } |
| } |
| } |
| return (done); |
| } |
| |
| /* |
| * Get the allocation status of a single cluster |
| * and mark as allocated |
| * |
| * Returns 1 if the cluster was previously allocated |
| */ |
| |
| static int bitmap_get_and_set(LCN lcn, unsigned long length) |
| { |
| struct BITMAP_ALLOCATION *p; |
| struct BITMAP_ALLOCATION *q; |
| int bit; |
| |
| if (length == 1) { |
| p = g_allocation; |
| q = (struct BITMAP_ALLOCATION*)NULL; |
| /* locate the first run which starts beyond the requested lcn */ |
| while (p && (p->lcn <= lcn)) { |
| q = p; |
| p = p->next; |
| } |
| if (q && (q->lcn <= lcn) && ((q->lcn + q->length) > lcn)) |
| bit = 1; /* was allocated */ |
| else { |
| bitmap_allocate(lcn, length); |
| bit = 0; |
| } |
| } else { |
| ntfs_log_error("Can only allocate a single cluster at a time\n"); |
| bit = 0; |
| } |
| return (bit); |
| } |
| |
| /* |
| * Build a section of the bitmap according to allocation |
| */ |
| |
| static void bitmap_build(u8 *buf, LCN lcn, s64 length) |
| { |
| struct BITMAP_ALLOCATION *p; |
| LCN first, last; |
| int j; /* byte number */ |
| int bn; /* bit number */ |
| |
| for (j=0; (8*j)<length; j++) |
| buf[j] = 0; |
| for (p=g_allocation; p; p=p->next) { |
| first = (p->lcn > lcn ? p->lcn : lcn); |
| last = ((p->lcn + p->length) < (lcn + length) |
| ? p->lcn + p->length : lcn + length); |
| if (first < last) { |
| bn = first - lcn; |
| /* initial partial byte, if any */ |
| while ((bn < (last - lcn)) && (bn & 7)) { |
| buf[bn >> 3] |= 1 << (bn & 7); |
| bn++; |
| } |
| /* full bytes */ |
| while (bn < (last - lcn - 7)) { |
| buf[bn >> 3] = 255; |
| bn += 8; |
| } |
| /* final partial byte, if any */ |
| while (bn < (last - lcn)) { |
| buf[bn >> 3] |= 1 << (bn & 7); |
| bn++; |
| } |
| } |
| } |
| } |
| |
| /** |
| * mkntfs_parse_long |
| */ |
| static BOOL mkntfs_parse_long(const char *string, const char *name, long *num) |
| { |
| char *end = NULL; |
| long tmp; |
| |
| if (!string || !name || !num) |
| return FALSE; |
| |
| if (*num >= 0) { |
| ntfs_log_error("You may only specify the %s once.\n", name); |
| return FALSE; |
| } |
| |
| tmp = strtol(string, &end, 0); |
| if (end && *end) { |
| ntfs_log_error("Cannot understand the %s '%s'.\n", name, string); |
| return FALSE; |
| } else { |
| *num = tmp; |
| return TRUE; |
| } |
| } |
| |
| /** |
| * mkntfs_parse_llong |
| */ |
| static BOOL mkntfs_parse_llong(const char *string, const char *name, |
| long long *num) |
| { |
| char *end = NULL; |
| long long tmp; |
| |
| if (!string || !name || !num) |
| return FALSE; |
| |
| if (*num >= 0) { |
| ntfs_log_error("You may only specify the %s once.\n", name); |
| return FALSE; |
| } |
| |
| tmp = strtoll(string, &end, 0); |
| if (end && *end) { |
| ntfs_log_error("Cannot understand the %s '%s'.\n", name, |
| string); |
| return FALSE; |
| } else { |
| *num = tmp; |
| return TRUE; |
| } |
| } |
| |
| /** |
| * mkntfs_init_options |
| */ |
| static void mkntfs_init_options(struct mkntfs_options *opts2) |
| { |
| if (!opts2) |
| return; |
| |
| memset(opts2, 0, sizeof(*opts2)); |
| |
| /* Mark all the numeric options as "unset". */ |
| opts2->cluster_size = -1; |
| opts2->heads = -1; |
| opts2->mft_zone_multiplier = -1; |
| opts2->num_sectors = -1; |
| opts2->part_start_sect = -1; |
| opts2->sector_size = -1; |
| opts2->sectors_per_track = -1; |
| } |
| |
| /** |
| * mkntfs_parse_options |
| */ |
| static BOOL mkntfs_parse_options(int argc, char *argv[], struct mkntfs_options *opts2) |
| { |
| static const char *sopt = "-c:CfFhH:IlL:np:qQs:S:TUvVz:"; |
| static const struct option lopt[] = { |
| { "cluster-size", required_argument, NULL, 'c' }, |
| { "debug", no_argument, NULL, 'Z' }, |
| { "enable-compression", no_argument, NULL, 'C' }, |
| { "fast", no_argument, NULL, 'f' }, |
| { "force", no_argument, NULL, 'F' }, |
| { "heads", required_argument, NULL, 'H' }, |
| { "help", no_argument, NULL, 'h' }, |
| { "label", required_argument, NULL, 'L' }, |
| { "license", no_argument, NULL, 'l' }, |
| { "mft-zone-multiplier",required_argument, NULL, 'z' }, |
| { "no-action", no_argument, NULL, 'n' }, |
| { "no-indexing", no_argument, NULL, 'I' }, |
| { "partition-start", required_argument, NULL, 'p' }, |
| { "quick", no_argument, NULL, 'Q' }, |
| { "quiet", no_argument, NULL, 'q' }, |
| { "sector-size", required_argument, NULL, 's' }, |
| { "sectors-per-track", required_argument, NULL, 'S' }, |
| { "with-uuid", no_argument, NULL, 'U' }, |
| { "verbose", no_argument, NULL, 'v' }, |
| { "version", no_argument, NULL, 'V' }, |
| { "zero-time", no_argument, NULL, 'T' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| int c = -1; |
| int lic = 0; |
| int err = 0; |
| int ver = 0; |
| |
| if (!argv || !opts2) { |
| ntfs_log_error("Internal error: invalid parameters to " |
| "mkntfs_options.\n"); |
| return FALSE; |
| } |
| |
| opterr = 0; /* We'll handle the errors, thank you. */ |
| |
| while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { |
| switch (c) { |
| case 1: /* A device, or a number of sectors */ |
| if (!opts2->dev_name) |
| opts2->dev_name = argv[optind - 1]; |
| else if (!mkntfs_parse_llong(optarg, |
| "number of sectors", |
| &opts2->num_sectors)) |
| err++; |
| break; |
| case 'C': |
| opts2->enable_compression = TRUE; |
| break; |
| case 'c': |
| if (!mkntfs_parse_long(optarg, "cluster size", |
| &opts2->cluster_size)) |
| err++; |
| break; |
| case 'F': |
| opts2->force = TRUE; |
| break; |
| case 'f': /* fast */ |
| case 'Q': /* quick */ |
| opts2->quick_format = TRUE; |
| break; |
| case 'H': |
| if (!mkntfs_parse_long(optarg, "heads", &opts2->heads)) |
| err++; |
| break; |
| case 'h': |
| err++; /* display help */ |
| break; |
| case 'I': |
| opts2->disable_indexing = TRUE; |
| break; |
| case 'L': |
| if (!opts2->label) { |
| opts2->label = argv[optind-1]; |
| } else { |
| ntfs_log_error("You may only specify the label " |
| "once.\n"); |
| err++; |
| } |
| break; |
| case 'l': |
| lic++; /* display the license */ |
| break; |
| case 'n': |
| opts2->no_action = TRUE; |
| break; |
| case 'p': |
| if (!mkntfs_parse_llong(optarg, "partition start", |
| &opts2->part_start_sect)) |
| err++; |
| break; |
| case 'q': |
| ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET | |
| NTFS_LOG_LEVEL_VERBOSE | |
| NTFS_LOG_LEVEL_PROGRESS); |
| break; |
| case 's': |
| if (!mkntfs_parse_long(optarg, "sector size", |
| &opts2->sector_size)) |
| err++; |
| break; |
| case 'S': |
| if (!mkntfs_parse_long(optarg, "sectors per track", |
| &opts2->sectors_per_track)) |
| err++; |
| break; |
| case 'T': |
| opts2->use_epoch_time = TRUE; |
| break; |
| case 'U': |
| opts2->with_uuid = TRUE; |
| break; |
| case 'v': |
| ntfs_log_set_levels(NTFS_LOG_LEVEL_QUIET | |
| NTFS_LOG_LEVEL_VERBOSE | |
| NTFS_LOG_LEVEL_PROGRESS); |
| break; |
| case 'V': |
| ver++; /* display version info */ |
| break; |
| case 'Z': /* debug - turn on everything */ |
| ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG | |
| NTFS_LOG_LEVEL_TRACE | |
| NTFS_LOG_LEVEL_VERBOSE | |
| NTFS_LOG_LEVEL_QUIET); |
| break; |
| case 'z': |
| if (!mkntfs_parse_long(optarg, "mft zone multiplier", |
| &opts2->mft_zone_multiplier)) |
| err++; |
| break; |
| default: |
| if (ntfs_log_parse_option (argv[optind-1])) |
| break; |
| if (((optopt == 'c') || (optopt == 'H') || |
| (optopt == 'L') || (optopt == 'p') || |
| (optopt == 's') || (optopt == 'S') || |
| (optopt == 'N') || (optopt == 'z')) && |
| (!optarg)) { |
| ntfs_log_error("Option '%s' requires an " |
| "argument.\n", argv[optind-1]); |
| } else if (optopt != '?') { |
| ntfs_log_error("Unknown option '%s'.\n", |
| argv[optind - 1]); |
| } |
| err++; |
| break; |
| } |
| } |
| |
| if (!err && !ver && !lic) { |
| if (opts2->dev_name == NULL) { |
| if (argc > 1) |
| ntfs_log_error("You must specify a device.\n"); |
| err++; |
| } |
| } |
| |
| if (ver) |
| mkntfs_version(); |
| if (lic) |
| mkntfs_license(); |
| if (err) |
| mkntfs_usage(); |
| |
| return (!err && !ver && !lic); |
| } |
| |
| |
| /** |
| * mkntfs_time |
| */ |
| static ntfs_time mkntfs_time(void) |
| { |
| struct timespec ts; |
| |
| ts.tv_sec = 0; |
| ts.tv_nsec = 0; |
| if (!opts.use_epoch_time) |
| ts.tv_sec = time(NULL); |
| return timespec2ntfs(ts); |
| } |
| |
| /** |
| * append_to_bad_blocks |
| */ |
| static BOOL append_to_bad_blocks(unsigned long long block) |
| { |
| long long *new_buf; |
| |
| if (!(g_num_bad_blocks & 15)) { |
| new_buf = realloc(g_bad_blocks, (g_num_bad_blocks + 16) * |
| sizeof(long long)); |
| if (!new_buf) { |
| ntfs_log_perror("Reallocating memory for bad blocks " |
| "list failed"); |
| return FALSE; |
| } |
| g_bad_blocks = new_buf; |
| } |
| g_bad_blocks[g_num_bad_blocks++] = block; |
| return TRUE; |
| } |
| |
| /** |
| * mkntfs_write |
| */ |
| static long long mkntfs_write(struct ntfs_device *dev, |
| const void *b, long long count) |
| { |
| long long bytes_written, total; |
| int retry; |
| |
| if (opts.no_action) |
| return count; |
| total = 0LL; |
| retry = 0; |
| do { |
| bytes_written = dev->d_ops->write(dev, b, count); |
| if (bytes_written == -1LL) { |
| retry = errno; |
| ntfs_log_perror("Error writing to %s", dev->d_name); |
| errno = retry; |
| return bytes_written; |
| } else if (!bytes_written) { |
| retry++; |
| } else { |
| count -= bytes_written; |
| total += bytes_written; |
| } |
| } while (count && retry < 3); |
| if (count) |
| ntfs_log_error("Failed to complete writing to %s after three retries." |
| "\n", dev->d_name); |
| return total; |
| } |
| |
| /** |
| * Build and write a part of the global bitmap |
| * without overflowing from the allocated buffer |
| * |
| * mkntfs_bitmap_write |
| */ |
| static s64 mkntfs_bitmap_write(struct ntfs_device *dev, |
| s64 offset, s64 length) |
| { |
| s64 partial_length; |
| s64 written; |
| |
| partial_length = length; |
| if (partial_length > g_dynamic_buf_size) |
| partial_length = g_dynamic_buf_size; |
| /* create a partial bitmap section, and write it */ |
| bitmap_build(g_dynamic_buf,offset << 3,partial_length << 3); |
| written = dev->d_ops->write(dev, g_dynamic_buf, partial_length); |
| return (written); |
| } |
| |
| /** |
| * Build and write a part of the log file |
| * without overflowing from the allocated buffer |
| * |
| * mkntfs_logfile_write |
| */ |
| static s64 mkntfs_logfile_write(struct ntfs_device *dev, |
| s64 offset __attribute__((unused)), s64 length) |
| { |
| s64 partial_length; |
| s64 written; |
| |
| partial_length = length; |
| if (partial_length > g_dynamic_buf_size) |
| partial_length = g_dynamic_buf_size; |
| /* create a partial bad cluster section, and write it */ |
| memset(g_dynamic_buf, -1, partial_length); |
| written = dev->d_ops->write(dev, g_dynamic_buf, partial_length); |
| return (written); |
| } |
| |
| /** |
| * ntfs_rlwrite - Write to disk the clusters contained in the runlist @rl |
| * taking the data from @val. Take @val_len bytes from @val and pad the |
| * rest with zeroes. |
| * |
| * If the @rl specifies a completely sparse file, @val is allowed to be NULL. |
| * |
| * @inited_size if not NULL points to an output variable which will contain |
| * the actual number of bytes written to disk. I.e. this will not include |
| * sparse bytes for example. |
| * |
| * Return the number of bytes written (minus padding) or -1 on error. Errno |
| * will be set to the error code. |
| */ |
| static s64 ntfs_rlwrite(struct ntfs_device *dev, const runlist *rl, |
| const u8 *val, const s64 val_len, s64 *inited_size, |
| WRITE_TYPE write_type) |
| { |
| s64 bytes_written, total, length, delta; |
| int retry, i; |
| |
| if (inited_size) |
| *inited_size = 0LL; |
| if (opts.no_action) |
| return val_len; |
| total = 0LL; |
| delta = 0LL; |
| for (i = 0; rl[i].length; i++) { |
| length = rl[i].length * g_vol->cluster_size; |
| /* Don't write sparse runs. */ |
| if (rl[i].lcn == -1) { |
| total += length; |
| if (!val) |
| continue; |
| /* TODO: Check that *val is really zero at pos and len. */ |
| continue; |
| } |
| /* |
| * Break up the write into the real data write and then a write |
| * of zeroes between the end of the real data and the end of |
| * the (last) run. |
| */ |
| if (total + length > val_len) { |
| delta = length; |
| length = val_len - total; |
| delta -= length; |
| } |
| if (dev->d_ops->seek(dev, rl[i].lcn * g_vol->cluster_size, |
| SEEK_SET) == (off_t)-1) |
| return -1LL; |
| retry = 0; |
| do { |
| /* use specific functions if buffer is not prefilled */ |
| switch (write_type) { |
| case WRITE_BITMAP : |
| bytes_written = mkntfs_bitmap_write(dev, |
| total, length); |
| break; |
| case WRITE_LOGFILE : |
| bytes_written = mkntfs_logfile_write(dev, |
| total, length); |
| break; |
| default : |
| bytes_written = dev->d_ops->write(dev, |
| val + total, length); |
| break; |
| } |
| if (bytes_written == -1LL) { |
| retry = errno; |
| ntfs_log_perror("Error writing to %s", |
| dev->d_name); |
| errno = retry; |
| return bytes_written; |
| } |
| if (bytes_written) { |
| length -= bytes_written; |
| total += bytes_written; |
| if (inited_size) |
| *inited_size += bytes_written; |
| } else { |
| retry++; |
| } |
| } while (length && retry < 3); |
| if (length) { |
| ntfs_log_error("Failed to complete writing to %s after three " |
| "retries.\n", dev->d_name); |
| return total; |
| } |
| } |
| if (delta) { |
| int eo; |
| char *b = ntfs_calloc(delta); |
| if (!b) |
| return -1; |
| bytes_written = mkntfs_write(dev, b, delta); |
| eo = errno; |
| free(b); |
| errno = eo; |
| if (bytes_written == -1LL) |
| return bytes_written; |
| } |
| return total; |
| } |
| |
| /** |
| * make_room_for_attribute - make room for an attribute inside an mft record |
| * @m: mft record |
| * @pos: position at which to make space |
| * @size: byte size to make available at this position |
| * |
| * @pos points to the attribute in front of which we want to make space. |
| * |
| * Return 0 on success or -errno on error. Possible error codes are: |
| * |
| * -ENOSPC There is not enough space available to complete |
| * operation. The caller has to make space before calling |
| * this. |
| * -EINVAL Can only occur if mkntfs was compiled with -DDEBUG. Means |
| * the input parameters were faulty. |
| */ |
| static int make_room_for_attribute(MFT_RECORD *m, char *pos, const u32 size) |
| { |
| u32 biu; |
| |
| if (!size) |
| return 0; |
| #ifdef DEBUG |
| /* |
| * Rigorous consistency checks. Always return -EINVAL even if more |
| * appropriate codes exist for simplicity of parsing the return value. |
| */ |
| if (size != ((size + 7) & ~7)) { |
| ntfs_log_error("make_room_for_attribute() received non 8-byte aligned " |
| "size.\n"); |
| return -EINVAL; |
| } |
| if (!m || !pos) |
| return -EINVAL; |
| if (pos < (char*)m || pos + size < (char*)m || |
| pos > (char*)m + le32_to_cpu(m->bytes_allocated) || |
| pos + size > (char*)m + le32_to_cpu(m->bytes_allocated)) |
| return -EINVAL; |
| /* The -8 is for the attribute terminator. */ |
| if (pos - (char*)m > (int)le32_to_cpu(m->bytes_in_use) - 8) |
| return -EINVAL; |
| #endif |
| biu = le32_to_cpu(m->bytes_in_use); |
| /* Do we have enough space? */ |
| if (biu + size > le32_to_cpu(m->bytes_allocated)) |
| return -ENOSPC; |
| /* Move everything after pos to pos + size. */ |
| memmove(pos + size, pos, biu - (pos - (char*)m)); |
| /* Update mft record. */ |
| m->bytes_in_use = cpu_to_le32(biu + size); |
| return 0; |
| } |
| |
| /** |
| * deallocate_scattered_clusters |
| */ |
| static void deallocate_scattered_clusters(const runlist *rl) |
| { |
| int i; |
| |
| if (!rl) |
| return; |
| /* Iterate over all runs in the runlist @rl. */ |
| for (i = 0; rl[i].length; i++) { |
| /* Skip sparse runs. */ |
| if (rl[i].lcn == -1LL) |
| continue; |
| /* Deallocate the current run. */ |
| bitmap_deallocate(rl[i].lcn, rl[i].length); |
| } |
| } |
| |
| /** |
| * allocate_scattered_clusters |
| * @clusters: Amount of clusters to allocate. |
| * |
| * Allocate @clusters and create a runlist of the allocated clusters. |
| * |
| * Return the allocated runlist. Caller has to free the runlist when finished |
| * with it. |
| * |
| * On error return NULL and errno is set to the error code. |
| * |
| * TODO: We should be returning the size as well, but for mkntfs this is not |
| * necessary. |
| */ |
| static runlist * allocate_scattered_clusters(s64 clusters) |
| { |
| runlist *rl = NULL, *rlt; |
| VCN vcn = 0LL; |
| LCN lcn, end, prev_lcn = 0LL; |
| int rlpos = 0; |
| int rlsize = 0; |
| s64 prev_run_len = 0LL; |
| char bit; |
| |
| end = g_vol->nr_clusters; |
| /* Loop until all clusters are allocated. */ |
| while (clusters) { |
| /* Loop in current zone until we run out of free clusters. */ |
| for (lcn = g_mft_zone_end; lcn < end; lcn++) { |
| bit = bitmap_get_and_set(lcn,1); |
| if (bit) |
| continue; |
| /* |
| * Reallocate memory if necessary. Make sure we have |
| * enough for the terminator entry as well. |
| */ |
| if ((rlpos + 2) * (int)sizeof(runlist) >= rlsize) { |
| rlsize += 4096; /* PAGE_SIZE */ |
| rlt = realloc(rl, rlsize); |
| if (!rlt) |
| goto err_end; |
| rl = rlt; |
| } |
| /* Coalesce with previous run if adjacent LCNs. */ |
| if (prev_lcn == lcn - prev_run_len) { |
| rl[rlpos - 1].length = ++prev_run_len; |
| vcn++; |
| } else { |
| rl[rlpos].vcn = vcn++; |
| rl[rlpos].lcn = lcn; |
| prev_lcn = lcn; |
| rl[rlpos].length = 1LL; |
| prev_run_len = 1LL; |
| rlpos++; |
| } |
| /* Done? */ |
| if (!--clusters) { |
| /* Add terminator element and return. */ |
| rl[rlpos].vcn = vcn; |
| rl[rlpos].lcn = 0LL; |
| rl[rlpos].length = 0LL; |
| return rl; |
| } |
| |
| } |
| /* Switch to next zone, decreasing mft zone by factor 2. */ |
| end = g_mft_zone_end; |
| g_mft_zone_end >>= 1; |
| /* Have we run out of space on the volume? */ |
| if (g_mft_zone_end <= 0) |
| goto err_end; |
| } |
| return rl; |
| err_end: |
| if (rl) { |
| /* Add terminator element. */ |
| rl[rlpos].vcn = vcn; |
| rl[rlpos].lcn = -1LL; |
| rl[rlpos].length = 0LL; |
| /* Deallocate all allocated clusters. */ |
| deallocate_scattered_clusters(rl); |
| /* Free the runlist. */ |
| free(rl); |
| } |
| return NULL; |
| } |
| |
| /** |
| * ntfs_attr_find - find (next) attribute in mft record |
| * @type: attribute type to find |
| * @name: attribute name to find (optional, i.e. NULL means don't care) |
| * @name_len: attribute name length (only needed if @name present) |
| * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) |
| * @val: attribute value to find (optional, resident attributes only) |
| * @val_len: attribute value length |
| * @ctx: search context with mft record and attribute to search from |
| * |
| * You shouldn't need to call this function directly. Use lookup_attr() instead. |
| * |
| * ntfs_attr_find() takes a search context @ctx as parameter and searches the |
| * mft record specified by @ctx->mrec, beginning at @ctx->attr, for an |
| * attribute of @type, optionally @name and @val. If found, ntfs_attr_find() |
| * returns 0 and @ctx->attr will point to the found attribute. |
| * |
| * If not found, ntfs_attr_find() returns -1, with errno set to ENOENT and |
| * @ctx->attr will 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() returns -1 with errno set to the error |
| * code but not to ENOENT. In this case @ctx->attr is undefined and in |
| * particular do not rely on it not changing. |
| * |
| * If @ctx->is_first is TRUE, the search begins with @ctx->attr itself. If it |
| * is FALSE, the search begins after @ctx->attr. |
| * |
| * If @type is AT_UNUSED, return the first found attribute, i.e. one can |
| * enumerate all attributes by setting @type to AT_UNUSED and then calling |
| * ntfs_attr_find() repeatedly until it returns -1 with errno set to ENOENT to |
| * indicate that there are no more entries. During the enumeration, each |
| * successful call of ntfs_attr_find() will return the next attribute in the |
| * mft record @ctx->mrec. |
| * |
| * If @type is AT_END, seek to the end and return -1 with errno set to ENOENT. |
| * AT_END is not a valid attribute, its length is zero for example, thus it is |
| * safer to return error instead of success in this case. This also allows us |
| * to interoperate cleanly with ntfs_external_attr_find(). |
| * |
| * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present |
| * but not AT_UNNAMED search for a named attribute matching @name. Otherwise, |
| * match both named and unnamed attributes. |
| * |
| * If @ic is IGNORE_CASE, the @name comparison is not case sensitive and |
| * @ctx->ntfs_ino must be set to the ntfs inode to which the mft record |
| * @ctx->mrec belongs. This is so we can get at the ntfs volume and hence at |
| * the upcase table. If @ic is CASE_SENSITIVE, the comparison is case |
| * sensitive. When @name is present, @name_len is the @name length in Unicode |
| * characters. |
| * |
| * If @name is not present (NULL), we assume that the unnamed attribute is |
| * being searched for. |
| * |
| * Finally, the resident attribute value @val is looked for, if present. |
| * If @val is not present (NULL), @val_len is ignored. |
| * |
| * ntfs_attr_find() only searches the specified mft record and it ignores the |
| * presence of an attribute list attribute (unless it is the one being searched |
| * for, obviously). If you need to take attribute lists into consideration, use |
| * ntfs_attr_lookup() instead (see below). This also means that you cannot use |
| * ntfs_attr_find() to search for extent records of non-resident attributes, as |
| * extents with lowest_vcn != 0 are usually described by the attribute list |
| * attribute only. - Note that it is possible that the first extent is only in |
| * the attribute list while the last extent is in the base mft record, so don't |
| * rely on being able to find the first extent in the base mft record. |
| * |
| * Warning: Never use @val when looking for attribute types which can be |
| * non-resident as this most likely will result in a crash! |
| */ |
| static int mkntfs_attr_find(const ATTR_TYPES type, const ntfschar *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const u8 *val, const u32 val_len, ntfs_attr_search_ctx *ctx) |
| { |
| ATTR_RECORD *a; |
| ntfschar *upcase = g_vol->upcase; |
| u32 upcase_len = g_vol->upcase_len; |
| |
| /* |
| * Iterate over attributes in mft record starting at @ctx->attr, or the |
| * attribute following that, if @ctx->is_first is TRUE. |
| */ |
| if (ctx->is_first) { |
| a = ctx->attr; |
| ctx->is_first = FALSE; |
| } else { |
| a = (ATTR_RECORD*)((char*)ctx->attr + |
| le32_to_cpu(ctx->attr->length)); |
| } |
| for (;; a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length))) { |
| if (p2n(a) < p2n(ctx->mrec) || (char*)a > (char*)ctx->mrec + |
| le32_to_cpu(ctx->mrec->bytes_allocated)) |
| break; |
| ctx->attr = a; |
| if (((type != AT_UNUSED) && (le32_to_cpu(a->type) > |
| le32_to_cpu(type))) || |
| (a->type == AT_END)) { |
| errno = ENOENT; |
| return -1; |
| } |
| if (!a->length) |
| break; |
| /* If this is an enumeration return this attribute. */ |
| if (type == AT_UNUSED) |
| return 0; |
| if (a->type != type) |
| continue; |
| /* |
| * If @name is AT_UNNAMED we want an unnamed attribute. |
| * If @name is present, compare the two names. |
| * Otherwise, match any attribute. |
| */ |
| if (name == AT_UNNAMED) { |
| /* The search failed if the found attribute is named. */ |
| if (a->name_length) { |
| errno = ENOENT; |
| return -1; |
| } |
| } else if (name && !ntfs_names_are_equal(name, name_len, |
| (ntfschar*)((char*)a + le16_to_cpu(a->name_offset)), |
| a->name_length, ic, upcase, upcase_len)) { |
| int rc; |
| |
| rc = ntfs_names_full_collate(name, name_len, |
| (ntfschar*)((char*)a + |
| le16_to_cpu(a->name_offset)), |
| a->name_length, IGNORE_CASE, |
| upcase, upcase_len); |
| /* |
| * If @name collates before a->name, there is no |
| * matching attribute. |
| */ |
| if (rc == -1) { |
| errno = ENOENT; |
| return -1; |
| } |
| /* If the strings are not equal, continue search. */ |
| if (rc) |
| continue; |
| rc = ntfs_names_full_collate(name, name_len, |
| (ntfschar*)((char*)a + |
| le16_to_cpu(a->name_offset)), |
| a->name_length, CASE_SENSITIVE, |
| upcase, upcase_len); |
| if (rc == -1) { |
| errno = ENOENT; |
| return -1; |
| } |
| if (rc) |
| continue; |
| } |
| /* |
| * The names match or @name not present and attribute is |
| * unnamed. If no @val specified, we have found the attribute |
| * and are done. |
| */ |
| if (!val) { |
| return 0; |
| /* @val is present; compare values. */ |
| } else { |
| int rc; |
| |
| rc = memcmp(val, (char*)a +le16_to_cpu(a->value_offset), |
| min(val_len, |
| le32_to_cpu(a->value_length))); |
| /* |
| * If @val collates before the current attribute's |
| * value, there is no matching attribute. |
| */ |
| if (!rc) { |
| u32 avl; |
| avl = le32_to_cpu(a->value_length); |
| if (val_len == avl) |
| return 0; |
| if (val_len < avl) { |
| errno = ENOENT; |
| return -1; |
| } |
| } else if (rc < 0) { |
| errno = ENOENT; |
| return -1; |
| } |
| } |
| } |
| ntfs_log_trace("File is corrupt. Run chkdsk.\n"); |
| errno = EIO; |
| return -1; |
| } |
| |
| /** |
| * ntfs_attr_lookup - find an attribute in an ntfs inode |
| * @type: attribute type to find |
| * @name: attribute name to find (optional, i.e. NULL means don't care) |
| * @name_len: attribute name length (only needed if @name present) |
| * @ic: IGNORE_CASE or CASE_SENSITIVE (ignored if @name not present) |
| * @lowest_vcn: lowest vcn to find (optional, non-resident attributes only) |
| * @val: attribute value to find (optional, resident attributes only) |
| * @val_len: attribute value length |
| * @ctx: search context with mft record and attribute to search from |
| * |
| * Find an attribute in an ntfs inode. On first search @ctx->ntfs_ino must |
| * be the base mft record and @ctx must have been obtained from a call to |
| * ntfs_attr_get_search_ctx(). |
| * |
| * This function transparently handles attribute lists and @ctx is used to |
| * continue searches where they were left off at. |
| * |
| * If @type is AT_UNUSED, return the first found attribute, i.e. one can |
| * enumerate all attributes by setting @type to AT_UNUSED and then calling |
| * ntfs_attr_lookup() repeatedly until it returns -1 with errno set to ENOENT |
| * to indicate that there are no more entries. During the enumeration, each |
| * successful call of ntfs_attr_lookup() will return the next attribute, with |
| * the current attribute being described by the search context @ctx. |
| * |
| * If @type is AT_END, seek to the end of the base mft record ignoring the |
| * attribute list completely and return -1 with errno set to ENOENT. AT_END is |
| * not a valid attribute, its length is zero for example, thus it is safer to |
| * return error instead of success in this case. It should never be needed to |
| * do this, but we implement the functionality because it allows for simpler |
| * code inside ntfs_external_attr_find(). |
| * |
| * If @name is AT_UNNAMED search for an unnamed attribute. If @name is present |
| * but not AT_UNNAMED search for a named attribute matching @name. Otherwise, |
| * match both named and unnamed attributes. |
| * |
| * After finishing with the attribute/mft record you need to call |
| * ntfs_attr_put_search_ctx() to cleanup the search context (unmapping any |
| * mapped extent inodes, etc). |
| * |
| * Return 0 if the search was successful and -1 if not, with errno set to the |
| * error code. |
| * |
| * On success, @ctx->attr is the found attribute, it is in mft record |
| * @ctx->mrec, and @ctx->al_entry is the attribute list entry for this |
| * attribute with @ctx->base_* being the base mft record to which @ctx->attr |
| * belongs. If no attribute list attribute is present @ctx->al_entry and |
| * @ctx->base_* are NULL. |
| * |
| * On error ENOENT, i.e. attribute not found, @ctx->attr is set to the |
| * attribute which collates just after the attribute being searched for in the |
| * base ntfs inode, i.e. if one wants to add the attribute to the mft record |
| * this is the correct place to insert it into, and if there is not enough |
| * space, the attribute should be placed in an extent mft record. |
| * @ctx->al_entry points to the position within @ctx->base_ntfs_ino->attr_list |
| * at which the new attribute's attribute list entry should be inserted. The |
| * other @ctx fields, base_ntfs_ino, base_mrec, and base_attr are set to NULL. |
| * The only exception to this is when @type is AT_END, in which case |
| * @ctx->al_entry is set to NULL also (see above). |
| * |
| * The following error codes are defined: |
| * ENOENT Attribute not found, not an error as such. |
| * EINVAL Invalid arguments. |
| * EIO I/O error or corrupt data structures found. |
| * ENOMEM Not enough memory to allocate necessary buffers. |
| */ |
| static int mkntfs_attr_lookup(const ATTR_TYPES type, const ntfschar *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const VCN lowest_vcn __attribute__((unused)), const u8 *val, |
| const u32 val_len, ntfs_attr_search_ctx *ctx) |
| { |
| ntfs_inode *base_ni; |
| |
| if (!ctx || !ctx->mrec || !ctx->attr) { |
| errno = EINVAL; |
| return -1; |
| } |
| if (ctx->base_ntfs_ino) |
| base_ni = ctx->base_ntfs_ino; |
| else |
| base_ni = ctx->ntfs_ino; |
| if (!base_ni || !NInoAttrList(base_ni) || type == AT_ATTRIBUTE_LIST) |
| return mkntfs_attr_find(type, name, name_len, ic, val, val_len, |
| ctx); |
| errno = EOPNOTSUPP; |
| return -1; |
| } |
| |
| /** |
| * insert_positioned_attr_in_mft_record |
| * |
| * Create a non-resident attribute with a predefined on disk location |
| * specified by the runlist @rl. The clusters specified by @rl are assumed to |
| * be allocated already. |
| * |
| * Return 0 on success and -errno on error. |
| */ |
| static int insert_positioned_attr_in_mft_record(MFT_RECORD *m, |
| const ATTR_TYPES type, const char *name, u32 name_len, |
| const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, |
| const runlist *rl, const u8 *val, const s64 val_len) |
| { |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| u16 hdr_size; |
| int asize, mpa_size, err, i; |
| s64 bw = 0, inited_size; |
| VCN highest_vcn; |
| ntfschar *uname = NULL; |
| int uname_len = 0; |
| /* |
| if (base record) |
| attr_lookup(); |
| else |
| */ |
| |
| uname = ntfs_str2ucs(name, &uname_len); |
| if (!uname) |
| return -errno; |
| |
| /* Check if the attribute is already there. */ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to allocate attribute search context.\n"); |
| err = -ENOMEM; |
| goto err_out; |
| } |
| if (ic == IGNORE_CASE) { |
| ntfs_log_error("FIXME: Hit unimplemented code path #1.\n"); |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (!mkntfs_attr_lookup(type, uname, uname_len, ic, 0, NULL, 0, ctx)) { |
| err = -EEXIST; |
| goto err_out; |
| } |
| if (errno != ENOENT) { |
| ntfs_log_error("Corrupt inode.\n"); |
| err = -errno; |
| goto err_out; |
| } |
| a = ctx->attr; |
| if (flags & ATTR_COMPRESSION_MASK) { |
| ntfs_log_error("Compressed attributes not supported yet.\n"); |
| /* FIXME: Compress attribute into a temporary buffer, set */ |
| /* val accordingly and save the compressed size. */ |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (flags & (ATTR_IS_ENCRYPTED | ATTR_IS_SPARSE)) { |
| ntfs_log_error("Encrypted/sparse attributes not supported.\n"); |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (flags & ATTR_COMPRESSION_MASK) { |
| hdr_size = 72; |
| /* FIXME: This compression stuff is all wrong. Never mind for */ |
| /* now. (AIA) */ |
| if (val_len) |
| mpa_size = 0; /* get_size_for_compressed_mapping_pairs(rl); */ |
| else |
| mpa_size = 0; |
| } else { |
| hdr_size = 64; |
| if (val_len) { |
| mpa_size = ntfs_get_size_for_mapping_pairs(g_vol, rl, 0, INT_MAX); |
| if (mpa_size < 0) { |
| err = -errno; |
| ntfs_log_error("Failed to get size for mapping " |
| "pairs.\n"); |
| goto err_out; |
| } |
| } else { |
| mpa_size = 0; |
| } |
| } |
| /* Mapping pairs array and next attribute must be 8-byte aligned. */ |
| asize = (((int)hdr_size + ((name_len + 7) & ~7) + mpa_size) + 7) & ~7; |
| /* Get the highest vcn. */ |
| for (i = 0, highest_vcn = 0LL; rl[i].length; i++) |
| highest_vcn += rl[i].length; |
| /* Does the value fit inside the allocated size? */ |
| if (highest_vcn * g_vol->cluster_size < val_len) { |
| ntfs_log_error("BUG: Allocated size is smaller than data size!\n"); |
| err = -EINVAL; |
| goto err_out; |
| } |
| err = make_room_for_attribute(m, (char*)a, asize); |
| if (err == -ENOSPC) { |
| /* |
| * FIXME: Make space! (AIA) |
| * can we make it non-resident? if yes, do that. |
| * does it fit now? yes -> do it. |
| * m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? |
| * yes -> make non-resident |
| * does it fit now? yes -> do it. |
| * make all attributes non-resident |
| * does it fit now? yes -> do it. |
| * m is a base record? yes -> allocate extension record |
| * does the new attribute fit in there? yes -> do it. |
| * split up runlist into extents and place each in an extension |
| * record. |
| * FIXME: the check for needing extension records should be |
| * earlier on as it is very quick: asize > m->bytes_allocated? |
| */ |
| err = -EOPNOTSUPP; |
| goto err_out; |
| #ifdef DEBUG |
| } else if (err == -EINVAL) { |
| ntfs_log_error("BUG(): in insert_positioned_attribute_in_mft_" |
| "record(): make_room_for_attribute() returned " |
| "error: EINVAL!\n"); |
| goto err_out; |
| #endif |
| } |
| a->type = type; |
| a->length = cpu_to_le32(asize); |
| a->non_resident = 1; |
| a->name_length = name_len; |
| a->name_offset = cpu_to_le16(hdr_size); |
| a->flags = flags; |
| a->instance = m->next_attr_instance; |
| m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) |
| + 1) & 0xffff); |
| a->lowest_vcn = cpu_to_le64(0); |
| a->highest_vcn = cpu_to_sle64(highest_vcn - 1LL); |
| a->mapping_pairs_offset = cpu_to_le16(hdr_size + ((name_len + 7) & ~7)); |
| memset(a->reserved1, 0, sizeof(a->reserved1)); |
| /* FIXME: Allocated size depends on compression. */ |
| a->allocated_size = cpu_to_sle64(highest_vcn * g_vol->cluster_size); |
| a->data_size = cpu_to_sle64(val_len); |
| if (name_len) |
| memcpy((char*)a + hdr_size, uname, name_len << 1); |
| if (flags & ATTR_COMPRESSION_MASK) { |
| if (flags & ATTR_COMPRESSION_MASK & ~ATTR_IS_COMPRESSED) { |
| ntfs_log_error("Unknown compression format. Reverting " |
| "to standard compression.\n"); |
| a->flags &= ~ATTR_COMPRESSION_MASK; |
| a->flags |= ATTR_IS_COMPRESSED; |
| } |
| a->compression_unit = 4; |
| inited_size = val_len; |
| /* FIXME: Set the compressed size. */ |
| a->compressed_size = cpu_to_le64(0); |
| /* FIXME: Write out the compressed data. */ |
| /* FIXME: err = build_mapping_pairs_compressed(); */ |
| err = -EOPNOTSUPP; |
| } else { |
| a->compression_unit = 0; |
| if ((type == AT_DATA) |
| && (m->mft_record_number |
| == const_cpu_to_le32(FILE_LogFile))) |
| bw = ntfs_rlwrite(g_vol->dev, rl, val, val_len, |
| &inited_size, WRITE_LOGFILE); |
| else |
| bw = ntfs_rlwrite(g_vol->dev, rl, val, val_len, |
| &inited_size, WRITE_STANDARD); |
| if (bw != val_len) { |
| ntfs_log_error("Error writing non-resident attribute " |
| "value.\n"); |
| return -errno; |
| } |
| err = ntfs_mapping_pairs_build(g_vol, (u8*)a + hdr_size + |
| ((name_len + 7) & ~7), mpa_size, rl, 0, NULL); |
| } |
| a->initialized_size = cpu_to_sle64(inited_size); |
| if (err < 0 || bw != val_len) { |
| /* FIXME: Handle error. */ |
| /* deallocate clusters */ |
| /* remove attribute */ |
| if (err >= 0) |
| err = -EIO; |
| ntfs_log_error("insert_positioned_attr_in_mft_record failed " |
| "with error %i.\n", err < 0 ? err : (int)bw); |
| } |
| err_out: |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_ucsfree(uname); |
| return err; |
| } |
| |
| /** |
| * insert_non_resident_attr_in_mft_record |
| * |
| * Return 0 on success and -errno on error. |
| */ |
| static int insert_non_resident_attr_in_mft_record(MFT_RECORD *m, |
| const ATTR_TYPES type, const char *name, u32 name_len, |
| const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, |
| const u8 *val, const s64 val_len, |
| WRITE_TYPE write_type) |
| { |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| u16 hdr_size; |
| int asize, mpa_size, err, i; |
| runlist *rl = NULL; |
| s64 bw = 0; |
| ntfschar *uname = NULL; |
| int uname_len = 0; |
| /* |
| if (base record) |
| attr_lookup(); |
| else |
| */ |
| |
| uname = ntfs_str2ucs(name, &uname_len); |
| if (!uname) |
| return -errno; |
| |
| /* Check if the attribute is already there. */ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to allocate attribute search context.\n"); |
| err = -ENOMEM; |
| goto err_out; |
| } |
| if (ic == IGNORE_CASE) { |
| ntfs_log_error("FIXME: Hit unimplemented code path #2.\n"); |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (!mkntfs_attr_lookup(type, uname, uname_len, ic, 0, NULL, 0, ctx)) { |
| err = -EEXIST; |
| goto err_out; |
| } |
| if (errno != ENOENT) { |
| ntfs_log_error("Corrupt inode.\n"); |
| err = -errno; |
| goto err_out; |
| } |
| a = ctx->attr; |
| if (flags & ATTR_COMPRESSION_MASK) { |
| ntfs_log_error("Compressed attributes not supported yet.\n"); |
| /* FIXME: Compress attribute into a temporary buffer, set */ |
| /* val accordingly and save the compressed size. */ |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (flags & (ATTR_IS_ENCRYPTED | ATTR_IS_SPARSE)) { |
| ntfs_log_error("Encrypted/sparse attributes not supported.\n"); |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (val_len) { |
| rl = allocate_scattered_clusters((val_len + |
| g_vol->cluster_size - 1) / g_vol->cluster_size); |
| if (!rl) { |
| err = -errno; |
| ntfs_log_perror("Failed to allocate scattered clusters"); |
| goto err_out; |
| } |
| } else { |
| rl = NULL; |
| } |
| if (flags & ATTR_COMPRESSION_MASK) { |
| hdr_size = 72; |
| /* FIXME: This compression stuff is all wrong. Never mind for */ |
| /* now. (AIA) */ |
| if (val_len) |
| mpa_size = 0; /* get_size_for_compressed_mapping_pairs(rl); */ |
| else |
| mpa_size = 0; |
| } else { |
| hdr_size = 64; |
| if (val_len) { |
| mpa_size = ntfs_get_size_for_mapping_pairs(g_vol, rl, 0, INT_MAX); |
| if (mpa_size < 0) { |
| err = -errno; |
| ntfs_log_error("Failed to get size for mapping " |
| "pairs.\n"); |
| goto err_out; |
| } |
| } else { |
| mpa_size = 0; |
| } |
| } |
| /* Mapping pairs array and next attribute must be 8-byte aligned. */ |
| asize = (((int)hdr_size + ((name_len + 7) & ~7) + mpa_size) + 7) & ~7; |
| err = make_room_for_attribute(m, (char*)a, asize); |
| if (err == -ENOSPC) { |
| /* |
| * FIXME: Make space! (AIA) |
| * can we make it non-resident? if yes, do that. |
| * does it fit now? yes -> do it. |
| * m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? |
| * yes -> make non-resident |
| * does it fit now? yes -> do it. |
| * make all attributes non-resident |
| * does it fit now? yes -> do it. |
| * m is a base record? yes -> allocate extension record |
| * does the new attribute fit in there? yes -> do it. |
| * split up runlist into extents and place each in an extension |
| * record. |
| * FIXME: the check for needing extension records should be |
| * earlier on as it is very quick: asize > m->bytes_allocated? |
| */ |
| err = -EOPNOTSUPP; |
| goto err_out; |
| #ifdef DEBUG |
| } else if (err == -EINVAL) { |
| ntfs_log_error("BUG(): in insert_non_resident_attribute_in_" |
| "mft_record(): make_room_for_attribute() " |
| "returned error: EINVAL!\n"); |
| goto err_out; |
| #endif |
| } |
| a->type = type; |
| a->length = cpu_to_le32(asize); |
| a->non_resident = 1; |
| a->name_length = name_len; |
| a->name_offset = cpu_to_le16(hdr_size); |
| a->flags = flags; |
| a->instance = m->next_attr_instance; |
| m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) |
| + 1) & 0xffff); |
| a->lowest_vcn = cpu_to_le64(0); |
| for (i = 0; rl[i].length; i++) |
| ; |
| a->highest_vcn = cpu_to_sle64(rl[i].vcn - 1); |
| a->mapping_pairs_offset = cpu_to_le16(hdr_size + ((name_len + 7) & ~7)); |
| memset(a->reserved1, 0, sizeof(a->reserved1)); |
| /* FIXME: Allocated size depends on compression. */ |
| a->allocated_size = cpu_to_sle64((val_len + (g_vol->cluster_size - 1)) & |
| ~(g_vol->cluster_size - 1)); |
| a->data_size = cpu_to_sle64(val_len); |
| a->initialized_size = cpu_to_sle64(val_len); |
| if (name_len) |
| memcpy((char*)a + hdr_size, uname, name_len << 1); |
| if (flags & ATTR_COMPRESSION_MASK) { |
| if (flags & ATTR_COMPRESSION_MASK & ~ATTR_IS_COMPRESSED) { |
| ntfs_log_error("Unknown compression format. Reverting " |
| "to standard compression.\n"); |
| a->flags &= ~ATTR_COMPRESSION_MASK; |
| a->flags |= ATTR_IS_COMPRESSED; |
| } |
| a->compression_unit = 4; |
| /* FIXME: Set the compressed size. */ |
| a->compressed_size = cpu_to_le64(0); |
| /* FIXME: Write out the compressed data. */ |
| /* FIXME: err = build_mapping_pairs_compressed(); */ |
| err = -EOPNOTSUPP; |
| } else { |
| a->compression_unit = 0; |
| bw = ntfs_rlwrite(g_vol->dev, rl, val, val_len, NULL, |
| write_type); |
| if (bw != val_len) { |
| ntfs_log_error("Error writing non-resident attribute " |
| "value.\n"); |
| return -errno; |
| } |
| err = ntfs_mapping_pairs_build(g_vol, (u8*)a + hdr_size + |
| ((name_len + 7) & ~7), mpa_size, rl, 0, NULL); |
| } |
| if (err < 0 || bw != val_len) { |
| /* FIXME: Handle error. */ |
| /* deallocate clusters */ |
| /* remove attribute */ |
| if (err >= 0) |
| err = -EIO; |
| ntfs_log_error("insert_non_resident_attr_in_mft_record failed with " |
| "error %lld.\n", (long long) (err < 0 ? err : bw)); |
| } |
| err_out: |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_ucsfree(uname); |
| free(rl); |
| return err; |
| } |
| |
| /** |
| * insert_resident_attr_in_mft_record |
| * |
| * Return 0 on success and -errno on error. |
| */ |
| static int insert_resident_attr_in_mft_record(MFT_RECORD *m, |
| const ATTR_TYPES type, const char *name, u32 name_len, |
| const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, |
| const RESIDENT_ATTR_FLAGS res_flags, |
| const u8 *val, const u32 val_len) |
| { |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| int asize, err; |
| ntfschar *uname = NULL; |
| int uname_len = 0; |
| /* |
| if (base record) |
| mkntfs_attr_lookup(); |
| else |
| */ |
| |
| uname = ntfs_str2ucs(name, &uname_len); |
| if (!uname) |
| return -errno; |
| |
| /* Check if the attribute is already there. */ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to allocate attribute search context.\n"); |
| err = -ENOMEM; |
| goto err_out; |
| } |
| if (ic == IGNORE_CASE) { |
| ntfs_log_error("FIXME: Hit unimplemented code path #3.\n"); |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| if (!mkntfs_attr_lookup(type, uname, uname_len, ic, 0, val, val_len, |
| ctx)) { |
| err = -EEXIST; |
| goto err_out; |
| } |
| if (errno != ENOENT) { |
| ntfs_log_error("Corrupt inode.\n"); |
| err = -errno; |
| goto err_out; |
| } |
| a = ctx->attr; |
| /* sizeof(resident attribute record header) == 24 */ |
| asize = ((24 + ((name_len*2 + 7) & ~7) + val_len) + 7) & ~7; |
| err = make_room_for_attribute(m, (char*)a, asize); |
| if (err == -ENOSPC) { |
| /* |
| * FIXME: Make space! (AIA) |
| * can we make it non-resident? if yes, do that. |
| * does it fit now? yes -> do it. |
| * m's $DATA or $BITMAP+$INDEX_ALLOCATION resident? |
| * yes -> make non-resident |
| * does it fit now? yes -> do it. |
| * make all attributes non-resident |
| * does it fit now? yes -> do it. |
| * m is a base record? yes -> allocate extension record |
| * does the new attribute fit in there? yes -> do it. |
| * split up runlist into extents and place each in an extension |
| * record. |
| * FIXME: the check for needing extension records should be |
| * earlier on as it is very quick: asize > m->bytes_allocated? |
| */ |
| err = -EOPNOTSUPP; |
| goto err_out; |
| } |
| #ifdef DEBUG |
| if (err == -EINVAL) { |
| ntfs_log_error("BUG(): in insert_resident_attribute_in_mft_" |
| "record(): make_room_for_attribute() returned " |
| "error: EINVAL!\n"); |
| goto err_out; |
| } |
| #endif |
| a->type = type; |
| a->length = cpu_to_le32(asize); |
| a->non_resident = 0; |
| a->name_length = name_len; |
| if (type == AT_OBJECT_ID) |
| a->name_offset = const_cpu_to_le16(0); |
| else |
| a->name_offset = const_cpu_to_le16(24); |
| a->flags = flags; |
| a->instance = m->next_attr_instance; |
| m->next_attr_instance = cpu_to_le16((le16_to_cpu(m->next_attr_instance) |
| + 1) & 0xffff); |
| a->value_length = cpu_to_le32(val_len); |
| a->value_offset = cpu_to_le16(24 + ((name_len*2 + 7) & ~7)); |
| a->resident_flags = res_flags; |
| a->reservedR = 0; |
| if (name_len) |
| memcpy((char*)a + 24, uname, name_len << 1); |
| if (val_len) |
| memcpy((char*)a + le16_to_cpu(a->value_offset), val, val_len); |
| err_out: |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_ucsfree(uname); |
| return err; |
| } |
| |
| |
| /** |
| * add_attr_std_info |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_std_info(MFT_RECORD *m, const FILE_ATTR_FLAGS flags, |
| le32 security_id) |
| { |
| STANDARD_INFORMATION si; |
| int err, sd_size; |
| |
| sd_size = 48; |
| |
| si.creation_time = mkntfs_time(); |
| si.last_data_change_time = si.creation_time; |
| si.last_mft_change_time = si.creation_time; |
| si.last_access_time = si.creation_time; |
| si.file_attributes = flags; /* already LE */ |
| si.maximum_versions = cpu_to_le32(0); |
| si.version_number = cpu_to_le32(0); |
| si.class_id = cpu_to_le32(0); |
| si.security_id = security_id; |
| if (si.security_id != const_cpu_to_le32(0)) |
| sd_size = 72; |
| /* FIXME: $Quota support... */ |
| si.owner_id = cpu_to_le32(0); |
| si.quota_charged = cpu_to_le64(0ULL); |
| /* FIXME: $UsnJrnl support... Not needed on fresh w2k3-volume */ |
| si.usn = cpu_to_le64(0ULL); |
| /* NTFS 1.2: size of si = 48, NTFS 3.[01]: size of si = 72 */ |
| err = insert_resident_attr_in_mft_record(m, AT_STANDARD_INFORMATION, |
| NULL, 0, CASE_SENSITIVE, const_cpu_to_le16(0), |
| 0, (u8*)&si, sd_size); |
| if (err < 0) |
| ntfs_log_perror("add_attr_std_info failed"); |
| return err; |
| } |
| |
| /* |
| * Tell whether the unnamed data is non resident |
| */ |
| |
| static BOOL non_resident_unnamed_data(MFT_RECORD *m) |
| { |
| ATTR_RECORD *a; |
| ntfs_attr_search_ctx *ctx; |
| BOOL nonres; |
| |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (ctx && !mkntfs_attr_find(AT_DATA, |
| (const ntfschar*)NULL, 0, CASE_SENSITIVE, |
| (u8*)NULL, 0, ctx)) { |
| a = ctx->attr; |
| nonres = a->non_resident != 0; |
| } else { |
| ntfs_log_error("BUG: Unnamed data not found\n"); |
| nonres = TRUE; |
| } |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| return (nonres); |
| } |
| |
| /* |
| * Get the time stored in the standard information attribute |
| */ |
| |
| static ntfs_time stdinfo_time(MFT_RECORD *m) |
| { |
| STANDARD_INFORMATION *si; |
| ntfs_attr_search_ctx *ctx; |
| ntfs_time info_time; |
| |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (ctx && !mkntfs_attr_find(AT_STANDARD_INFORMATION, |
| (const ntfschar*)NULL, 0, CASE_SENSITIVE, |
| (u8*)NULL, 0, ctx)) { |
| si = (STANDARD_INFORMATION*)((char*)ctx->attr + |
| le16_to_cpu(ctx->attr->value_offset)); |
| info_time = si->creation_time; |
| } else { |
| ntfs_log_error("BUG: Standard information not found\n"); |
| info_time = mkntfs_time(); |
| } |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| return (info_time); |
| } |
| |
| /** |
| * add_attr_file_name |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_file_name(MFT_RECORD *m, const leMFT_REF parent_dir, |
| const s64 allocated_size, const s64 data_size, |
| const FILE_ATTR_FLAGS flags, const u16 packed_ea_size, |
| const u32 reparse_point_tag, const char *file_name, |
| const FILE_NAME_TYPE_FLAGS file_name_type) |
| { |
| ntfs_attr_search_ctx *ctx; |
| STANDARD_INFORMATION *si; |
| FILE_NAME_ATTR *fn; |
| int i, fn_size; |
| ntfschar *uname; |
| |
| /* Check if the attribute is already there. */ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to get attribute search context.\n"); |
| return -ENOMEM; |
| } |
| if (mkntfs_attr_lookup(AT_STANDARD_INFORMATION, AT_UNNAMED, 0, |
| CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| int eo = errno; |
| ntfs_log_error("BUG: Standard information attribute not " |
| "present in file record.\n"); |
| ntfs_attr_put_search_ctx(ctx); |
| return -eo; |
| } |
| si = (STANDARD_INFORMATION*)((char*)ctx->attr + |
| le16_to_cpu(ctx->attr->value_offset)); |
| i = (strlen(file_name) + 1) * sizeof(ntfschar); |
| fn_size = sizeof(FILE_NAME_ATTR) + i; |
| fn = ntfs_malloc(fn_size); |
| if (!fn) { |
| ntfs_attr_put_search_ctx(ctx); |
| return -errno; |
| } |
| fn->parent_directory = parent_dir; |
| |
| fn->creation_time = si->creation_time; |
| fn->last_data_change_time = si->last_data_change_time; |
| fn->last_mft_change_time = si->last_mft_change_time; |
| fn->last_access_time = si->last_access_time; |
| ntfs_attr_put_search_ctx(ctx); |
| |
| fn->allocated_size = cpu_to_sle64(allocated_size); |
| fn->data_size = cpu_to_sle64(data_size); |
| fn->file_attributes = flags; |
| /* These are in a union so can't have both. */ |
| if (packed_ea_size && reparse_point_tag) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (packed_ea_size) { |
| fn->packed_ea_size = cpu_to_le16(packed_ea_size); |
| fn->reserved = cpu_to_le16(0); |
| } else { |
| fn->reparse_point_tag = cpu_to_le32(reparse_point_tag); |
| } |
| fn->file_name_type = file_name_type; |
| uname = fn->file_name; |
| i = ntfs_mbstoucs_libntfscompat(file_name, &uname, i); |
| if (i < 1) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (i > 0xff) { |
| free(fn); |
| return -ENAMETOOLONG; |
| } |
| /* No terminating null in file names. */ |
| fn->file_name_length = i; |
| fn_size = sizeof(FILE_NAME_ATTR) + i * sizeof(ntfschar); |
| i = insert_resident_attr_in_mft_record(m, AT_FILE_NAME, NULL, 0, |
| CASE_SENSITIVE, const_cpu_to_le16(0), |
| RESIDENT_ATTR_IS_INDEXED, (u8*)fn, fn_size); |
| free(fn); |
| if (i < 0) |
| ntfs_log_error("add_attr_file_name failed: %s\n", strerror(-i)); |
| return i; |
| } |
| |
| /** |
| * add_attr_object_id - |
| * |
| * Note we insert only a basic object id which only has the GUID and none of |
| * the extended fields. This is because we currently only use this function |
| * when creating the object id for the volume. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_object_id(MFT_RECORD *m, const GUID *object_id) |
| { |
| OBJECT_ID_ATTR oi; |
| int err; |
| |
| oi = (OBJECT_ID_ATTR) { |
| .object_id = *object_id, |
| }; |
| err = insert_resident_attr_in_mft_record(m, AT_OBJECT_ID, NULL, |
| 0, CASE_SENSITIVE, const_cpu_to_le16(0), |
| 0, (u8*)&oi, sizeof(oi.object_id)); |
| if (err < 0) |
| ntfs_log_error("add_attr_vol_info failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_sd |
| * |
| * Create the security descriptor attribute adding the security descriptor @sd |
| * of length @sd_len to the mft record @m. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_sd(MFT_RECORD *m, const u8 *sd, const s64 sd_len) |
| { |
| int err; |
| |
| /* Does it fit? NO: create non-resident. YES: create resident. */ |
| if (le32_to_cpu(m->bytes_in_use) + 24 + sd_len > |
| le32_to_cpu(m->bytes_allocated)) |
| err = insert_non_resident_attr_in_mft_record(m, |
| AT_SECURITY_DESCRIPTOR, NULL, 0, |
| CASE_SENSITIVE, const_cpu_to_le16(0), sd, |
| sd_len, WRITE_STANDARD); |
| else |
| err = insert_resident_attr_in_mft_record(m, |
| AT_SECURITY_DESCRIPTOR, NULL, 0, |
| CASE_SENSITIVE, const_cpu_to_le16(0), 0, sd, |
| sd_len); |
| if (err < 0) |
| ntfs_log_error("add_attr_sd failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_data |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_data(MFT_RECORD *m, const char *name, const u32 name_len, |
| const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, |
| const u8 *val, const s64 val_len) |
| { |
| int err; |
| |
| /* |
| * Does it fit? NO: create non-resident. YES: create resident. |
| * |
| * FIXME: Introduced arbitrary limit of mft record allocated size - 512. |
| * This is to get around the problem that if $Bitmap/$DATA becomes too |
| * big, but is just small enough to be resident, we would make it |
| * resident, and later run out of space when creating the other |
| * attributes and this would cause us to abort as making resident |
| * attributes non-resident is not supported yet. |
| * The proper fix is to support making resident attribute non-resident. |
| */ |
| if (le32_to_cpu(m->bytes_in_use) + 24 + val_len > |
| min(le32_to_cpu(m->bytes_allocated), |
| le32_to_cpu(m->bytes_allocated) - 512)) |
| err = insert_non_resident_attr_in_mft_record(m, AT_DATA, name, |
| name_len, ic, flags, val, val_len, |
| WRITE_STANDARD); |
| else |
| err = insert_resident_attr_in_mft_record(m, AT_DATA, name, |
| name_len, ic, flags, 0, val, val_len); |
| |
| if (err < 0) |
| ntfs_log_error("add_attr_data failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_data_positioned |
| * |
| * Create a non-resident data attribute with a predefined on disk location |
| * specified by the runlist @rl. The clusters specified by @rl are assumed to |
| * be allocated already. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_data_positioned(MFT_RECORD *m, const char *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const ATTR_FLAGS flags, const runlist *rl, |
| const u8 *val, const s64 val_len) |
| { |
| int err; |
| |
| err = insert_positioned_attr_in_mft_record(m, AT_DATA, name, name_len, |
| ic, flags, rl, val, val_len); |
| if (err < 0) |
| ntfs_log_error("add_attr_data_positioned failed: %s\n", |
| strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_vol_name |
| * |
| * Create volume name attribute specifying the volume name @vol_name as a null |
| * terminated char string of length @vol_name_len (number of characters not |
| * including the terminating null), which is converted internally to a little |
| * endian ntfschar string. The name is at least 1 character long (though |
| * Windows accepts zero characters), and at most 128 characters long (not |
| * counting the terminating null). |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_vol_name(MFT_RECORD *m, const char *vol_name, |
| const int vol_name_len __attribute__((unused))) |
| { |
| ntfschar *uname = NULL; |
| int uname_len = 0; |
| int i; |
| |
| if (vol_name) { |
| uname_len = ntfs_mbstoucs(vol_name, &uname); |
| if (uname_len < 0) |
| return -errno; |
| if (uname_len > 128) { |
| free(uname); |
| return -ENAMETOOLONG; |
| } |
| } |
| i = insert_resident_attr_in_mft_record(m, AT_VOLUME_NAME, NULL, 0, |
| CASE_SENSITIVE, const_cpu_to_le16(0), |
| 0, (u8*)uname, uname_len*sizeof(ntfschar)); |
| free(uname); |
| if (i < 0) |
| ntfs_log_error("add_attr_vol_name failed: %s\n", strerror(-i)); |
| return i; |
| } |
| |
| /** |
| * add_attr_vol_info |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_vol_info(MFT_RECORD *m, const VOLUME_FLAGS flags, |
| const u8 major_ver, const u8 minor_ver) |
| { |
| VOLUME_INFORMATION vi; |
| int err; |
| |
| memset(&vi, 0, sizeof(vi)); |
| vi.major_ver = major_ver; |
| vi.minor_ver = minor_ver; |
| vi.flags = flags & VOLUME_FLAGS_MASK; |
| err = insert_resident_attr_in_mft_record(m, AT_VOLUME_INFORMATION, NULL, |
| 0, CASE_SENSITIVE, const_cpu_to_le16(0), |
| 0, (u8*)&vi, sizeof(vi)); |
| if (err < 0) |
| ntfs_log_error("add_attr_vol_info failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_index_root |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_index_root(MFT_RECORD *m, const char *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const ATTR_TYPES indexed_attr_type, |
| const COLLATION_RULES collation_rule, |
| const u32 index_block_size) |
| { |
| INDEX_ROOT *r; |
| INDEX_ENTRY_HEADER *e; |
| int err, val_len; |
| |
| val_len = sizeof(INDEX_ROOT) + sizeof(INDEX_ENTRY_HEADER); |
| r = ntfs_malloc(val_len); |
| if (!r) |
| return -errno; |
| r->type = (indexed_attr_type == AT_FILE_NAME) |
| ? AT_FILE_NAME : const_cpu_to_le32(0); |
| if (indexed_attr_type == AT_FILE_NAME && |
| collation_rule != COLLATION_FILE_NAME) { |
| free(r); |
| ntfs_log_error("add_attr_index_root: indexed attribute is $FILE_NAME " |
| "but collation rule is not COLLATION_FILE_NAME.\n"); |
| return -EINVAL; |
| } |
| r->collation_rule = collation_rule; |
| r->index_block_size = cpu_to_le32(index_block_size); |
| if (index_block_size >= g_vol->cluster_size) { |
| if (index_block_size % g_vol->cluster_size) { |
| ntfs_log_error("add_attr_index_root: index block size is not " |
| "a multiple of the cluster size.\n"); |
| free(r); |
| return -EINVAL; |
| } |
| r->clusters_per_index_block = index_block_size / |
| g_vol->cluster_size; |
| } else { /* if (g_vol->cluster_size > index_block_size) */ |
| if (index_block_size & (index_block_size - 1)) { |
| ntfs_log_error("add_attr_index_root: index block size is not " |
| "a power of 2.\n"); |
| free(r); |
| return -EINVAL; |
| } |
| if (index_block_size < (u32)opts.sector_size) { |
| ntfs_log_error("add_attr_index_root: index block size " |
| "is smaller than the sector size.\n"); |
| free(r); |
| return -EINVAL; |
| } |
| r->clusters_per_index_block = index_block_size |
| >> NTFS_BLOCK_SIZE_BITS; |
| } |
| memset(&r->reserved, 0, sizeof(r->reserved)); |
| r->index.entries_offset = const_cpu_to_le32(sizeof(INDEX_HEADER)); |
| r->index.index_length = const_cpu_to_le32(sizeof(INDEX_HEADER) + |
| sizeof(INDEX_ENTRY_HEADER)); |
| r->index.allocated_size = r->index.index_length; |
| r->index.ih_flags = SMALL_INDEX; |
| memset(&r->index.reserved, 0, sizeof(r->index.reserved)); |
| e = (INDEX_ENTRY_HEADER*)((u8*)&r->index + |
| le32_to_cpu(r->index.entries_offset)); |
| /* |
| * No matter whether this is a file index or a view as this is a |
| * termination entry, hence no key value / data is associated with it |
| * at all. Thus, we just need the union to be all zero. |
| */ |
| e->indexed_file = const_cpu_to_le64(0LL); |
| e->length = const_cpu_to_le16(sizeof(INDEX_ENTRY_HEADER)); |
| e->key_length = const_cpu_to_le16(0); |
| e->flags = INDEX_ENTRY_END; |
| e->reserved = const_cpu_to_le16(0); |
| err = insert_resident_attr_in_mft_record(m, AT_INDEX_ROOT, name, |
| name_len, ic, const_cpu_to_le16(0), 0, |
| (u8*)r, val_len); |
| free(r); |
| if (err < 0) |
| ntfs_log_error("add_attr_index_root failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_index_alloc |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_index_alloc(MFT_RECORD *m, const char *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const u8 *index_alloc_val, const u32 index_alloc_val_len) |
| { |
| int err; |
| |
| err = insert_non_resident_attr_in_mft_record(m, AT_INDEX_ALLOCATION, |
| name, name_len, ic, const_cpu_to_le16(0), |
| index_alloc_val, index_alloc_val_len, WRITE_STANDARD); |
| if (err < 0) |
| ntfs_log_error("add_attr_index_alloc failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_bitmap |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_bitmap(MFT_RECORD *m, const char *name, const u32 name_len, |
| const IGNORE_CASE_BOOL ic, const u8 *bitmap, |
| const u32 bitmap_len) |
| { |
| int err; |
| |
| /* Does it fit? NO: create non-resident. YES: create resident. */ |
| if (le32_to_cpu(m->bytes_in_use) + 24 + bitmap_len > |
| le32_to_cpu(m->bytes_allocated)) |
| err = insert_non_resident_attr_in_mft_record(m, AT_BITMAP, name, |
| name_len, ic, const_cpu_to_le16(0), bitmap, |
| bitmap_len, WRITE_STANDARD); |
| else |
| err = insert_resident_attr_in_mft_record(m, AT_BITMAP, name, |
| name_len, ic, const_cpu_to_le16(0), 0, |
| bitmap, bitmap_len); |
| |
| if (err < 0) |
| ntfs_log_error("add_attr_bitmap failed: %s\n", strerror(-err)); |
| return err; |
| } |
| |
| /** |
| * add_attr_bitmap_positioned |
| * |
| * Create a non-resident bitmap attribute with a predefined on disk location |
| * specified by the runlist @rl. The clusters specified by @rl are assumed to |
| * be allocated already. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int add_attr_bitmap_positioned(MFT_RECORD *m, const char *name, |
| const u32 name_len, const IGNORE_CASE_BOOL ic, |
| const runlist *rl, const u8 *bitmap, const u32 bitmap_len) |
| { |
| int err; |
| |
| err = insert_positioned_attr_in_mft_record(m, AT_BITMAP, name, name_len, |
| ic, const_cpu_to_le16(0), rl, bitmap, bitmap_len); |
| if (err < 0) |
| ntfs_log_error("add_attr_bitmap_positioned failed: %s\n", |
| strerror(-err)); |
| return err; |
| } |
| |
| |
| /** |
| * upgrade_to_large_index |
| * |
| * Create bitmap and index allocation attributes, modify index root |
| * attribute accordingly and move all of the index entries from the index root |
| * into the index allocation. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int upgrade_to_large_index(MFT_RECORD *m, const char *name, |
| u32 name_len, const IGNORE_CASE_BOOL ic, |
| INDEX_ALLOCATION **idx) |
| { |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| INDEX_ROOT *r; |
| INDEX_ENTRY *re; |
| INDEX_ALLOCATION *ia_val = NULL; |
| ntfschar *uname = NULL; |
| int uname_len = 0; |
| u8 bmp[8]; |
| char *re_start, *re_end; |
| int i, err, index_block_size; |
| |
| uname = ntfs_str2ucs(name, &uname_len); |
| if (!uname) |
| return -errno; |
| |
| /* Find the index root attribute. */ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to allocate attribute search context.\n"); |
| ntfs_ucsfree(uname); |
| return -ENOMEM; |
| } |
| if (ic == IGNORE_CASE) { |
| ntfs_log_error("FIXME: Hit unimplemented code path #4.\n"); |
| err = -EOPNOTSUPP; |
| ntfs_ucsfree(uname); |
| goto err_out; |
| } |
| err = mkntfs_attr_lookup(AT_INDEX_ROOT, uname, uname_len, ic, 0, NULL, 0, |
| ctx); |
| ntfs_ucsfree(uname); |
| if (err) { |
| err = -ENOTDIR; |
| goto err_out; |
| } |
| a = ctx->attr; |
| if (a->non_resident || a->flags) { |
| err = -EINVAL; |
| goto err_out; |
| } |
| r = (INDEX_ROOT*)((char*)a + le16_to_cpu(a->value_offset)); |
| re_end = (char*)r + le32_to_cpu(a->value_length); |
| re_start = (char*)&r->index + le32_to_cpu(r->index.entries_offset); |
| re = (INDEX_ENTRY*)re_start; |
| index_block_size = le32_to_cpu(r->index_block_size); |
| memset(bmp, 0, sizeof(bmp)); |
| ntfs_bit_set(bmp, 0ULL, 1); |
| /* Bitmap has to be at least 8 bytes in size. */ |
| err = add_attr_bitmap(m, name, name_len, ic, bmp, sizeof(bmp)); |
| if (err) |
| goto err_out; |
| ia_val = ntfs_calloc(index_block_size); |
| if (!ia_val) { |
| err = -errno; |
| goto err_out; |
| } |
| /* Setup header. */ |
| ia_val->magic = magic_INDX; |
| ia_val->usa_ofs = cpu_to_le16(sizeof(INDEX_ALLOCATION)); |
| if (index_block_size >= NTFS_BLOCK_SIZE) { |
| ia_val->usa_count = cpu_to_le16(index_block_size / |
| NTFS_BLOCK_SIZE + 1); |
| } else { |
| ia_val->usa_count = cpu_to_le16(1); |
| ntfs_log_error("Sector size is bigger than index block size. " |
| "Setting usa_count to 1. If Windows chkdsk " |
| "reports this as corruption, please email %s " |
| "stating that you saw this message and that " |
| "the filesystem created was corrupt. " |
| "Thank you.", NTFS_DEV_LIST); |
| } |
| /* Set USN to 1. */ |
| *(le16*)((char*)ia_val + le16_to_cpu(ia_val->usa_ofs)) = |
| cpu_to_le16(1); |
| ia_val->lsn = cpu_to_le64(0); |
| ia_val->index_block_vcn = cpu_to_le64(0); |
| ia_val->index.ih_flags = LEAF_NODE; |
| /* Align to 8-byte boundary. */ |
| ia_val->index.entries_offset = cpu_to_le32((sizeof(INDEX_HEADER) + |
| le16_to_cpu(ia_val->usa_count) * 2 + 7) & ~7); |
| ia_val->index.allocated_size = cpu_to_le32(index_block_size - |
| (sizeof(INDEX_ALLOCATION) - sizeof(INDEX_HEADER))); |
| /* Find the last entry in the index root and save it in re. */ |
| while ((char*)re < re_end && !(re->ie_flags & INDEX_ENTRY_END)) { |
| /* Next entry in index root. */ |
| re = (INDEX_ENTRY*)((char*)re + le16_to_cpu(re->length)); |
| } |
| /* Copy all the entries including the termination entry. */ |
| i = (char*)re - re_start + le16_to_cpu(re->length); |
| memcpy((char*)&ia_val->index + |
| le32_to_cpu(ia_val->index.entries_offset), re_start, i); |
| /* Finish setting up index allocation. */ |
| ia_val->index.index_length = cpu_to_le32(i + |
| le32_to_cpu(ia_val->index.entries_offset)); |
| /* Move the termination entry forward to the beginning if necessary. */ |
| if ((char*)re > re_start) { |
| memmove(re_start, (char*)re, le16_to_cpu(re->length)); |
| re = (INDEX_ENTRY*)re_start; |
| } |
| /* Now fixup empty index root with pointer to index allocation VCN 0. */ |
| r->index.ih_flags = LARGE_INDEX; |
| re->ie_flags |= INDEX_ENTRY_NODE; |
| if (le16_to_cpu(re->length) < sizeof(INDEX_ENTRY_HEADER) + sizeof(VCN)) |
| re->length = cpu_to_le16(le16_to_cpu(re->length) + sizeof(VCN)); |
| r->index.index_length = cpu_to_le32(le32_to_cpu(r->index.entries_offset) |
| + le16_to_cpu(re->length)); |
| r->index.allocated_size = r->index.index_length; |
| /* Resize index root attribute. */ |
| if (ntfs_resident_attr_value_resize(m, a, sizeof(INDEX_ROOT) - |
| sizeof(INDEX_HEADER) + |
| le32_to_cpu(r->index.allocated_size))) { |
| /* TODO: Remove the added bitmap! */ |
| /* Revert index root from index allocation. */ |
| err = -errno; |
| goto err_out; |
| } |
| /* Set VCN pointer to 0LL. */ |
| *(leVCN*)((char*)re + cpu_to_le16(re->length) - sizeof(VCN)) = |
| cpu_to_le64(0); |
| err = ntfs_mst_pre_write_fixup((NTFS_RECORD*)ia_val, index_block_size); |
| if (err) { |
| err = -errno; |
| ntfs_log_error("ntfs_mst_pre_write_fixup() failed in " |
| "upgrade_to_large_index.\n"); |
| goto err_out; |
| } |
| err = add_attr_index_alloc(m, name, name_len, ic, (u8*)ia_val, |
| index_block_size); |
| ntfs_mst_post_write_fixup((NTFS_RECORD*)ia_val); |
| if (err) { |
| /* TODO: Remove the added bitmap! */ |
| /* Revert index root from index allocation. */ |
| goto err_out; |
| } |
| *idx = ia_val; |
| ntfs_attr_put_search_ctx(ctx); |
| return 0; |
| err_out: |
| ntfs_attr_put_search_ctx(ctx); |
| free(ia_val); |
| return err; |
| } |
| |
| /** |
| * make_room_for_index_entry_in_index_block |
| * |
| * Create space of @size bytes at position @pos inside the index block @idx. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int make_room_for_index_entry_in_index_block(INDEX_BLOCK *idx, |
| INDEX_ENTRY *pos, u32 size) |
| { |
| u32 biu; |
| |
| if (!size) |
| return 0; |
| #ifdef DEBUG |
| /* |
| * Rigorous consistency checks. Always return -EINVAL even if more |
| * appropriate codes exist for simplicity of parsing the return value. |
| */ |
| if (size != ((size + 7) & ~7)) { |
| ntfs_log_error("make_room_for_index_entry_in_index_block() received " |
| "non 8-byte aligned size.\n"); |
| return -EINVAL; |
| } |
| if (!idx || !pos) |
| return -EINVAL; |
| if ((char*)pos < (char*)idx || (char*)pos + size < (char*)idx || |
| (char*)pos > (char*)idx + sizeof(INDEX_BLOCK) - |
| sizeof(INDEX_HEADER) + |
| le32_to_cpu(idx->index.allocated_size) || |
| (char*)pos + size > (char*)idx + sizeof(INDEX_BLOCK) - |
| sizeof(INDEX_HEADER) + |
| le32_to_cpu(idx->index.allocated_size)) |
| return -EINVAL; |
| /* The - sizeof(INDEX_ENTRY_HEADER) is for the index terminator. */ |
| if ((char*)pos - (char*)&idx->index > |
| (int)le32_to_cpu(idx->index.index_length) |
| - (int)sizeof(INDEX_ENTRY_HEADER)) |
| return -EINVAL; |
| #endif |
| biu = le32_to_cpu(idx->index.index_length); |
| /* Do we have enough space? */ |
| if (biu + size > le32_to_cpu(idx->index.allocated_size)) |
| return -ENOSPC; |
| /* Move everything after pos to pos + size. */ |
| memmove((char*)pos + size, (char*)pos, biu - ((char*)pos - |
| (char*)&idx->index)); |
| /* Update index block. */ |
| idx->index.index_length = cpu_to_le32(biu + size); |
| return 0; |
| } |
| |
| /** |
| * ntfs_index_keys_compare |
| * |
| * not all types of COLLATION_RULES supported yet... |
| * added as needed.. (remove this comment when all are added) |
| */ |
| static int ntfs_index_keys_compare(u8 *key1, u8 *key2, int key1_length, |
| int key2_length, COLLATION_RULES collation_rule) |
| { |
| u32 u1, u2; |
| int i; |
| |
| if (collation_rule == COLLATION_NTOFS_ULONG) { |
| /* i.e. $SII or $QUOTA-$Q */ |
| u1 = le32_to_cpup((const le32*)key1); |
| u2 = le32_to_cpup((const le32*)key2); |
| if (u1 < u2) |
| return -1; |
| if (u1 > u2) |
| return 1; |
| /* u1 == u2 */ |
| return 0; |
| } |
| if (collation_rule == COLLATION_NTOFS_ULONGS) { |
| /* i.e $OBJID-$O */ |
| i = 0; |
| while (i < min(key1_length, key2_length)) { |
| u1 = le32_to_cpup((const le32*)(key1 + i)); |
| u2 = le32_to_cpup((const le32*)(key2 + i)); |
| if (u1 < u2) |
| return -1; |
| if (u1 > u2) |
| return 1; |
| /* u1 == u2 */ |
| i += sizeof(u32); |
| } |
| if (key1_length < key2_length) |
| return -1; |
| if (key1_length > key2_length) |
| return 1; |
| return 0; |
| } |
| if (collation_rule == COLLATION_NTOFS_SECURITY_HASH) { |
| /* i.e. $SDH */ |
| u1 = le32_to_cpu(((SDH_INDEX_KEY*)key1)->hash); |
| u2 = le32_to_cpu(((SDH_INDEX_KEY*)key2)->hash); |
| if (u1 < u2) |
| return -1; |
| if (u1 > u2) |
| return 1; |
| /* u1 == u2 */ |
| u1 = le32_to_cpu(((SDH_INDEX_KEY*)key1)->security_id); |
| u2 = le32_to_cpu(((SDH_INDEX_KEY*)key2)->security_id); |
| if (u1 < u2) |
| return -1; |
| if (u1 > u2) |
| return 1; |
| return 0; |
| } |
| if (collation_rule == COLLATION_NTOFS_SID) { |
| /* i.e. $QUOTA-O */ |
| i = memcmp(key1, key2, min(key1_length, key2_length)); |
| if (!i) { |
| if (key1_length < key2_length) |
| return -1; |
| if (key1_length > key2_length) |
| return 1; |
| } |
| return i; |
| } |
| ntfs_log_critical("ntfs_index_keys_compare called without supported " |
| "collation rule.\n"); |
| return 0; /* Claim they're equal. What else can we do? */ |
| } |
| |
| /** |
| * insert_index_entry_in_res_dir_index |
| * |
| * i.e. insert an index_entry in some named index_root |
| * simplified search method, works for mkntfs |
| */ |
| static int insert_index_entry_in_res_dir_index(INDEX_ENTRY *idx, u32 idx_size, |
| MFT_RECORD *m, ntfschar *name, u32 name_size, ATTR_TYPES type) |
| { |
| ntfs_attr_search_ctx *ctx; |
| INDEX_HEADER *idx_header; |
| INDEX_ENTRY *idx_entry, *idx_end; |
| ATTR_RECORD *a; |
| COLLATION_RULES collation_rule; |
| int err, i; |
| |
| err = 0; |
| /* does it fit ?*/ |
| if (g_vol->mft_record_size > idx_size + le32_to_cpu(m->bytes_allocated)) |
| return -ENOSPC; |
| /* find the INDEX_ROOT attribute:*/ |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_error("Failed to allocate attribute search " |
| "context.\n"); |
| err = -ENOMEM; |
| goto err_out; |
| } |
| if (mkntfs_attr_lookup(AT_INDEX_ROOT, name, name_size, |
| CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| err = -EEXIST; |
| goto err_out; |
| } |
| /* found attribute */ |
| a = (ATTR_RECORD*)ctx->attr; |
| collation_rule = ((INDEX_ROOT*)((u8*)a + |
| le16_to_cpu(a->value_offset)))->collation_rule; |
| idx_header = (INDEX_HEADER*)((u8*)a + le16_to_cpu(a->value_offset) |
| + 0x10); |
| idx_entry = (INDEX_ENTRY*)((u8*)idx_header + |
| le32_to_cpu(idx_header->entries_offset)); |
| idx_end = (INDEX_ENTRY*)((u8*)idx_entry + |
| le32_to_cpu(idx_header->index_length)); |
| /* |
| * Loop until we exceed valid memory (corruption case) or until we |
| * reach the last entry. |
| */ |
| if (type == AT_FILE_NAME) { |
| while (((u8*)idx_entry < (u8*)idx_end) && |
| !(idx_entry->ie_flags & INDEX_ENTRY_END)) { |
| /* |
| i = ntfs_file_values_compare(&idx->key.file_name, |
| &idx_entry->key.file_name, 1, |
| IGNORE_CASE, g_vol->upcase, |
| g_vol->upcase_len); |
| */ |
| i = ntfs_names_full_collate(idx->key.file_name.file_name, idx->key.file_name.file_name_length, |
| idx_entry->key.file_name.file_name, idx_entry->key.file_name.file_name_length, |
| IGNORE_CASE, g_vol->upcase, |
| g_vol->upcase_len); |
| /* |
| * If @file_name collates before ie->key.file_name, |
| * there is no matching index entry. |
| */ |
| if (i == -1) |
| break; |
| /* If file names are not equal, continue search. */ |
| if (i) |
| goto do_next; |
| if (idx->key.file_name.file_name_type != |
| FILE_NAME_POSIX || |
| idx_entry->key.file_name.file_name_type |
| != FILE_NAME_POSIX) |
| return -EEXIST; |
| /* |
| i = ntfs_file_values_compare(&idx->key.file_name, |
| &idx_entry->key.file_name, 1, |
| CASE_SENSITIVE, g_vol->upcase, |
| g_vol->upcase_len); |
| */ |
| i = ntfs_names_full_collate(idx->key.file_name.file_name, idx->key.file_name.file_name_length, |
| idx_entry->key.file_name.file_name, idx_entry->key.file_name.file_name_length, |
| CASE_SENSITIVE, g_vol->upcase, |
| g_vol->upcase_len); |
| if (!i) |
| return -EEXIST; |
| if (i == -1) |
| break; |
| do_next: |
| idx_entry = (INDEX_ENTRY*)((u8*)idx_entry + |
| le16_to_cpu(idx_entry->length)); |
| } |
| } else if (type == AT_UNUSED) { /* case view */ |
| while (((u8*)idx_entry < (u8*)idx_end) && |
| !(idx_entry->ie_flags & INDEX_ENTRY_END)) { |
| i = ntfs_index_keys_compare((u8*)idx + 0x10, |
| (u8*)idx_entry + 0x10, |
| le16_to_cpu(idx->key_length), |
| le16_to_cpu(idx_entry->key_length), |
| collation_rule); |
| if (!i) |
| return -EEXIST; |
| if (i == -1) |
| break; |
| idx_entry = (INDEX_ENTRY*)((u8*)idx_entry + |
| le16_to_cpu(idx_entry->length)); |
| } |
| } else |
| return -EINVAL; |
| memmove((u8*)idx_entry + idx_size, (u8*)idx_entry, |
| le32_to_cpu(m->bytes_in_use) - |
| ((u8*)idx_entry - (u8*)m)); |
| memcpy((u8*)idx_entry, (u8*)idx, idx_size); |
| /* Adjust various offsets, etc... */ |
| m->bytes_in_use = cpu_to_le32(le32_to_cpu(m->bytes_in_use) + idx_size); |
| a->length = cpu_to_le32(le32_to_cpu(a->length) + idx_size); |
| a->value_length = cpu_to_le32(le32_to_cpu(a->value_length) + idx_size); |
| idx_header->index_length = cpu_to_le32( |
| le32_to_cpu(idx_header->index_length) + idx_size); |
| idx_header->allocated_size = cpu_to_le32( |
| le32_to_cpu(idx_header->allocated_size) + idx_size); |
| err_out: |
| if (ctx) |
| ntfs_attr_put_search_ctx(ctx); |
| return err; |
| } |
| |
| /** |
| * initialize_secure |
| * |
| * initializes $Secure's $SDH and $SII indexes from $SDS datastream |
| */ |
| static int initialize_secure(char *sds, u32 sds_size, MFT_RECORD *m) |
| { |
| int err, sdh_size, sii_size; |
| SECURITY_DESCRIPTOR_HEADER *sds_header; |
| INDEX_ENTRY *idx_entry_sdh, *idx_entry_sii; |
| SDH_INDEX_DATA *sdh_data; |
| SII_INDEX_DATA *sii_data; |
| |
| sds_header = (SECURITY_DESCRIPTOR_HEADER*)sds; |
| sdh_size = sizeof(INDEX_ENTRY_HEADER); |
| sdh_size += sizeof(SDH_INDEX_KEY) + sizeof(SDH_INDEX_DATA); |
| sii_size = sizeof(INDEX_ENTRY_HEADER); |
| sii_size += sizeof(SII_INDEX_KEY) + sizeof(SII_INDEX_DATA); |
| idx_entry_sdh = ntfs_calloc(sizeof(INDEX_ENTRY)); |
| if (!idx_entry_sdh) |
| return -errno; |
| idx_entry_sii = ntfs_calloc(sizeof(INDEX_ENTRY)); |
| if (!idx_entry_sii) { |
| free(idx_entry_sdh); |
| return -errno; |
| } |
| err = 0; |
| |
| while ((char*)sds_header < (char*)sds + sds_size) { |
| if (!sds_header->length) |
| break; |
| /* SDH index entry */ |
| idx_entry_sdh->data_offset = const_cpu_to_le16(0x18); |
| idx_entry_sdh->data_length = const_cpu_to_le16(0x14); |
| idx_entry_sdh->reservedV = const_cpu_to_le32(0x00); |
| idx_entry_sdh->length = const_cpu_to_le16(0x30); |
| idx_entry_sdh->key_length = const_cpu_to_le16(0x08); |
| idx_entry_sdh->ie_flags = const_cpu_to_le16(0x00); |
| idx_entry_sdh->reserved = const_cpu_to_le16(0x00); |
| idx_entry_sdh->key.sdh.hash = sds_header->hash; |
| idx_entry_sdh->key.sdh.security_id = sds_header->security_id; |
| sdh_data = (SDH_INDEX_DATA*)((u8*)idx_entry_sdh + |
| le16_to_cpu(idx_entry_sdh->data_offset)); |
| sdh_data->hash = sds_header->hash; |
| sdh_data->security_id = sds_header->security_id; |
| sdh_data->offset = sds_header->offset; |
| sdh_data->length = sds_header->length; |
| sdh_data->reserved_II = const_cpu_to_le32(0x00490049); |
| |
| /* SII index entry */ |
| idx_entry_sii->data_offset = const_cpu_to_le16(0x14); |
| idx_entry_sii->data_length = const_cpu_to_le16(0x14); |
| idx_entry_sii->reservedV = const_cpu_to_le32(0x00); |
| idx_entry_sii->length = const_cpu_to_le16(0x28); |
| idx_entry_sii->key_length = const_cpu_to_le16(0x04); |
| idx_entry_sii->ie_flags = const_cpu_to_le16(0x00); |
| idx_entry_sii->reserved = const_cpu_to_le16(0x00); |
| idx_entry_sii->key.sii.security_id = sds_header->security_id; |
| sii_data = (SII_INDEX_DATA*)((u8*)idx_entry_sii + |
| le16_to_cpu(idx_entry_sii->data_offset)); |
| sii_data->hash = sds_header->hash; |
| sii_data->security_id = sds_header->security_id; |
| sii_data->offset = sds_header->offset; |
| sii_data->length = sds_header->length; |
| if ((err = insert_index_entry_in_res_dir_index(idx_entry_sdh, |
| sdh_size, m, NTFS_INDEX_SDH, 4, AT_UNUSED))) |
| break; |
| if ((err = insert_index_entry_in_res_dir_index(idx_entry_sii, |
| sii_size, m, NTFS_INDEX_SII, 4, AT_UNUSED))) |
| break; |
| sds_header = (SECURITY_DESCRIPTOR_HEADER*)((u8*)sds_header + |
| ((le32_to_cpu(sds_header->length) + 15) & ~15)); |
| } |
| free(idx_entry_sdh); |
| free(idx_entry_sii); |
| return err; |
| } |
| |
| /** |
| * initialize_quota |
| * |
| * initialize $Quota with the default quota index-entries. |
| */ |
| static int initialize_quota(MFT_RECORD *m) |
| { |
| int o_size, q1_size, q2_size, err, i; |
| INDEX_ENTRY *idx_entry_o, *idx_entry_q1, *idx_entry_q2; |
| QUOTA_O_INDEX_DATA *idx_entry_o_data; |
| QUOTA_CONTROL_ENTRY *idx_entry_q1_data, *idx_entry_q2_data; |
| |
| err = 0; |
| /* q index entry num 1 */ |
| q1_size = 0x48; |
| idx_entry_q1 = ntfs_calloc(q1_size); |
| if (!idx_entry_q1) |
| return errno; |
| idx_entry_q1->data_offset = const_cpu_to_le16(0x14); |
| idx_entry_q1->data_length = const_cpu_to_le16(0x30); |
| idx_entry_q1->reservedV = const_cpu_to_le32(0x00); |
| idx_entry_q1->length = const_cpu_to_le16(0x48); |
| idx_entry_q1->key_length = const_cpu_to_le16(0x04); |
| idx_entry_q1->ie_flags = const_cpu_to_le16(0x00); |
| idx_entry_q1->reserved = const_cpu_to_le16(0x00); |
| idx_entry_q1->key.owner_id = const_cpu_to_le32(0x01); |
| idx_entry_q1_data = (QUOTA_CONTROL_ENTRY*)((char*)idx_entry_q1 |
| + le16_to_cpu(idx_entry_q1->data_offset)); |
| idx_entry_q1_data->version = const_cpu_to_le32(0x02); |
| idx_entry_q1_data->flags = QUOTA_FLAG_DEFAULT_LIMITS; |
| idx_entry_q1_data->bytes_used = const_cpu_to_le64(0x00); |
| idx_entry_q1_data->change_time = mkntfs_time(); |
| idx_entry_q1_data->threshold = cpu_to_sle64(-1); |
| idx_entry_q1_data->limit = cpu_to_sle64(-1); |
| idx_entry_q1_data->exceeded_time = const_cpu_to_le64(0); |
| err = insert_index_entry_in_res_dir_index(idx_entry_q1, q1_size, m, |
| NTFS_INDEX_Q, 2, AT_UNUSED); |
| free(idx_entry_q1); |
| if (err) |
| return err; |
| /* q index entry num 2 */ |
| q2_size = 0x58; |
| idx_entry_q2 = ntfs_calloc(q2_size); |
| if (!idx_entry_q2) |
| return errno; |
| idx_entry_q2->data_offset = const_cpu_to_le16(0x14); |
| idx_entry_q2->data_length = const_cpu_to_le16(0x40); |
| idx_entry_q2->reservedV = const_cpu_to_le32(0x00); |
| idx_entry_q2->length = const_cpu_to_le16(0x58); |
| idx_entry_q2->key_length = const_cpu_to_le16(0x04); |
| idx_entry_q2->ie_flags = const_cpu_to_le16(0x00); |
| idx_entry_q2->reserved = const_cpu_to_le16(0x00); |
| idx_entry_q2->key.owner_id = QUOTA_FIRST_USER_ID; |
| idx_entry_q2_data = (QUOTA_CONTROL_ENTRY*)((char*)idx_entry_q2 |
| + le16_to_cpu(idx_entry_q2->data_offset)); |
| idx_entry_q2_data->version = const_cpu_to_le32(0x02); |
| idx_entry_q2_data->flags = QUOTA_FLAG_DEFAULT_LIMITS; |
| idx_entry_q2_data->bytes_used = const_cpu_to_le64(0x00); |
| idx_entry_q2_data->change_time = mkntfs_time(); |
| idx_entry_q2_data->threshold = cpu_to_sle64(-1); |
| idx_entry_q2_data->limit = cpu_to_sle64(-1); |
| idx_entry_q2_data->exceeded_time = const_cpu_to_le64(0); |
| idx_entry_q2_data->sid.revision = 1; |
| idx_entry_q2_data->sid.sub_authority_count = 2; |
| for (i = 0; i < 5; i++) |
| idx_entry_q2_data->sid.identifier_authority.value[i] = 0; |
| idx_entry_q2_data->sid.identifier_authority.value[5] = 0x05; |
| idx_entry_q2_data->sid.sub_authority[0] = |
| const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); |
| idx_entry_q2_data->sid.sub_authority[1] = |
| const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); |
| err = insert_index_entry_in_res_dir_index(idx_entry_q2, q2_size, m, |
| NTFS_INDEX_Q, 2, AT_UNUSED); |
| free(idx_entry_q2); |
| if (err) |
| return err; |
| o_size = 0x28; |
| idx_entry_o = ntfs_calloc(o_size); |
| if (!idx_entry_o) |
| return errno; |
| idx_entry_o->data_offset = const_cpu_to_le16(0x20); |
| idx_entry_o->data_length = const_cpu_to_le16(0x04); |
| idx_entry_o->reservedV = const_cpu_to_le32(0x00); |
| idx_entry_o->length = const_cpu_to_le16(0x28); |
| idx_entry_o->key_length = const_cpu_to_le16(0x10); |
| idx_entry_o->ie_flags = const_cpu_to_le16(0x00); |
| idx_entry_o->reserved = const_cpu_to_le16(0x00); |
| idx_entry_o->key.sid.revision = 0x01; |
| idx_entry_o->key.sid.sub_authority_count = 0x02; |
| for (i = 0; i < 5; i++) |
| idx_entry_o->key.sid.identifier_authority.value[i] = 0; |
| idx_entry_o->key.sid.identifier_authority.value[5] = 0x05; |
| idx_entry_o->key.sid.sub_authority[0] = |
| const_cpu_to_le32(SECURITY_BUILTIN_DOMAIN_RID); |
| idx_entry_o->key.sid.sub_authority[1] = |
| const_cpu_to_le32(DOMAIN_ALIAS_RID_ADMINS); |
| idx_entry_o_data = (QUOTA_O_INDEX_DATA*)((char*)idx_entry_o |
| + le16_to_cpu(idx_entry_o->data_offset)); |
| idx_entry_o_data->owner_id = QUOTA_FIRST_USER_ID; |
| /* 20 00 00 00 padding after here on ntfs 3.1. 3.0 is unchecked. */ |
| idx_entry_o_data->unknown = const_cpu_to_le32(32); |
| err = insert_index_entry_in_res_dir_index(idx_entry_o, o_size, m, |
| NTFS_INDEX_O, 2, AT_UNUSED); |
| free(idx_entry_o); |
| |
| return err; |
| } |
| |
| /** |
| * insert_file_link_in_dir_index |
| * |
| * Insert the fully completed FILE_NAME_ATTR @file_name which is inside |
| * the file with mft reference @file_ref into the index (allocation) block |
| * @idx (which belongs to @file_ref's parent directory). |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int insert_file_link_in_dir_index(INDEX_BLOCK *idx, leMFT_REF file_ref, |
| FILE_NAME_ATTR *file_name, u32 file_name_size) |
| { |
| int err, i; |
| INDEX_ENTRY *ie; |
| char *index_end; |
| |
| /* |
| * Lookup dir entry @file_name in dir @idx to determine correct |
| * insertion location. FIXME: Using a very oversimplified lookup |
| * method which is sufficient for mkntfs but no good whatsoever in |
| * real world scenario. (AIA) |
| */ |
| |
| index_end = (char*)&idx->index + le32_to_cpu(idx->index.index_length); |
| ie = (INDEX_ENTRY*)((char*)&idx->index + |
| le32_to_cpu(idx->index.entries_offset)); |
| /* |
| * Loop until we exceed valid memory (corruption case) or until we |
| * reach the last entry. |
| */ |
| while ((char*)ie < index_end && !(ie->ie_flags & INDEX_ENTRY_END)) { |
| #if 0 |
| #ifdef DEBUG |
| ntfs_log_debug("file_name_attr1->file_name_length = %i\n", |
| file_name->file_name_length); |
| if (file_name->file_name_length) { |
| char *__buf = NULL; |
| i = ntfs_ucstombs((ntfschar*)&file_name->file_name, |
| file_name->file_name_length, &__buf, 0); |
| if (i < 0) |
| ntfs_log_debug("Name contains non-displayable " |
| "Unicode characters.\n"); |
| ntfs_log_debug("file_name_attr1->file_name = %s\n", |
| __buf); |
| free(__buf); |
| } |
| ntfs_log_debug("file_name_attr2->file_name_length = %i\n", |
| ie->key.file_name.file_name_length); |
| if (ie->key.file_name.file_name_length) { |
| char *__buf = NULL; |
| i = ntfs_ucstombs(ie->key.file_name.file_name, |
| ie->key.file_name.file_name_length + 1, &__buf, |
| 0); |
| if (i < 0) |
| ntfs_log_debug("Name contains non-displayable " |
| "Unicode characters.\n"); |
| ntfs_log_debug("file_name_attr2->file_name = %s\n", |
| __buf); |
| free(__buf); |
| } |
| #endif |
| #endif |
| /* |
| i = ntfs_file_values_compare(file_name, |
| (FILE_NAME_ATTR*)&ie->key.file_name, 1, |
| IGNORE_CASE, g_vol->upcase, g_vol->upcase_len); |
| */ |
| i = ntfs_names_full_collate(file_name->file_name, file_name->file_name_length, |
| ((FILE_NAME_ATTR*)&ie->key.file_name)->file_name, ((FILE_NAME_ATTR*)&ie->key.file_name)->file_name_length, |
| IGNORE_CASE, g_vol->upcase, g_vol->upcase_len); |
| /* |
| * If @file_name collates before ie->key.file_name, there is no |
| * matching index entry. |
| */ |
| if (i == -1) |
| break; |
| /* If file names are not equal, continue search. */ |
| if (i) |
| goto do_next; |
| /* File names are equal when compared ignoring case. */ |
| /* |
| * If BOTH file names are in the POSIX namespace, do a case |
| * sensitive comparison as well. Otherwise the names match so |
| * we return -EEXIST. FIXME: There are problems with this in a |
| * real world scenario, when one is POSIX and one isn't, but |
| * fine for mkntfs where we don't use POSIX namespace at all |
| * and hence this following code is luxury. (AIA) |
| */ |
| if (file_name->file_name_type != FILE_NAME_POSIX || |
| ie->key.file_name.file_name_type != FILE_NAME_POSIX) |
| return -EEXIST; |
| /* |
| i = ntfs_file_values_compare(file_name, |
| (FILE_NAME_ATTR*)&ie->key.file_name, 1, |
| CASE_SENSITIVE, g_vol->upcase, |
| g_vol->upcase_len); |
| */ |
| i = ntfs_names_full_collate(file_name->file_name, file_name->file_name_length, |
| ((FILE_NAME_ATTR*)&ie->key.file_name)->file_name, ((FILE_NAME_ATTR*)&ie->key.file_name)->file_name_length, |
| CASE_SENSITIVE, g_vol->upcase, g_vol->upcase_len); |
| if (i == -1) |
| break; |
| /* Complete match. Bugger. Can't insert. */ |
| if (!i) |
| return -EEXIST; |
| do_next: |
| #ifdef DEBUG |
| /* Next entry. */ |
| if (!ie->length) { |
| ntfs_log_debug("BUG: ie->length is zero, breaking out " |
| "of loop.\n"); |
| break; |
| } |
| #endif |
| ie = (INDEX_ENTRY*)((char*)ie + le16_to_cpu(ie->length)); |
| }; |
| i = (sizeof(INDEX_ENTRY_HEADER) + file_name_size + 7) & ~7; |
| err = make_room_for_index_entry_in_index_block(idx, ie, i); |
| if (err) { |
| ntfs_log_error("make_room_for_index_entry_in_index_block " |
| "failed: %s\n", strerror(-err)); |
| return err; |
| } |
| /* Create entry in place and copy file name attribute value. */ |
| ie->indexed_file = file_ref; |
| ie->length = cpu_to_le16(i); |
| ie->key_length = cpu_to_le16(file_name_size); |
| ie->ie_flags = cpu_to_le16(0); |
| ie->reserved = cpu_to_le16(0); |
| memcpy((char*)&ie->key.file_name, (char*)file_name, file_name_size); |
| return 0; |
| } |
| |
| /** |
| * create_hardlink_res |
| * |
| * Create a file_name_attribute in the mft record @m_file which points to the |
| * parent directory with mft reference @ref_parent. |
| * |
| * Then, insert an index entry with this file_name_attribute in the index |
| * root @idx of the index_root attribute of the parent directory. |
| * |
| * @ref_file is the mft reference of @m_file. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int create_hardlink_res(MFT_RECORD *m_parent, const leMFT_REF ref_parent, |
| MFT_RECORD *m_file, const leMFT_REF ref_file, |
| const s64 allocated_size, const s64 data_size, |
| const FILE_ATTR_FLAGS flags, const u16 packed_ea_size, |
| const u32 reparse_point_tag, const char *file_name, |
| const FILE_NAME_TYPE_FLAGS file_name_type) |
| { |
| FILE_NAME_ATTR *fn; |
| int i, fn_size, idx_size; |
| INDEX_ENTRY *idx_entry_new; |
| ntfschar *uname; |
| |
| /* Create the file_name attribute. */ |
| i = (strlen(file_name) + 1) * sizeof(ntfschar); |
| fn_size = sizeof(FILE_NAME_ATTR) + i; |
| fn = ntfs_malloc(fn_size); |
| if (!fn) |
| return -errno; |
| fn->parent_directory = ref_parent; |
| fn->creation_time = stdinfo_time(m_file); |
| fn->last_data_change_time = fn->creation_time; |
| fn->last_mft_change_time = fn->creation_time; |
| fn->last_access_time = fn->creation_time; |
| fn->allocated_size = cpu_to_sle64(allocated_size); |
| fn->data_size = cpu_to_sle64(data_size); |
| fn->file_attributes = flags; |
| /* These are in a union so can't have both. */ |
| if (packed_ea_size && reparse_point_tag) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (packed_ea_size) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (packed_ea_size) { |
| fn->packed_ea_size = cpu_to_le16(packed_ea_size); |
| fn->reserved = cpu_to_le16(0); |
| } else { |
| fn->reparse_point_tag = cpu_to_le32(reparse_point_tag); |
| } |
| fn->file_name_type = file_name_type; |
| uname = fn->file_name; |
| i = ntfs_mbstoucs_libntfscompat(file_name, &uname, i); |
| if (i < 1) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (i > 0xff) { |
| free(fn); |
| return -ENAMETOOLONG; |
| } |
| /* No terminating null in file names. */ |
| fn->file_name_length = i; |
| fn_size = sizeof(FILE_NAME_ATTR) + i * sizeof(ntfschar); |
| /* Increment the link count of @m_file. */ |
| i = le16_to_cpu(m_file->link_count); |
| if (i == 0xffff) { |
| ntfs_log_error("Too many hardlinks present already.\n"); |
| free(fn); |
| return -EINVAL; |
| } |
| m_file->link_count = cpu_to_le16(i + 1); |
| /* Add the file_name to @m_file. */ |
| i = insert_resident_attr_in_mft_record(m_file, AT_FILE_NAME, NULL, 0, |
| CASE_SENSITIVE, const_cpu_to_le16(0), |
| RESIDENT_ATTR_IS_INDEXED, (u8*)fn, fn_size); |
| if (i < 0) { |
| ntfs_log_error("create_hardlink failed adding file name " |
| "attribute: %s\n", strerror(-i)); |
| free(fn); |
| /* Undo link count increment. */ |
| m_file->link_count = cpu_to_le16( |
| le16_to_cpu(m_file->link_count) - 1); |
| return i; |
| } |
| /* Insert the index entry for file_name in @idx. */ |
| idx_size = (fn_size + 7) & ~7; |
| idx_entry_new = ntfs_calloc(idx_size + 0x10); |
| if (!idx_entry_new) |
| return -errno; |
| idx_entry_new->indexed_file = ref_file; |
| idx_entry_new->length = cpu_to_le16(idx_size + 0x10); |
| idx_entry_new->key_length = cpu_to_le16(fn_size); |
| memcpy((u8*)idx_entry_new + 0x10, (u8*)fn, fn_size); |
| i = insert_index_entry_in_res_dir_index(idx_entry_new, idx_size + 0x10, |
| m_parent, NTFS_INDEX_I30, 4, AT_FILE_NAME); |
| if (i < 0) { |
| ntfs_log_error("create_hardlink failed inserting index entry: " |
| "%s\n", strerror(-i)); |
| /* FIXME: Remove the file name attribute from @m_file. */ |
| free(idx_entry_new); |
| free(fn); |
| /* Undo link count increment. */ |
| m_file->link_count = cpu_to_le16( |
| le16_to_cpu(m_file->link_count) - 1); |
| return i; |
| } |
| free(idx_entry_new); |
| free(fn); |
| return 0; |
| } |
| |
| /** |
| * create_hardlink |
| * |
| * Create a file_name_attribute in the mft record @m_file which points to the |
| * parent directory with mft reference @ref_parent. |
| * |
| * Then, insert an index entry with this file_name_attribute in the index |
| * block @idx of the index allocation attribute of the parent directory. |
| * |
| * @ref_file is the mft reference of @m_file. |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int create_hardlink(INDEX_BLOCK *idx, const leMFT_REF ref_parent, |
| MFT_RECORD *m_file, const leMFT_REF ref_file, |
| const s64 allocated_size, const s64 data_size, |
| const FILE_ATTR_FLAGS flags, const u16 packed_ea_size, |
| const u32 reparse_point_tag, const char *file_name, |
| const FILE_NAME_TYPE_FLAGS file_name_type) |
| { |
| FILE_NAME_ATTR *fn; |
| int i, fn_size; |
| ntfschar *uname; |
| |
| /* Create the file_name attribute. */ |
| i = (strlen(file_name) + 1) * sizeof(ntfschar); |
| fn_size = sizeof(FILE_NAME_ATTR) + i; |
| fn = ntfs_malloc(fn_size); |
| if (!fn) |
| return -errno; |
| fn->parent_directory = ref_parent; |
| fn->creation_time = stdinfo_time(m_file); |
| fn->last_data_change_time = fn->creation_time; |
| fn->last_mft_change_time = fn->creation_time; |
| fn->last_access_time = fn->creation_time; |
| /* allocated size depends on unnamed data being resident */ |
| if (allocated_size && non_resident_unnamed_data(m_file)) |
| fn->allocated_size = cpu_to_sle64(allocated_size); |
| else |
| fn->allocated_size = cpu_to_sle64((data_size + 7) & -8); |
| fn->data_size = cpu_to_sle64(data_size); |
| fn->file_attributes = flags; |
| /* These are in a union so can't have both. */ |
| if (packed_ea_size && reparse_point_tag) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (packed_ea_size) { |
| fn->packed_ea_size = cpu_to_le16(packed_ea_size); |
| fn->reserved = cpu_to_le16(0); |
| } else { |
| fn->reparse_point_tag = cpu_to_le32(reparse_point_tag); |
| } |
| fn->file_name_type = file_name_type; |
| uname = fn->file_name; |
| i = ntfs_mbstoucs_libntfscompat(file_name, &uname, i); |
| if (i < 1) { |
| free(fn); |
| return -EINVAL; |
| } |
| if (i > 0xff) { |
| free(fn); |
| return -ENAMETOOLONG; |
| } |
| /* No terminating null in file names. */ |
| fn->file_name_length = i; |
| fn_size = sizeof(FILE_NAME_ATTR) + i * sizeof(ntfschar); |
| /* Increment the link count of @m_file. */ |
| i = le16_to_cpu(m_file->link_count); |
| if (i == 0xffff) { |
| ntfs_log_error("Too many hardlinks present already.\n"); |
| free(fn); |
| return -EINVAL; |
| } |
| m_file->link_count = cpu_to_le16(i + 1); |
| /* Add the file_name to @m_file. */ |
| i = insert_resident_attr_in_mft_record(m_file, AT_FILE_NAME, NULL, 0, |
| CASE_SENSITIVE, cpu_to_le16(0), |
| RESIDENT_ATTR_IS_INDEXED, (u8*)fn, fn_size); |
| if (i < 0) { |
| ntfs_log_error("create_hardlink failed adding file name attribute: " |
| "%s\n", strerror(-i)); |
| free(fn); |
| /* Undo link count increment. */ |
| m_file->link_count = cpu_to_le16( |
| le16_to_cpu(m_file->link_count) - 1); |
| return i; |
| } |
| /* Insert the index entry for file_name in @idx. */ |
| i = insert_file_link_in_dir_index(idx, ref_file, fn, fn_size); |
| if (i < 0) { |
| ntfs_log_error("create_hardlink failed inserting index entry: %s\n", |
| strerror(-i)); |
| /* FIXME: Remove the file name attribute from @m_file. */ |
| free(fn); |
| /* Undo link count increment. */ |
| m_file->link_count = cpu_to_le16( |
| le16_to_cpu(m_file->link_count) - 1); |
| return i; |
| } |
| free(fn); |
| return 0; |
| } |
| |
| /** |
| * index_obj_id_insert |
| * |
| * Insert an index entry with the key @guid and data pointing to the mft record |
| * @ref in the $O index root of the mft record @m (which must be the mft record |
| * for $ObjId). |
| * |
| * Return 0 on success or -errno on error. |
| */ |
| static int index_obj_id_insert(MFT_RECORD *m, const GUID *guid, |
| const leMFT_REF ref) |
| { |
| INDEX_ENTRY *idx_entry_new; |
| int data_ofs, idx_size, err; |
| OBJ_ID_INDEX_DATA *oi; |
| |
| /* |
| * Insert the index entry for the object id in the index. |
| * |
| * First determine the size of the index entry to be inserted. This |
| * consists of the index entry header, followed by the index key, i.e. |
| * the GUID, followed by the index data, i.e. OBJ_ID_INDEX_DATA. |
| */ |
| data_ofs = (sizeof(INDEX_ENTRY_HEADER) + sizeof(GUID) + 7) & ~7; |
| idx_size = (data_ofs + sizeof(OBJ_ID_INDEX_DATA) + 7) & ~7; |
| idx_entry_new = ntfs_calloc(idx_size); |
| if (!idx_entry_new) |
| return -errno; |
| idx_entry_new->data_offset = cpu_to_le16(data_ofs); |
| idx_entry_new->data_length = cpu_to_le16(sizeof(OBJ_ID_INDEX_DATA)); |
| idx_entry_new->length = cpu_to_le16(idx_size); |
| idx_entry_new->key_length = cpu_to_le16(sizeof(GUID)); |
| idx_entry_new->key.object_id = *guid; |
| oi = (OBJ_ID_INDEX_DATA*)((u8*)idx_entry_new + data_ofs); |
| oi->mft_reference = ref; |
| err = insert_index_entry_in_res_dir_index(idx_entry_new, idx_size, m, |
| NTFS_INDEX_O, 2, AT_UNUSED); |
| free(idx_entry_new); |
| if (err < 0) { |
| ntfs_log_error("index_obj_id_insert failed inserting index " |
| "entry: %s\n", strerror(-err)); |
| return err; |
| } |
| return 0; |
| } |
| |
| /** |
| * mkntfs_cleanup |
| */ |
| static void mkntfs_cleanup(void) |
| { |
| struct BITMAP_ALLOCATION *p, *q; |
| |
| /* Close the volume */ |
| if (g_vol) { |
| if (g_vol->dev) { |
| if (NDevOpen(g_vol->dev) && g_vol->dev->d_ops->close(g_vol->dev)) |
| ntfs_log_perror("Warning: Could not close %s", g_vol->dev->d_name); |
| ntfs_device_free(g_vol->dev); |
| } |
| free(g_vol->vol_name); |
| free(g_vol->attrdef); |
| free(g_vol->upcase); |
| free(g_vol); |
| g_vol = NULL; |
| } |
| |
| /* Free any memory we've used */ |
| free(g_bad_blocks); g_bad_blocks = NULL; |
| free(g_buf); g_buf = NULL; |
| free(g_index_block); g_index_block = NULL; |
| free(g_dynamic_buf); g_dynamic_buf = NULL; |
| free(g_mft_bitmap); g_mft_bitmap = NULL; |
| free(g_rl_bad); g_rl_bad = NULL; |
| free(g_rl_boot); g_rl_boot = NULL; |
| free(g_rl_logfile); g_rl_logfile = NULL; |
| free(g_rl_mft); g_rl_mft = NULL; |
| free(g_rl_mft_bmp); g_rl_mft_bmp = NULL; |
| free(g_rl_mftmirr); g_rl_mftmirr = NULL; |
| |
| p = g_allocation; |
| while (p) { |
| q = p->next; |
| free(p); |
| p = q; |
| } |
| } |
| |
| |
| /** |
| * mkntfs_open_partition - |
| */ |
| static BOOL mkntfs_open_partition(ntfs_volume *vol) |
| { |
| BOOL result = FALSE; |
| int i; |
| struct stat sbuf; |
| unsigned long mnt_flags; |
| |
| /* |
| * Allocate and initialize an ntfs device structure and attach it to |
| * the volume. |
| */ |
| vol->dev = ntfs_device_alloc(opts.dev_name, 0, &ntfs_device_default_io_ops, NULL); |
| if (!vol->dev) { |
| ntfs_log_perror("Could not create device"); |
| goto done; |
| } |
| |
| /* Open the device for reading or reading and writing. */ |
| if (opts.no_action) { |
| ntfs_log_quiet("Running in READ-ONLY mode!\n"); |
| i = O_RDONLY; |
| } else { |
| i = O_RDWR; |
| } |
| if (vol->dev->d_ops->open(vol->dev, i)) { |
| if (errno == ENOENT) |
| ntfs_log_error("The device doesn't exist; did you specify it correctly?\n"); |
| else |
| ntfs_log_perror("Could not open %s", vol->dev->d_name); |
| goto done; |
| } |
| /* Verify we are dealing with a block device. */ |
| if (vol->dev->d_ops->stat(vol->dev, &sbuf)) { |
| ntfs_log_perror("Error getting information about %s", vol->dev->d_name); |
| goto done; |
| } |
| |
| if (!S_ISBLK(sbuf.st_mode)) { |
| ntfs_log_error("%s is not a block device.\n", vol->dev->d_name); |
| if (!opts.force) { |
| ntfs_log_error("Refusing to make a filesystem here!\n"); |
| goto done; |
| } |
| if (!opts.num_sectors) { |
| if (!sbuf.st_size && !sbuf.st_blocks) { |
| ntfs_log_error("You must specify the number of sectors.\n"); |
| goto done; |
| } |
| if (opts.sector_size) { |
| if (sbuf.st_size) |
| opts.num_sectors = sbuf.st_size / opts.sector_size; |
| else |
| opts.num_sectors = ((s64)sbuf.st_blocks << 9) / opts.sector_size; |
| } else { |
| if (sbuf.st_size) |
| opts.num_sectors = sbuf.st_size / 512; |
| else |
| opts.num_sectors = sbuf.st_blocks; |
| opts.sector_size = 512; |
| } |
| } |
| ntfs_log_warning("mkntfs forced anyway.\n"); |
| #ifdef HAVE_LINUX_MAJOR_H |
| } else if ((IDE_DISK_MAJOR(MAJOR(sbuf.st_rdev)) && |
| MINOR(sbuf.st_rdev) % 64 == 0) || |
| (SCSI_DISK_MAJOR(MAJOR(sbuf.st_rdev)) && |
| MINOR(sbuf.st_rdev) % 16 == 0)) { |
| ntfs_log_error("%s is entire device, not just one partition.\n", vol->dev->d_name); |
| if (!opts.force) { |
| ntfs_log_error("Refusing to make a filesystem here!\n"); |
| goto done; |
| } |
| ntfs_log_warning("mkntfs forced anyway.\n"); |
| #endif |
| } |
| /* Make sure the file system is not mounted. */ |
| if (ntfs_check_if_mounted(vol->dev->d_name, &mnt_flags)) { |
| ntfs_log_perror("Failed to determine whether %s is mounted", vol->dev->d_name); |
| } else if (mnt_flags & NTFS_MF_MOUNTED) { |
| ntfs_log_error("%s is mounted.\n", vol->dev->d_name); |
| if (!opts.force) { |
| ntfs_log_error("Refusing to make a filesystem here!\n"); |
| goto done; |
| } |
| ntfs_log_warning("mkntfs forced anyway. Hope /etc/mtab is incorrect.\n"); |
| } |
| result = TRUE; |
| done: |
| return result; |
| } |
| |
| /** |
| * mkntfs_get_page_size - detect the system's memory page size. |
| */ |
| static long mkntfs_get_page_size(void) |
| { |
| long page_size; |
| #ifdef _SC_PAGESIZE |
| page_size = sysconf(_SC_PAGESIZE); |
| if (page_size < 0) |
| #endif |
| #ifdef _SC_PAGE_SIZE |
| page_size = sysconf(_SC_PAGE_SIZE); |
| if (page_size < 0) |
| #endif |
| { |
| ntfs_log_warning("Failed to determine system page size. " |
| "Assuming safe default of 4096 bytes.\n"); |
| return 4096; |
| } |
| ntfs_log_debug("System page size is %li bytes.\n", page_size); |
| return page_size; |
| } |
| |
| /** |
| * mkntfs_override_vol_params - |
| */ |
| static BOOL mkntfs_override_vol_params(ntfs_volume *vol) |
| { |
| s64 volume_size; |
| long page_size; |
| int i; |
| BOOL winboot = TRUE; |
| |
| /* If user didn't specify the sector size, determine it now. */ |
| if (opts.sector_size < 0) { |
| opts.sector_size = ntfs_device_sector_size_get(vol->dev); |
| if (opts.sector_size < 0) { |
| ntfs_log_warning("The sector size was not specified " |
| "for %s and it could not be obtained " |
| "automatically. It has been set to 512 " |
| "bytes.\n", vol->dev->d_name); |
| opts.sector_size = 512; |
| } |
| } |
| /* Validate sector size. */ |
| if ((opts.sector_size - 1) & opts.sector_size) { |
| ntfs_log_error("The sector size is invalid. It must be a " |
| "power of two, e.g. 512, 1024.\n"); |
| return FALSE; |
| } |
| if (opts.sector_size < 256 || opts.sector_size > 4096) { |
| ntfs_log_error("The sector size is invalid. The minimum size " |
| "is 256 bytes and the maximum is 4096 bytes.\n"); |
| return FALSE; |
| } |
| ntfs_log_debug("sector size = %ld bytes\n", opts.sector_size); |
| /* Now set the device block size to the sector size. */ |
| if (ntfs_device_block_size_set(vol->dev, opts.sector_size)) |
| ntfs_log_debug("Failed to set the device block size to the " |
| "sector size. This may cause problems when " |
| "creating the backup boot sector and also may " |
| "affect performance but should be harmless " |
| "otherwise. Error: %s\n", strerror(errno)); |
| /* If user didn't specify the number of sectors, determine it now. */ |
| if (opts.num_sectors < 0) { |
| opts.num_sectors = ntfs_device_size_get(vol->dev, |
| opts.sector_size); |
| if (opts.num_sectors <= 0) { |
| ntfs_log_error("Couldn't determine the size of %s. " |
| "Please specify the number of sectors " |
| "manually.\n", vol->dev->d_name); |
| return FALSE; |
| } |
| } |
| ntfs_log_debug("number of sectors = %lld (0x%llx)\n", opts.num_sectors, |
| opts.num_sectors); |
| /* |
| * Reserve the last sector for the backup boot sector unless the |
| * sector size is less than 512 bytes in which case reserve 512 bytes |
| * worth of sectors. |
| */ |
| i = 1; |
| if (opts.sector_size < 512) |
| i = 512 / opts.sector_size; |
| opts.num_sectors -= i; |
| /* If user didn't specify the partition start sector, determine it. */ |
| if (opts.part_start_sect < 0) { |
| opts.part_start_sect = ntfs_device_partition_start_sector_get( |
| vol->dev); |
| if (opts.part_start_sect < 0) { |
| ntfs_log_warning("The partition start sector was not " |
| "specified for %s and it could not be obtained " |
| "automatically. It has been set to 0.\n", |
| vol->dev->d_name); |
| opts.part_start_sect = 0; |
| winboot = FALSE; |
| } else if (opts.part_start_sect >> 32) { |
| ntfs_log_warning("The partition start sector specified " |
| "for %s and the automatically determined value " |
| "is too large. It has been set to 0.\n", |
| vol->dev->d_name); |
| opts.part_start_sect = 0; |
| winboot = FALSE; |
| } |
| } else if (opts.part_start_sect >> 32) { |
| ntfs_log_error("Invalid partition start sector. Maximum is " |
| "4294967295 (2^32-1).\n"); |
| return FALSE; |
| } |
| /* If user didn't specify the sectors per track, determine it now. */ |
| if (opts.sectors_per_track < 0) { |
| opts.sectors_per_track = ntfs_device_sectors_per_track_get( |
| vol->dev); |
| if (opts.sectors_per_track < 0) { |
| ntfs_log_warning("The number of sectors per track was " |
| "not specified for %s and it could not be " |
| "obtained automatically. It has been set to " |
| "0.\n", vol->dev->d_name); |
| opts.sectors_per_track = 0; |
| winboot = FALSE; |
| } else if (opts.sectors_per_track > 65535) { |
| ntfs_log_warning("The number of sectors per track was " |
| "not specified for %s and the automatically " |
| "determined value is too large. It has been " |
| "set to 0.\n", vol->dev->d_name); |
| opts.sectors_per_track = 0; |
| winboot = FALSE; |
| } |
| } else if (opts.sectors_per_track > 65535) { |
| ntfs_log_error("Invalid number of sectors per track. Maximum " |
| "is 65535.\n"); |
| return FALSE; |
| } |
| /* If user didn't specify the number of heads, determine it now. */ |
| if (opts.heads < 0) { |
| opts.heads = ntfs_device_heads_get(vol->dev); |
| if (opts.heads < 0) { |
| ntfs_log_warning("The number of heads was not " |
| "specified for %s and it could not be obtained " |
| "automatically. It has been set to 0.\n", |
| vol->dev->d_name); |
| opts.heads = 0; |
| winboot = FALSE; |
| } else if (opts.heads > 65535) { |
| ntfs_log_warning("The number of heads was not " |
| "specified for %s and the automatically " |
| "determined value is too large. It has been " |
| "set to 0.\n", vol->dev->d_name); |
| opts.heads = 0; |
| winboot = FALSE; |
| } |
| } else if (opts.heads > 65535) { |
| ntfs_log_error("Invalid number of heads. Maximum is 65535.\n"); |
| return FALSE; |
| } |
| volume_size = opts.num_sectors * opts.sector_size; |
| /* Validate volume size. */ |
| if (volume_size < (1 << 20)) { /* 1MiB */ |
| ntfs_log_error("Device is too small (%llikiB). Minimum NTFS " |
| "volume size is 1MiB.\n", |
| (long long)(volume_size / 1024)); |
| return FALSE; |
| } |
| ntfs_log_debug("volume size = %llikiB\n", volume_size / 1024); |
| /* If user didn't specify the cluster size, determine it now. */ |
| if (!vol->cluster_size) { |
| /* |
| * Windows Vista always uses 4096 bytes as the default cluster |
| * size regardless of the volume size so we do it, too. |
| */ |
| vol->cluster_size = 4096; |
| /* For small volumes on devices with large sector sizes. */ |
| if (vol->cluster_size < (u32)opts.sector_size) |
| vol->cluster_size = opts.sector_size; |
| /* |
| * For huge volumes, grow the cluster size until the number of |
| * clusters fits into 32 bits or the cluster size exceeds the |
| * maximum limit of 64kiB. |
| */ |
| while (volume_size >> (ffs(vol->cluster_size) - 1 + 32)) { |
| vol->cluster_size <<= 1; |
| if (vol->cluster_size > 65535) { |
| ntfs_log_error("Device is too large to hold an " |
| "NTFS volume (maximum size is " |
| "256TiB).\n"); |
| return FALSE; |
| } |
| } |
| ntfs_log_quiet("Cluster size has been automatically set to %u " |
| "bytes.\n", (unsigned)vol->cluster_size); |
| } |
| /* Validate cluster size. */ |
| if (vol->cluster_size & (vol->cluster_size - 1)) { |
| ntfs_log_error("The cluster size is invalid. It must be a " |
| "power of two, e.g. 1024, 4096.\n"); |
| return FALSE; |
| } |
| if (vol->cluster_size < (u32)opts.sector_size) { |
| ntfs_log_error("The cluster size is invalid. It must be equal " |
| "to, or larger than, the sector size.\n"); |
| return FALSE; |
| } |
| if (vol->cluster_size > 128 * (u32)opts.sector_size) { |
| ntfs_log_error("The cluster size is invalid. It cannot be " |
| "more that 128 times the size of the sector " |
| "size.\n"); |
| return FALSE; |
| } |
| if (vol->cluster_size > 65536) { |
| ntfs_log_error("The cluster size is invalid. The maximum " |
| "cluster size is 65536 bytes (64kiB).\n"); |
| return FALSE; |
| } |
| vol->cluster_size_bits = ffs(vol->cluster_size) - 1; |
| ntfs_log_debug("cluster size = %u bytes\n", |
| (unsigned int)vol->cluster_size); |
| if (vol->cluster_size > 4096) { |
| if (opts.enable_compression) { |
| if (!opts.force) { |
| ntfs_log_error("Windows cannot use compression " |
| "when the cluster size is " |
| "larger than 4096 bytes.\n"); |
| return FALSE; |
| } |
| opts.enable_compression = 0; |
| } |
| ntfs_log_warning("Windows cannot use compression when the " |
| "cluster size is larger than 4096 bytes. " |
| "Compression has been disabled for this " |
| "volume.\n"); |
| } |
| vol->nr_clusters = volume_size / vol->cluster_size; |
| /* |
| * Check the cluster_size and num_sectors for consistency with |
| * sector_size and num_sectors. And check both of these for consistency |
| * with volume_size. |
| */ |
| if ((vol->nr_clusters != ((opts.num_sectors * opts.sector_size) / |
| vol->cluster_size) || |
| (volume_size / opts.sector_size) != opts.num_sectors || |
| (volume_size / vol->cluster_size) != |
| vol->nr_clusters)) { |
| /* XXX is this code reachable? */ |
| ntfs_log_error("Illegal combination of volume/cluster/sector " |
| "size and/or cluster/sector number.\n"); |
| return FALSE; |
| } |
| ntfs_log_debug("number of clusters = %llu (0x%llx)\n", |
| vol->nr_clusters, vol->nr_clusters); |
| /* Number of clusters must fit within 32 bits (Win2k limitation). */ |
| if (vol->nr_clusters >> 32) { |
| if (vol->cluster_size >= 65536) { |
| ntfs_log_error("Device is too large to hold an NTFS " |
| "volume (maximum size is 256TiB).\n"); |
| return FALSE; |
| } |
| ntfs_log_error("Number of clusters exceeds 32 bits. Please " |
| "try again with a larger\ncluster size or " |
| "leave the cluster size unspecified and the " |
| "smallest possible cluster size for the size " |
| "of the device will be used.\n"); |
| return FALSE; |
| } |
| page_size = mkntfs_get_page_size(); |
| /* |
| * Set the mft record size. By default this is 1024 but it has to be |
| * at least as big as a sector and not bigger than a page on the system |
| * or the NTFS kernel driver will not be able to mount the volume. |
| * TODO: The mft record size should be user specifiable just like the |
| * "inode size" can be specified on other Linux/Unix file systems. |
| */ |
| vol->mft_record_size = 1024; |
| if (vol->mft_record_size < (u32)opts.sector_size) |
| vol->mft_record_size = opts.sector_size; |
| if (vol->mft_record_size > (unsigned long)page_size) |
| ntfs_log_warning("Mft record size (%u bytes) exceeds system " |
| "page size (%li bytes). You will not be able " |
| "to mount this volume using the NTFS kernel " |
| "driver.\n", (unsigned)vol->mft_record_size, |
| page_size); |
| vol->mft_record_size_bits = ffs(vol->mft_record_size) - 1; |
| ntfs_log_debug("mft record size = %u bytes\n", |
| (unsigned)vol->mft_record_size); |
| /* |
| * Set the index record size. By default this is 4096 but it has to be |
| * at least as big as a sector and not bigger than a page on the system |
| * or the NTFS kernel driver will not be able to mount the volume. |
| * FIXME: Should we make the index record size to be user specifiable? |
| */ |
| vol->indx_record_size = 4096; |
| if (vol->indx_record_size < (u32)opts.sector_size) |
| vol->indx_record_size = opts.sector_size; |
| if (vol->indx_record_size > (unsigned long)page_size) |
| ntfs_log_warning("Index record size (%u bytes) exceeds system " |
| "page size (%li bytes). You will not be able " |
| "to mount this volume using the NTFS kernel " |
| "driver.\n", (unsigned)vol->indx_record_size, |
| page_size); |
| vol->indx_record_size_bits = ffs(vol->indx_record_size) - 1; |
| ntfs_log_debug("index record size = %u bytes\n", |
| (unsigned)vol->indx_record_size); |
| if (!winboot) { |
| ntfs_log_warning("To boot from a device, Windows needs the " |
| "'partition start sector', the 'sectors per " |
| "track' and the 'number of heads' to be " |
| "set.\n"); |
| ntfs_log_warning("Windows will not be able to boot from this " |
| "device.\n"); |
| } |
| return TRUE; |
| } |
| |
| /** |
| * mkntfs_initialize_bitmaps - |
| */ |
| static BOOL mkntfs_initialize_bitmaps(void) |
| { |
| u64 i; |
| int mft_bitmap_size; |
| |
| /* Determine lcn bitmap byte size and allocate it. */ |
| g_lcn_bitmap_byte_size = (g_vol->nr_clusters + 7) >> 3; |
| /* Needs to be multiple of 8 bytes. */ |
| g_lcn_bitmap_byte_size = (g_lcn_bitmap_byte_size + 7) & ~7; |
| i = (g_lcn_bitmap_byte_size + g_vol->cluster_size - 1) & |
| ~(g_vol->cluster_size - 1); |
| ntfs_log_debug("g_lcn_bitmap_byte_size = %i, allocated = %llu\n", |
| g_lcn_bitmap_byte_size, i); |
| g_dynamic_buf_size = mkntfs_get_page_size(); |
| g_dynamic_buf = (u8*)ntfs_calloc(g_dynamic_buf_size); |
| if (!g_dynamic_buf) |
| return FALSE; |
| /* |
| * $Bitmap can overlap the end of the volume. Any bits in this region |
| * must be set. This region also encompasses the backup boot sector. |
| */ |
| if (!bitmap_allocate(g_vol->nr_clusters, |
| ((s64)g_lcn_bitmap_byte_size << 3) - g_vol->nr_clusters)) |
| return (FALSE); |
| /* |
| * Mft size is 27 (NTFS 3.0+) mft records or one cluster, whichever is |
| * bigger. |
| */ |
| g_mft_size = 27; |
| g_mft_size *= g_vol->mft_record_size; |
| if (g_mft_size < (s32)g_vol->cluster_size) |
| g_mft_size = g_vol->cluster_size; |
| ntfs_log_debug("MFT size = %i (0x%x) bytes\n", g_mft_size, g_mft_size); |
| /* Determine mft bitmap size and allocate it. */ |
| mft_bitmap_size = g_mft_size / g_vol->mft_record_size; |
| /* Convert to bytes, at least one. */ |
| g_mft_bitmap_byte_size = (mft_bitmap_size + 7) >> 3; |
| /* Mft bitmap is allocated in multiples of 8 bytes. */ |
| g_mft_bitmap_byte_size = (g_mft_bitmap_byte_size + 7) & ~7; |
| ntfs_log_debug("mft_bitmap_size = %i, g_mft_bitmap_byte_size = %i\n", |
| mft_bitmap_size, g_mft_bitmap_byte_size); |
| g_mft_bitmap = ntfs_calloc(g_mft_bitmap_byte_size); |
| if (!g_mft_bitmap) |
| return FALSE; |
| /* Create runlist for mft bitmap. */ |
| g_rl_mft_bmp = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_mft_bmp) |
| return FALSE; |
| |
| g_rl_mft_bmp[0].vcn = 0LL; |
| /* Mft bitmap is right after $Boot's data. */ |
| i = (8192 + g_vol->cluster_size - 1) / g_vol->cluster_size; |
| g_rl_mft_bmp[0].lcn = i; |
| /* |
| * Size is always one cluster, even though valid data size and |
| * initialized data size are only 8 bytes. |
| */ |
| g_rl_mft_bmp[1].vcn = 1LL; |
| g_rl_mft_bmp[0].length = 1LL; |
| g_rl_mft_bmp[1].lcn = -1LL; |
| g_rl_mft_bmp[1].length = 0LL; |
| /* Allocate cluster for mft bitmap. */ |
| return (bitmap_allocate(i,1)); |
| } |
| |
| /** |
| * mkntfs_initialize_rl_mft - |
| */ |
| static BOOL mkntfs_initialize_rl_mft(void) |
| { |
| int j; |
| BOOL done; |
| |
| /* If user didn't specify the mft lcn, determine it now. */ |
| if (!g_mft_lcn) { |
| /* |
| * We start at the higher value out of 16kiB and just after the |
| * mft bitmap. |
| */ |
| g_mft_lcn = g_rl_mft_bmp[0].lcn + g_rl_mft_bmp[0].length; |
| if (g_mft_lcn * g_vol->cluster_size < 16 * 1024) |
| g_mft_lcn = (16 * 1024 + g_vol->cluster_size - 1) / |
| g_vol->cluster_size; |
| } |
| ntfs_log_debug("$MFT logical cluster number = 0x%llx\n", g_mft_lcn); |
| /* Determine MFT zone size. */ |
| g_mft_zone_end = g_vol->nr_clusters; |
| switch (opts.mft_zone_multiplier) { /* % of volume size in clusters */ |
| case 4: |
| g_mft_zone_end = g_mft_zone_end >> 1; /* 50% */ |
| break; |
| case 3: |
| g_mft_zone_end = g_mft_zone_end * 3 >> 3;/* 37.5% */ |
| break; |
| case 2: |
| g_mft_zone_end = g_mft_zone_end >> 2; /* 25% */ |
| break; |
| case 1: |
| default: |
| g_mft_zone_end = g_mft_zone_end >> 3; /* 12.5% */ |
| break; |
| } |
| ntfs_log_debug("MFT zone size = %lldkiB\n", g_mft_zone_end << |
| g_vol->cluster_size_bits >> 10 /* >> 10 == / 1024 */); |
| /* |
| * The mft zone begins with the mft data attribute, not at the beginning |
| * of the device. |
| */ |
| g_mft_zone_end += g_mft_lcn; |
| /* Create runlist for mft. */ |
| g_rl_mft = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_mft) |
| return FALSE; |
| |
| g_rl_mft[0].vcn = 0LL; |
| g_rl_mft[0].lcn = g_mft_lcn; |
| /* rounded up division by cluster size */ |
| j = (g_mft_size + g_vol->cluster_size - 1) / g_vol->cluster_size; |
| g_rl_mft[1].vcn = j; |
| g_rl_mft[0].length = j; |
| g_rl_mft[1].lcn = -1LL; |
| g_rl_mft[1].length = 0LL; |
| /* Allocate clusters for mft. */ |
| bitmap_allocate(g_mft_lcn,j); |
| /* Determine mftmirr_lcn (middle of volume). */ |
| g_mftmirr_lcn = (opts.num_sectors * opts.sector_size >> 1) |
| / g_vol->cluster_size; |
| ntfs_log_debug("$MFTMirr logical cluster number = 0x%llx\n", |
| g_mftmirr_lcn); |
| /* Create runlist for mft mirror. */ |
| g_rl_mftmirr = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_mftmirr) |
| return FALSE; |
| |
| g_rl_mftmirr[0].vcn = 0LL; |
| g_rl_mftmirr[0].lcn = g_mftmirr_lcn; |
| /* |
| * The mft mirror is either 4kb (the first four records) or one cluster |
| * in size, which ever is bigger. In either case, it contains a |
| * byte-for-byte identical copy of the beginning of the mft (i.e. either |
| * the first four records (4kb) or the first cluster worth of records, |
| * whichever is bigger). |
| */ |
| j = (4 * g_vol->mft_record_size + g_vol->cluster_size - 1) / g_vol->cluster_size; |
| g_rl_mftmirr[1].vcn = j; |
| g_rl_mftmirr[0].length = j; |
| g_rl_mftmirr[1].lcn = -1LL; |
| g_rl_mftmirr[1].length = 0LL; |
| /* Allocate clusters for mft mirror. */ |
| done = bitmap_allocate(g_mftmirr_lcn,j); |
| g_logfile_lcn = g_mftmirr_lcn + j; |
| ntfs_log_debug("$LogFile logical cluster number = 0x%llx\n", |
| g_logfile_lcn); |
| return (done); |
| } |
| |
| /** |
| * mkntfs_initialize_rl_logfile - |
| */ |
| static BOOL mkntfs_initialize_rl_logfile(void) |
| { |
| int j; |
| u64 volume_size; |
| |
| /* Create runlist for log file. */ |
| g_rl_logfile = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_logfile) |
| return FALSE; |
| |
| |
| volume_size = g_vol->nr_clusters << g_vol->cluster_size_bits; |
| |
| g_rl_logfile[0].vcn = 0LL; |
| g_rl_logfile[0].lcn = g_logfile_lcn; |
| /* |
| * Determine logfile_size from volume_size (rounded up to a cluster), |
| * making sure it does not overflow the end of the volume. |
| */ |
| if (volume_size < 2048LL * 1024) /* < 2MiB */ |
| g_logfile_size = 256LL * 1024; /* -> 256kiB */ |
| else if (volume_size < 4000000LL) /* < 4MB */ |
| g_logfile_size = 512LL * 1024; /* -> 512kiB */ |
| else if (volume_size <= 200LL * 1024 * 1024) /* < 200MiB */ |
| g_logfile_size = 2048LL * 1024; /* -> 2MiB */ |
| else { |
| /* |
| * FIXME: The $LogFile size is 64 MiB upwards from 12GiB but |
| * the "200" divider below apparently approximates "100" or |
| * some other value as the volume size decreases. For example: |
| * Volume size LogFile size Ratio |
| * 8799808 46048 191.100 |
| * 8603248 45072 190.877 |
| * 7341704 38768 189.375 |
| * 6144828 32784 187.433 |
| * 4192932 23024 182.111 |
| */ |
| if (volume_size >= 12LL << 30) /* > 12GiB */ |
| g_logfile_size = 64 << 20; /* -> 64MiB */ |
| else |
| g_logfile_size = (volume_size / 200) & |
| ~(g_vol->cluster_size - 1); |
| } |
| j = g_logfile_size / g_vol->cluster_size; |
| while (g_rl_logfile[0].lcn + j >= g_vol->nr_clusters) { |
| /* |
| * $Logfile would overflow volume. Need to make it smaller than |
| * the standard size. It's ok as we are creating a non-standard |
| * volume anyway if it is that small. |
| */ |
| g_logfile_size >>= 1; |
| j = g_logfile_size / g_vol->cluster_size; |
| } |
| g_logfile_size = (g_logfile_size + g_vol->cluster_size - 1) & |
| ~(g_vol->cluster_size - 1); |
| ntfs_log_debug("$LogFile (journal) size = %ikiB\n", |
| g_logfile_size / 1024); |
| /* |
| * FIXME: The 256kiB limit is arbitrary. Should find out what the real |
| * minimum requirement for Windows is so it doesn't blue screen. |
| */ |
| if (g_logfile_size < 256 << 10) { |
| ntfs_log_error("$LogFile would be created with invalid size. " |
| "This is not allowed as it would cause Windows " |
| "to blue screen and during boot.\n"); |
| return FALSE; |
| } |
| g_rl_logfile[1].vcn = j; |
| g_rl_logfile[0].length = j; |
| g_rl_logfile[1].lcn = -1LL; |
| g_rl_logfile[1].length = 0LL; |
| /* Allocate clusters for log file. */ |
| return (bitmap_allocate(g_logfile_lcn,j)); |
| } |
| |
| /** |
| * mkntfs_initialize_rl_boot - |
| */ |
| static BOOL mkntfs_initialize_rl_boot(void) |
| { |
| int j; |
| /* Create runlist for $Boot. */ |
| g_rl_boot = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_boot) |
| return FALSE; |
| |
| g_rl_boot[0].vcn = 0LL; |
| g_rl_boot[0].lcn = 0LL; |
| /* |
| * $Boot is always 8192 (0x2000) bytes or 1 cluster, whichever is |
| * bigger. |
| */ |
| j = (8192 + g_vol->cluster_size - 1) / g_vol->cluster_size; |
| g_rl_boot[1].vcn = j; |
| g_rl_boot[0].length = j; |
| g_rl_boot[1].lcn = -1LL; |
| g_rl_boot[1].length = 0LL; |
| /* Allocate clusters for $Boot. */ |
| return (bitmap_allocate(0,j)); |
| } |
| |
| /** |
| * mkntfs_initialize_rl_bad - |
| */ |
| static BOOL mkntfs_initialize_rl_bad(void) |
| { |
| /* Create runlist for $BadClus, $DATA named stream $Bad. */ |
| g_rl_bad = ntfs_malloc(2 * sizeof(runlist)); |
| if (!g_rl_bad) |
| return FALSE; |
| |
| g_rl_bad[0].vcn = 0LL; |
| g_rl_bad[0].lcn = -1LL; |
| /* |
| * $BadClus named stream $Bad contains the whole volume as a single |
| * sparse runlist entry. |
| */ |
| g_rl_bad[1].vcn = g_vol->nr_clusters; |
| g_rl_bad[0].length = g_vol->nr_clusters; |
| g_rl_bad[1].lcn = -1LL; |
| g_rl_bad[1].length = 0LL; |
| |
| /* TODO: Mark bad blocks as such. */ |
| return TRUE; |
| } |
| |
| /** |
| * mkntfs_fill_device_with_zeroes - |
| */ |
| static BOOL mkntfs_fill_device_with_zeroes(void) |
| { |
| /* |
| * If not quick format, fill the device with 0s. |
| * FIXME: Except bad blocks! (AIA) |
| */ |
| int i; |
| ssize_t bw; |
| unsigned long long position; |
| float progress_inc = (float)g_vol->nr_clusters / 100; |
| u64 volume_size; |
| |
| volume_size = g_vol->nr_clusters << g_vol->cluster_size_bits; |
| |
| ntfs_log_progress("Initializing device with zeroes: 0%%"); |
| for (position = 0; position < (unsigned long long)g_vol->nr_clusters; |
| position++) { |
| if (!(position % (int)(progress_inc+1))) { |
| ntfs_log_progress("\b\b\b\b%3.0f%%", position / |
| progress_inc); |
| } |
| bw = mkntfs_write(g_vol->dev, g_buf, g_vol->cluster_size); |
| if (bw != (ssize_t)g_vol->cluster_size) { |
| if (bw != -1 || errno != EIO) { |
| ntfs_log_error("This should not happen.\n"); |
| return FALSE; |
| } |
| if (!position) { |
| ntfs_log_error("Error: Cluster zero is bad. " |
| "Cannot create NTFS file " |
| "system.\n"); |
| return FALSE; |
| } |
| /* Add the baddie to our bad blocks list. */ |
| if (!append_to_bad_blocks(position)) |
| return FALSE; |
| ntfs_log_quiet("\nFound bad cluster (%lld). Adding to " |
| "list of bad blocks.\nInitializing " |
| "device with zeroes: %3.0f%%", position, |
| position / progress_inc); |
| /* Seek to next cluster. */ |
| g_vol->dev->d_ops->seek(g_vol->dev, |
| ((off_t)position + 1) * |
| g_vol->cluster_size, SEEK_SET); |
| } |
| } |
| ntfs_log_progress("\b\b\b\b100%%"); |
| position = (volume_size & (g_vol->cluster_size - 1)) / |
| opts.sector_size; |
| for (i = 0; (unsigned long)i < position; i++) { |
| bw = mkntfs_write(g_vol->dev, g_buf, opts.sector_size); |
| if (bw != opts.sector_size) { |
| if (bw != -1 || errno != EIO) { |
| ntfs_log_error("This should not happen.\n"); |
| return FALSE; |
| } else if (i + 1ull == position) { |
| ntfs_log_error("Error: Bad cluster found in " |
| "location reserved for system " |
| "file $Boot.\n"); |
| return FALSE; |
| } |
| /* Seek to next sector. */ |
| g_vol->dev->d_ops->seek(g_vol->dev, |
| opts.sector_size, SEEK_CUR); |
| } |
| } |
| ntfs_log_progress(" - Done.\n"); |
| return TRUE; |
| } |
| |
| /** |
| * mkntfs_sync_index_record |
| * |
| * (ERSO) made a function out of this, but the reason for doing that |
| * disappeared during coding.... |
| */ |
| static BOOL mkntfs_sync_index_record(INDEX_ALLOCATION* idx, MFT_RECORD* m, |
| ntfschar* name, u32 name_len) |
| { |
| int i, err; |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| long long lw; |
| runlist *rl_index = NULL; |
| |
| i = 5 * sizeof(ntfschar); |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_perror("Failed to allocate attribute search context"); |
| return FALSE; |
| } |
| /* FIXME: This should be IGNORE_CASE! */ |
| if (mkntfs_attr_lookup(AT_INDEX_ALLOCATION, name, name_len, |
| CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_log_error("BUG: $INDEX_ALLOCATION attribute not found.\n"); |
| return FALSE; |
| } |
| a = ctx->attr; |
| rl_index = ntfs_mapping_pairs_decompress(g_vol, a, NULL); |
| if (!rl_index) { |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_log_error("Failed to decompress runlist of $INDEX_ALLOCATION " |
| "attribute.\n"); |
| return FALSE; |
| } |
| if (sle64_to_cpu(a->initialized_size) < i) { |
| ntfs_attr_put_search_ctx(ctx); |
| free(rl_index); |
| ntfs_log_error("BUG: $INDEX_ALLOCATION attribute too short.\n"); |
| return FALSE; |
| } |
| ntfs_attr_put_search_ctx(ctx); |
| i = sizeof(INDEX_BLOCK) - sizeof(INDEX_HEADER) + |
| le32_to_cpu(idx->index.allocated_size); |
| err = ntfs_mst_pre_write_fixup((NTFS_RECORD*)idx, i); |
| if (err) { |
| free(rl_index); |
| ntfs_log_error("ntfs_mst_pre_write_fixup() failed while " |
| "syncing index block.\n"); |
| return FALSE; |
| } |
| lw = ntfs_rlwrite(g_vol->dev, rl_index, (u8*)idx, i, NULL, |
| WRITE_STANDARD); |
| free(rl_index); |
| if (lw != i) { |
| ntfs_log_error("Error writing $INDEX_ALLOCATION.\n"); |
| return FALSE; |
| } |
| /* No more changes to @idx below here so no need for fixup: */ |
| /* ntfs_mst_post_write_fixup((NTFS_RECORD*)idx); */ |
| return TRUE; |
| } |
| |
| /** |
| * create_file_volume - |
| */ |
| static BOOL create_file_volume(MFT_RECORD *m, leMFT_REF root_ref, |
| VOLUME_FLAGS fl, const GUID *volume_guid) |
| { |
| int i, err; |
| u8 *sd; |
| |
| ntfs_log_verbose("Creating $Volume (mft record 3)\n"); |
| m = (MFT_RECORD*)(g_buf + 3 * g_vol->mft_record_size); |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_Volume, FILE_Volume), 0LL, 0LL, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$Volume", FILE_NAME_WIN32_AND_DOS); |
| if (!err) { |
| init_system_file_sd(FILE_Volume, &sd, &i); |
| err = add_attr_sd(m, sd, i); |
| } |
| if (!err) |
| err = add_attr_data(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), NULL, 0); |
| if (!err) |
| err = add_attr_vol_name(m, g_vol->vol_name, g_vol->vol_name ? |
| strlen(g_vol->vol_name) : 0); |
| if (!err) { |
| if (fl & VOLUME_IS_DIRTY) |
| ntfs_log_quiet("Setting the volume dirty so check " |
| "disk runs on next reboot into " |
| "Windows.\n"); |
| err = add_attr_vol_info(m, fl, g_vol->major_ver, |
| g_vol->minor_ver); |
| } |
| if (!err && opts.with_uuid) |
| err = add_attr_object_id(m, volume_guid); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Volume: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * create_backup_boot_sector |
| * |
| * Return 0 on success or -1 if it couldn't be created. |
| */ |
| static int create_backup_boot_sector(u8 *buff) |
| { |
| const char *s; |
| ssize_t bw; |
| int size, e; |
| |
| ntfs_log_verbose("Creating backup boot sector.\n"); |
| /* |
| * Write the first max(512, opts.sector_size) bytes from buf to the |
| * last sector, but limit that to 8192 bytes of written data since that |
| * is how big $Boot is (and how big our buffer is).. |
| */ |
| size = 512; |
| if (size < opts.sector_size) |
| size = opts.sector_size; |
| if (g_vol->dev->d_ops->seek(g_vol->dev, (opts.num_sectors + 1) * |
| opts.sector_size - size, SEEK_SET) == (off_t)-1) { |
| ntfs_log_perror("Seek failed"); |
| goto bb_err; |
| } |
| if (size > 8192) |
| size = 8192; |
| bw = mkntfs_write(g_vol->dev, buff, size); |
| if (bw == size) |
| return 0; |
| e = errno; |
| if (bw == -1LL) |
| s = strerror(e); |
| else |
| s = "unknown error"; |
| /* At least some 2.4 kernels return EIO instead of ENOSPC. */ |
| if (bw != -1LL || (bw == -1LL && e != ENOSPC && e != EIO)) { |
| ntfs_log_critical("Couldn't write backup boot sector: %s\n", s); |
| return -1; |
| } |
| bb_err: |
| ntfs_log_error("Couldn't write backup boot sector. This is due to a " |
| "limitation in the\nLinux kernel. This is not a major " |
| "problem as Windows check disk will create the\n" |
| "backup boot sector when it is run on your next boot " |
| "into Windows.\n"); |
| return -1; |
| } |
| |
| /** |
| * mkntfs_create_root_structures - |
| */ |
| static BOOL mkntfs_create_root_structures(void) |
| { |
| NTFS_BOOT_SECTOR *bs; |
| MFT_RECORD *m; |
| leMFT_REF root_ref; |
| leMFT_REF extend_ref; |
| int i; |
| int j; |
| int err; |
| u8 *sd; |
| FILE_ATTR_FLAGS extend_flags; |
| VOLUME_FLAGS volume_flags = const_cpu_to_le16(0); |
| int nr_sysfiles; |
| int buf_sds_first_size; |
| char *buf_sds; |
| GUID vol_guid; |
| |
| ntfs_log_quiet("Creating NTFS volume structures.\n"); |
| nr_sysfiles = 27; |
| /* |
| * Setup an empty mft record. Note, we can just give 0 as the mft |
| * reference as we are creating an NTFS 1.2 volume for which the mft |
| * reference is ignored by ntfs_mft_record_layout(). |
| * |
| * Copy the mft record onto all 16 records in the buffer and setup the |
| * sequence numbers of each system file to equal the mft record number |
| * of that file (only for $MFT is the sequence number 1 rather than 0). |
| */ |
| for (i = 0; i < nr_sysfiles; i++) { |
| if (ntfs_mft_record_layout(g_vol, 0, m = (MFT_RECORD *)(g_buf + |
| i * g_vol->mft_record_size))) { |
| ntfs_log_error("Failed to layout system mft records." |
| "\n"); |
| return FALSE; |
| } |
| if (i == 0 || i > 23) |
| m->sequence_number = cpu_to_le16(1); |
| else |
| m->sequence_number = cpu_to_le16(i); |
| } |
| /* |
| * If only one cluster contains all system files then |
| * fill the rest of it with empty, formatted records. |
| */ |
| if (nr_sysfiles * (s32)g_vol->mft_record_size < g_mft_size) { |
| for (i = nr_sysfiles; |
| i * (s32)g_vol->mft_record_size < g_mft_size; i++) { |
| m = (MFT_RECORD *)(g_buf + i * g_vol->mft_record_size); |
| if (ntfs_mft_record_layout(g_vol, 0, m)) { |
| ntfs_log_error("Failed to layout mft record." |
| "\n"); |
| return FALSE; |
| } |
| m->flags = cpu_to_le16(0); |
| m->sequence_number = cpu_to_le16(i); |
| } |
| } |
| /* |
| * Create the 16 system files, adding the system information attribute |
| * to each as well as marking them in use in the mft bitmap. |
| */ |
| for (i = 0; i < nr_sysfiles; i++) { |
| le32 file_attrs; |
| |
| m = (MFT_RECORD*)(g_buf + i * g_vol->mft_record_size); |
| if (i < 16 || i > 23) { |
| m->mft_record_number = cpu_to_le32(i); |
| m->flags |= MFT_RECORD_IN_USE; |
| ntfs_bit_set(g_mft_bitmap, 0LL + i, 1); |
| } |
| file_attrs = FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM; |
| if (i == FILE_root) { |
| file_attrs |= FILE_ATTR_ARCHIVE; |
| if (opts.disable_indexing) |
| file_attrs |= FILE_ATTR_NOT_CONTENT_INDEXED; |
| if (opts.enable_compression) |
| file_attrs |= FILE_ATTR_COMPRESSED; |
| } |
| /* setting specific security_id flag and */ |
| /* file permissions for ntfs 3.x */ |
| if (i == 0 || i == 1 || i == 2 || i == 6 || i == 8 || |
| i == 10) { |
| add_attr_std_info(m, file_attrs, |
| cpu_to_le32(0x0100)); |
| } else if (i == 9) { |
| file_attrs |= FILE_ATTR_VIEW_INDEX_PRESENT; |
| add_attr_std_info(m, file_attrs, |
| cpu_to_le32(0x0101)); |
| } else if (i == 11) { |
| add_attr_std_info(m, file_attrs, |
| cpu_to_le32(0x0101)); |
| } else if (i == 24 || i == 25 || i == 26) { |
| file_attrs |= FILE_ATTR_ARCHIVE; |
| file_attrs |= FILE_ATTR_VIEW_INDEX_PRESENT; |
| add_attr_std_info(m, file_attrs, |
| cpu_to_le32(0x0101)); |
| } else { |
| add_attr_std_info(m, file_attrs, |
| cpu_to_le32(0x00)); |
| } |
| } |
| /* The root directory mft reference. */ |
| root_ref = MK_LE_MREF(FILE_root, FILE_root); |
| extend_ref = MK_LE_MREF(11,11); |
| ntfs_log_verbose("Creating root directory (mft record 5)\n"); |
| m = (MFT_RECORD*)(g_buf + 5 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_DIRECTORY; |
| m->link_count = cpu_to_le16(le16_to_cpu(m->link_count) + 1); |
| err = add_attr_file_name(m, root_ref, 0LL, 0LL, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM | |
| FILE_ATTR_I30_INDEX_PRESENT, 0, 0, ".", |
| FILE_NAME_WIN32_AND_DOS); |
| if (!err) { |
| init_root_sd(&sd, &i); |
| err = add_attr_sd(m, sd, i); |
| } |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$I30", 4, CASE_SENSITIVE, |
| AT_FILE_NAME, COLLATION_FILE_NAME, |
| g_vol->indx_record_size); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = upgrade_to_large_index(m, "$I30", 4, CASE_SENSITIVE, |
| &g_index_block); |
| if (!err) { |
| ntfs_attr_search_ctx *ctx; |
| ATTR_RECORD *a; |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_perror("Failed to allocate attribute search " |
| "context"); |
| return FALSE; |
| } |
| /* There is exactly one file name so this is ok. */ |
| if (mkntfs_attr_lookup(AT_FILE_NAME, AT_UNNAMED, 0, |
| CASE_SENSITIVE, 0, NULL, 0, ctx)) { |
| ntfs_attr_put_search_ctx(ctx); |
| ntfs_log_error("BUG: $FILE_NAME attribute not found." |
| "\n"); |
| return FALSE; |
| } |
| a = ctx->attr; |
| err = insert_file_link_in_dir_index(g_index_block, root_ref, |
| (FILE_NAME_ATTR*)((char*)a + |
| le16_to_cpu(a->value_offset)), |
| le32_to_cpu(a->value_length)); |
| ntfs_attr_put_search_ctx(ctx); |
| } |
| if (err) { |
| ntfs_log_error("Couldn't create root directory: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| /* Add all other attributes, on a per-file basis for clarity. */ |
| ntfs_log_verbose("Creating $MFT (mft record 0)\n"); |
| m = (MFT_RECORD*)g_buf; |
| err = add_attr_data_positioned(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), g_rl_mft, g_buf, g_mft_size); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_MFT, 1), |
| ((g_mft_size - 1) |
| | (g_vol->cluster_size - 1)) + 1, |
| g_mft_size, FILE_ATTR_HIDDEN | |
| FILE_ATTR_SYSTEM, 0, 0, "$MFT", |
| FILE_NAME_WIN32_AND_DOS); |
| /* mft_bitmap is not modified in mkntfs; no need to sync it later. */ |
| if (!err) |
| err = add_attr_bitmap_positioned(m, NULL, 0, CASE_SENSITIVE, |
| g_rl_mft_bmp, |
| g_mft_bitmap, g_mft_bitmap_byte_size); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $MFT: %s\n", strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $MFTMirr (mft record 1)\n"); |
| m = (MFT_RECORD*)(g_buf + 1 * g_vol->mft_record_size); |
| err = add_attr_data_positioned(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), g_rl_mftmirr, g_buf, |
| g_rl_mftmirr[0].length * g_vol->cluster_size); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_MFTMirr, FILE_MFTMirr), |
| g_rl_mftmirr[0].length * g_vol->cluster_size, |
| g_rl_mftmirr[0].length * g_vol->cluster_size, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$MFTMirr", FILE_NAME_WIN32_AND_DOS); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $MFTMirr: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $LogFile (mft record 2)\n"); |
| m = (MFT_RECORD*)(g_buf + 2 * g_vol->mft_record_size); |
| err = add_attr_data_positioned(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), g_rl_logfile, |
| (const u8*)NULL, g_logfile_size); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_LogFile, FILE_LogFile), |
| g_logfile_size, g_logfile_size, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$LogFile", FILE_NAME_WIN32_AND_DOS); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $LogFile: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $AttrDef (mft record 4)\n"); |
| m = (MFT_RECORD*)(g_buf + 4 * g_vol->mft_record_size); |
| err = add_attr_data(m, NULL, 0, CASE_SENSITIVE, const_cpu_to_le16(0), |
| (u8*)g_vol->attrdef, g_vol->attrdef_len); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_AttrDef, FILE_AttrDef), |
| (g_vol->attrdef_len + g_vol->cluster_size - 1) & |
| ~(g_vol->cluster_size - 1), g_vol->attrdef_len, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$AttrDef", FILE_NAME_WIN32_AND_DOS); |
| if (!err) { |
| init_system_file_sd(FILE_AttrDef, &sd, &i); |
| err = add_attr_sd(m, sd, i); |
| } |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $AttrDef: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $Bitmap (mft record 6)\n"); |
| m = (MFT_RECORD*)(g_buf + 6 * g_vol->mft_record_size); |
| /* the data attribute of $Bitmap must be non-resident or otherwise */ |
| /* windows 2003 will regard the volume as corrupt (ERSO) */ |
| if (!err) |
| err = insert_non_resident_attr_in_mft_record(m, |
| AT_DATA, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), (const u8*)NULL, |
| g_lcn_bitmap_byte_size, WRITE_BITMAP); |
| |
| |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_Bitmap, FILE_Bitmap), |
| (g_lcn_bitmap_byte_size + g_vol->cluster_size - |
| 1) & ~(g_vol->cluster_size - 1), |
| g_lcn_bitmap_byte_size, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$Bitmap", FILE_NAME_WIN32_AND_DOS); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Bitmap: %s\n", strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $Boot (mft record 7)\n"); |
| m = (MFT_RECORD*)(g_buf + 7 * g_vol->mft_record_size); |
| bs = ntfs_calloc(8192); |
| if (!bs) |
| return FALSE; |
| memcpy(bs, boot_array, sizeof(boot_array)); |
| /* |
| * Create the boot sector in bs. Note, that bs is already zeroed |
| * in the boot sector section and that it has the NTFS OEM id/magic |
| * already inserted, so no need to worry about these things. |
| */ |
| bs->bpb.bytes_per_sector = cpu_to_le16(opts.sector_size); |
| bs->bpb.sectors_per_cluster = (u8)(g_vol->cluster_size / |
| opts.sector_size); |
| bs->bpb.media_type = 0xf8; /* hard disk */ |
| bs->bpb.sectors_per_track = cpu_to_le16(opts.sectors_per_track); |
| ntfs_log_debug("sectors per track = %ld (0x%lx)\n", |
| opts.sectors_per_track, opts.sectors_per_track); |
| bs->bpb.heads = cpu_to_le16(opts.heads); |
| ntfs_log_debug("heads = %ld (0x%lx)\n", opts.heads, opts.heads); |
| bs->bpb.hidden_sectors = cpu_to_le32(opts.part_start_sect); |
| ntfs_log_debug("hidden sectors = %llu (0x%llx)\n", opts.part_start_sect, |
| opts.part_start_sect); |
| bs->physical_drive = 0x80; /* boot from hard disk */ |
| bs->extended_boot_signature = 0x80; /* everybody sets this, so we do */ |
| bs->number_of_sectors = cpu_to_sle64(opts.num_sectors); |
| bs->mft_lcn = cpu_to_sle64(g_mft_lcn); |
| bs->mftmirr_lcn = cpu_to_sle64(g_mftmirr_lcn); |
| if (g_vol->mft_record_size >= g_vol->cluster_size) { |
| bs->clusters_per_mft_record = g_vol->mft_record_size / |
| g_vol->cluster_size; |
| } else { |
| bs->clusters_per_mft_record = -(ffs(g_vol->mft_record_size) - |
| 1); |
| if ((u32)(1 << -bs->clusters_per_mft_record) != |
| g_vol->mft_record_size) { |
| free(bs); |
| ntfs_log_error("BUG: calculated clusters_per_mft_record" |
| " is wrong (= 0x%x)\n", |
| bs->clusters_per_mft_record); |
| return FALSE; |
| } |
| } |
| ntfs_log_debug("clusters per mft record = %i (0x%x)\n", |
| bs->clusters_per_mft_record, |
| bs->clusters_per_mft_record); |
| if (g_vol->indx_record_size >= g_vol->cluster_size) { |
| bs->clusters_per_index_record = g_vol->indx_record_size / |
| g_vol->cluster_size; |
| } else { |
| bs->clusters_per_index_record = -g_vol->indx_record_size_bits; |
| if ((1 << -bs->clusters_per_index_record) != |
| (s32)g_vol->indx_record_size) { |
| free(bs); |
| ntfs_log_error("BUG: calculated " |
| "clusters_per_index_record is wrong " |
| "(= 0x%x)\n", |
| bs->clusters_per_index_record); |
| return FALSE; |
| } |
| } |
| ntfs_log_debug("clusters per index block = %i (0x%x)\n", |
| bs->clusters_per_index_record, |
| bs->clusters_per_index_record); |
| /* Generate a 64-bit random number for the serial number. */ |
| bs->volume_serial_number = cpu_to_le64(((u64)random() << 32) | |
| ((u64)random() & 0xffffffff)); |
| /* |
| * Leave zero for now as NT4 leaves it zero, too. If want it later, see |
| * ../libntfs/bootsect.c for how to calculate it. |
| */ |
| bs->checksum = cpu_to_le32(0); |
| /* Make sure the bootsector is ok. */ |
| if (!ntfs_boot_sector_is_ntfs(bs)) { |
| free(bs); |
| ntfs_log_error("FATAL: Generated boot sector is invalid!\n"); |
| return FALSE; |
| } |
| err = add_attr_data_positioned(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), g_rl_boot, (u8*)bs, 8192); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_Boot, FILE_Boot), |
| (8192 + g_vol->cluster_size - 1) & |
| ~(g_vol->cluster_size - 1), 8192, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$Boot", FILE_NAME_WIN32_AND_DOS); |
| if (!err) { |
| init_system_file_sd(FILE_Boot, &sd, &i); |
| err = add_attr_sd(m, sd, i); |
| } |
| if (err < 0) { |
| free(bs); |
| ntfs_log_error("Couldn't create $Boot: %s\n", strerror(-err)); |
| return FALSE; |
| } |
| if (create_backup_boot_sector((u8*)bs)) { |
| /* |
| * Pre-2.6 kernels couldn't access the last sector if it was |
| * odd and we failed to set the device block size to the sector |
| * size, hence we schedule chkdsk to create it. |
| */ |
| volume_flags |= VOLUME_IS_DIRTY; |
| } |
| free(bs); |
| /* |
| * We cheat a little here and if the user has requested all times to be |
| * set to zero then we set the GUID to zero as well. This options is |
| * only used for development purposes so that should be fine. |
| */ |
| if (!opts.use_epoch_time) { |
| /* Generate a GUID for the volume. */ |
| #ifdef ENABLE_UUID |
| uuid_generate((void*)&vol_guid); |
| #else |
| ntfs_generate_guid(&vol_guid); |
| #endif |
| } else |
| memset(&vol_guid, 0, sizeof(vol_guid)); |
| if (!create_file_volume(m, root_ref, volume_flags, &vol_guid)) |
| return FALSE; |
| ntfs_log_verbose("Creating $BadClus (mft record 8)\n"); |
| m = (MFT_RECORD*)(g_buf + 8 * g_vol->mft_record_size); |
| /* FIXME: This should be IGNORE_CASE */ |
| /* Create a sparse named stream of size equal to the volume size. */ |
| err = add_attr_data_positioned(m, "$Bad", 4, CASE_SENSITIVE, |
| const_cpu_to_le16(0), g_rl_bad, NULL, |
| g_vol->nr_clusters * g_vol->cluster_size); |
| if (!err) { |
| err = add_attr_data(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), NULL, 0); |
| } |
| if (!err) { |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_BadClus, FILE_BadClus), |
| 0LL, 0LL, FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, |
| 0, 0, "$BadClus", FILE_NAME_WIN32_AND_DOS); |
| } |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $BadClus: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| /* create $Secure (NTFS 3.0+) */ |
| ntfs_log_verbose("Creating $Secure (mft record 9)\n"); |
| m = (MFT_RECORD*)(g_buf + 9 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_VIEW_INDEX; |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(9, 9), 0LL, 0LL, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM | |
| FILE_ATTR_VIEW_INDEX_PRESENT, 0, 0, |
| "$Secure", FILE_NAME_WIN32_AND_DOS); |
| buf_sds = NULL; |
| buf_sds_first_size = 0; |
| if (!err) { |
| int buf_sds_size; |
| |
| buf_sds_first_size = 0xfc; |
| buf_sds_size = 0x40000 + buf_sds_first_size; |
| buf_sds = ntfs_calloc(buf_sds_size); |
| if (!buf_sds) |
| return FALSE; |
| init_secure_sds(buf_sds); |
| memcpy(buf_sds + 0x40000, buf_sds, buf_sds_first_size); |
| err = add_attr_data(m, "$SDS", 4, CASE_SENSITIVE, |
| const_cpu_to_le16(0), (u8*)buf_sds, |
| buf_sds_size); |
| } |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$SDH", 4, CASE_SENSITIVE, |
| AT_UNUSED, COLLATION_NTOFS_SECURITY_HASH, |
| g_vol->indx_record_size); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$SII", 4, CASE_SENSITIVE, |
| AT_UNUSED, COLLATION_NTOFS_ULONG, |
| g_vol->indx_record_size); |
| if (!err) |
| err = initialize_secure(buf_sds, buf_sds_first_size, m); |
| free(buf_sds); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Secure: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $UpCase (mft record 0xa)\n"); |
| m = (MFT_RECORD*)(g_buf + 0xa * g_vol->mft_record_size); |
| err = add_attr_data(m, NULL, 0, CASE_SENSITIVE, const_cpu_to_le16(0), |
| (u8*)g_vol->upcase, g_vol->upcase_len << 1); |
| /* |
| * The $Info only exists since Windows 8, but it apparently |
| * does not disturb chkdsk from earlier versions. |
| */ |
| if (!err) |
| err = add_attr_data(m, "$Info", 5, CASE_SENSITIVE, |
| const_cpu_to_le16(0), |
| (u8*)g_upcaseinfo, sizeof(struct UPCASEINFO)); |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(FILE_UpCase, FILE_UpCase), |
| ((g_vol->upcase_len << 1) + |
| g_vol->cluster_size - 1) & |
| ~(g_vol->cluster_size - 1), |
| g_vol->upcase_len << 1, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, |
| "$UpCase", FILE_NAME_WIN32_AND_DOS); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $UpCase: %s\n", strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $Extend (mft record 11)\n"); |
| /* |
| * $Extend index must be resident. Otherwise, w2k3 will regard the |
| * volume as corrupt. (ERSO) |
| */ |
| m = (MFT_RECORD*)(g_buf + 11 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_DIRECTORY; |
| if (!err) |
| err = create_hardlink(g_index_block, root_ref, m, |
| MK_LE_MREF(11, 11), 0LL, 0LL, |
| FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM | |
| FILE_ATTR_I30_INDEX_PRESENT, 0, 0, |
| "$Extend", FILE_NAME_WIN32_AND_DOS); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$I30", 4, CASE_SENSITIVE, |
| AT_FILE_NAME, COLLATION_FILE_NAME, |
| g_vol->indx_record_size); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Extend: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| /* NTFS reserved system files (mft records 0xc-0xf) */ |
| for (i = 0xc; i < 0x10; i++) { |
| ntfs_log_verbose("Creating system file (mft record 0x%x)\n", i); |
| m = (MFT_RECORD*)(g_buf + i * g_vol->mft_record_size); |
| err = add_attr_data(m, NULL, 0, CASE_SENSITIVE, |
| const_cpu_to_le16(0), NULL, 0); |
| if (!err) { |
| init_system_file_sd(i, &sd, &j); |
| err = add_attr_sd(m, sd, j); |
| } |
| if (err < 0) { |
| ntfs_log_error("Couldn't create system file %i (0x%x): " |
| "%s\n", i, i, strerror(-err)); |
| return FALSE; |
| } |
| } |
| /* create systemfiles for ntfs volumes (3.1) */ |
| /* starting with file 24 (ignoring file 16-23) */ |
| extend_flags = FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM | |
| FILE_ATTR_ARCHIVE | FILE_ATTR_VIEW_INDEX_PRESENT; |
| ntfs_log_verbose("Creating $Quota (mft record 24)\n"); |
| m = (MFT_RECORD*)(g_buf + 24 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_4; |
| m->flags |= MFT_RECORD_IS_VIEW_INDEX; |
| if (!err) |
| err = create_hardlink_res((MFT_RECORD*)(g_buf + |
| 11 * g_vol->mft_record_size), extend_ref, m, |
| MK_LE_MREF(24, 1), 0LL, 0LL, extend_flags, |
| 0, 0, "$Quota", FILE_NAME_WIN32_AND_DOS); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$Q", 2, CASE_SENSITIVE, AT_UNUSED, |
| COLLATION_NTOFS_ULONG, g_vol->indx_record_size); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$O", 2, CASE_SENSITIVE, AT_UNUSED, |
| COLLATION_NTOFS_SID, g_vol->indx_record_size); |
| if (!err) |
| err = initialize_quota(m); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Quota: %s\n", strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $ObjId (mft record 25)\n"); |
| m = (MFT_RECORD*)(g_buf + 25 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_4; |
| m->flags |= MFT_RECORD_IS_VIEW_INDEX; |
| if (!err) |
| err = create_hardlink_res((MFT_RECORD*)(g_buf + |
| 11 * g_vol->mft_record_size), extend_ref, |
| m, MK_LE_MREF(25, 1), 0LL, 0LL, |
| extend_flags, 0, 0, "$ObjId", |
| FILE_NAME_WIN32_AND_DOS); |
| |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$O", 2, CASE_SENSITIVE, AT_UNUSED, |
| COLLATION_NTOFS_ULONGS, |
| g_vol->indx_record_size); |
| if (!err && opts.with_uuid) |
| err = index_obj_id_insert(m, &vol_guid, |
| MK_LE_MREF(FILE_Volume, FILE_Volume)); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $ObjId: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| ntfs_log_verbose("Creating $Reparse (mft record 26)\n"); |
| m = (MFT_RECORD*)(g_buf + 26 * g_vol->mft_record_size); |
| m->flags |= MFT_RECORD_IS_4; |
| m->flags |= MFT_RECORD_IS_VIEW_INDEX; |
| if (!err) |
| err = create_hardlink_res((MFT_RECORD*)(g_buf + |
| 11 * g_vol->mft_record_size), |
| extend_ref, m, MK_LE_MREF(26, 1), |
| 0LL, 0LL, extend_flags, 0, 0, |
| "$Reparse", FILE_NAME_WIN32_AND_DOS); |
| /* FIXME: This should be IGNORE_CASE */ |
| if (!err) |
| err = add_attr_index_root(m, "$R", 2, CASE_SENSITIVE, AT_UNUSED, |
| COLLATION_NTOFS_ULONGS, g_vol->indx_record_size); |
| if (err < 0) { |
| ntfs_log_error("Couldn't create $Reparse: %s\n", |
| strerror(-err)); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| /** |
| * mkntfs_redirect |
| */ |
| static int mkntfs_redirect(struct mkntfs_options *opts2) |
| { |
| u64 upcase_crc; |
| int result = 1; |
| ntfs_attr_search_ctx *ctx = NULL; |
| long long lw, pos; |
| ATTR_RECORD *a; |
| MFT_RECORD *m; |
| int i, err; |
| |
| if (!opts2) { |
| ntfs_log_error("Internal error: invalid parameters to mkntfs_options.\n"); |
| goto done; |
| } |
| /* Initialize the random number generator with the current time. */ |
| srandom(le64_to_cpu(mkntfs_time())/10000000); |
| /* Allocate and initialize ntfs_volume structure g_vol. */ |
| g_vol = ntfs_volume_alloc(); |
| if (!g_vol) { |
| ntfs_log_perror("Could not create volume"); |
| goto done; |
| } |
| /* Create NTFS 3.1 (Windows XP/Vista) volumes. */ |
| g_vol->major_ver = 3; |
| g_vol->minor_ver = 1; |
| /* Transfer some options to the volume. */ |
| if (opts.label) { |
| g_vol->vol_name = strdup(opts.label); |
| if (!g_vol->vol_name) { |
| ntfs_log_perror("Could not copy volume name"); |
| goto done; |
| } |
| } |
| if (opts.cluster_size >= 0) |
| g_vol->cluster_size = opts.cluster_size; |
| /* Length is in unicode characters. */ |
| g_vol->upcase_len = ntfs_upcase_build_default(&g_vol->upcase); |
| /* Since Windows 8, there is a $Info stream in $UpCase */ |
| g_upcaseinfo = |
| (struct UPCASEINFO*)ntfs_malloc(sizeof(struct UPCASEINFO)); |
| if (!g_vol->upcase_len || !g_upcaseinfo) |
| goto done; |
| /* If the CRC is correct, chkdsk does not warn about obsolete table */ |
| crc64(0,(byte*)NULL,0); /* initialize the crc computation */ |
| upcase_crc = crc64(0,(byte*)g_vol->upcase, |
| g_vol->upcase_len * sizeof(ntfschar)); |
| /* keep the version fields as zero */ |
| memset(g_upcaseinfo, 0, sizeof(struct UPCASEINFO)); |
| g_upcaseinfo->len = cpu_to_le32(sizeof(struct UPCASEINFO)); |
| g_upcaseinfo->crc = cpu_to_le64(upcase_crc); |
| g_vol->attrdef = ntfs_malloc(sizeof(attrdef_ntfs3x_array)); |
| if (!g_vol->attrdef) { |
| ntfs_log_perror("Could not create attrdef structure"); |
| goto done; |
| } |
| memcpy(g_vol->attrdef, attrdef_ntfs3x_array, |
| sizeof(attrdef_ntfs3x_array)); |
| g_vol->attrdef_len = sizeof(attrdef_ntfs3x_array); |
| /* Open the partition. */ |
| if (!mkntfs_open_partition(g_vol)) |
| goto done; |
| /* |
| * Decide on the sector size, cluster size, mft record and index record |
| * sizes as well as the number of sectors/tracks/heads/size, etc. |
| */ |
| if (!mkntfs_override_vol_params(g_vol)) |
| goto done; |
| /* Initialize $Bitmap and $MFT/$BITMAP related stuff. */ |
| if (!mkntfs_initialize_bitmaps()) |
| goto done; |
| /* Initialize MFT & set g_logfile_lcn. */ |
| if (!mkntfs_initialize_rl_mft()) |
| goto done; |
| /* Initialize $LogFile. */ |
| if (!mkntfs_initialize_rl_logfile()) |
| goto done; |
| /* Initialize $Boot. */ |
| if (!mkntfs_initialize_rl_boot()) |
| goto done; |
| /* Allocate a buffer large enough to hold the mft. */ |
| g_buf = ntfs_calloc(g_mft_size); |
| if (!g_buf) |
| goto done; |
| /* Create runlist for $BadClus, $DATA named stream $Bad. */ |
| if (!mkntfs_initialize_rl_bad()) |
| goto done; |
| /* If not quick format, fill the device with 0s. */ |
| if (!opts.quick_format) { |
| if (!mkntfs_fill_device_with_zeroes()) |
| goto done; |
| } |
| /* Create NTFS volume structures. */ |
| if (!mkntfs_create_root_structures()) |
| goto done; |
| /* |
| * - Do not step onto bad blocks!!! |
| * - If any bad blocks were specified or found, modify $BadClus, |
| * allocating the bad clusters in $Bitmap. |
| * - C&w bootsector backup bootsector (backup in last sector of the |
| * partition). |
| * - If NTFS 3.0+, c&w $Secure file and $Extend directory with the |
| * corresponding special files in it, i.e. $ObjId, $Quota, $Reparse, |
| * and $UsnJrnl. And others? Or not all necessary? |
| * - RE: Populate $root with the system files (and $Extend directory if |
| * applicable). Possibly should move this as far to the top as |
| * possible and update during each subsequent c&w of each system file. |
| */ |
| ntfs_log_verbose("Syncing root directory index record.\n"); |
| if (!mkntfs_sync_index_record(g_index_block, (MFT_RECORD*)(g_buf + 5 * |
| g_vol->mft_record_size), NTFS_INDEX_I30, 4)) |
| goto done; |
| |
| ntfs_log_verbose("Syncing $Bitmap.\n"); |
| m = (MFT_RECORD*)(g_buf + 6 * g_vol->mft_record_size); |
| |
| ctx = ntfs_attr_get_search_ctx(NULL, m); |
| if (!ctx) { |
| ntfs_log_perror("Could not create an attribute search context"); |
| goto done; |
| } |
| |
| if (mkntfs_attr_lookup(AT_DATA, AT_UNNAMED, 0, CASE_SENSITIVE, |
| 0, NULL, 0, ctx)) { |
| ntfs_log_error("BUG: $DATA attribute not found.\n"); |
| goto done; |
| } |
| |
| a = ctx->attr; |
| if (a->non_resident) { |
| runlist *rl = ntfs_mapping_pairs_decompress(g_vol, a, NULL); |
| if (!rl) { |
| ntfs_log_error("ntfs_mapping_pairs_decompress() failed\n"); |
| goto done; |
| } |
| lw = ntfs_rlwrite(g_vol->dev, rl, (const u8*)NULL, |
| g_lcn_bitmap_byte_size, NULL, WRITE_BITMAP); |
| err = errno; |
| free(rl); |
| if (lw != g_lcn_bitmap_byte_size) { |
| ntfs_log_error("ntfs_rlwrite: %s\n", lw == -1 ? |
| strerror(err) : "unknown error"); |
| goto done; |
| } |
| } else { |
| /* Error : the bitmap must be created non resident */ |
| ntfs_log_error("Error : the global bitmap is resident\n"); |
| goto done; |
| } |
| |
| /* |
| * No need to sync $MFT/$BITMAP as that has never been modified since |
| * its creation. |
| */ |
| ntfs_log_verbose("Syncing $MFT.\n"); |
| pos = g_mft_lcn * g_vol->cluster_size; |
| lw = 1; |
| for (i = 0; i < g_mft_size / (s32)g_vol->mft_record_size; i++) { |
| if (!opts.no_action) |
| lw = ntfs_mst_pwrite(g_vol->dev, pos, 1, g_vol->mft_record_size, g_buf + i * g_vol->mft_record_size); |
| if (lw != 1) { |
| ntfs_log_error("ntfs_mst_pwrite: %s\n", lw == -1 ? |
| strerror(errno) : "unknown error"); |
| goto done; |
| } |
| pos += g_vol->mft_record_size; |
| } |
| ntfs_log_verbose("Updating $MFTMirr.\n"); |
| pos = g_mftmirr_lcn * g_vol->cluster_size; |
| lw = 1; |
| for (i = 0; i < g_rl_mftmirr[0].length * g_vol->cluster_size / g_vol->mft_record_size; i++) { |
| m = (MFT_RECORD*)(g_buf + i * g_vol->mft_record_size); |
| /* |
| * Decrement the usn by one, so it becomes the same as the one |
| * in $MFT once it is mst protected. - This is as we need the |
| * $MFTMirr to have the exact same byte by byte content as |
| * $MFT, rather than just equivalent meaning content. |
| */ |
| if (ntfs_mft_usn_dec(m)) { |
| ntfs_log_error("ntfs_mft_usn_dec"); |
| goto done; |
| } |
| if (!opts.no_action) |
| lw = ntfs_mst_pwrite(g_vol->dev, pos, 1, g_vol->mft_record_size, g_buf + i * g_vol->mft_record_size); |
| if (lw != 1) { |
| ntfs_log_error("ntfs_mst_pwrite: %s\n", lw == -1 ? |
| strerror(errno) : "unknown error"); |
| goto done; |
| } |
| pos += g_vol->mft_record_size; |
| } |
| ntfs_log_verbose("Syncing device.\n"); |
| if (g_vol->dev->d_ops->sync(g_vol->dev)) { |
| ntfs_log_error("Syncing device. FAILED"); |
| goto done; |
| } |
| ntfs_log_quiet("mkntfs completed successfully. Have a nice day.\n"); |
| result = 0; |
| done: |
| ntfs_attr_put_search_ctx(ctx); |
| mkntfs_cleanup(); /* Device is unlocked and closed here */ |
| return result; |
| } |
| |
| |
| /** |
| * main - Begin here |
| * |
| * Start from here. |
| * |
| * Return: 0 Success, the program worked |
| * 1 Error, something went wrong |
| */ |
| int main(int argc, char *argv[]) |
| { |
| int result = 1; |
| |
| ntfs_log_set_handler(ntfs_log_handler_outerr); |
| utils_set_locale(); |
| |
| mkntfs_init_options(&opts); /* Set up the options */ |
| |
| if (!mkntfs_parse_options(argc, argv, &opts)) /* Read the command line options */ |
| goto done; |
| |
| result = mkntfs_redirect(&opts); |
| done: |
| return result; |
| } |