From bd5e13a59b89dcaaef4681416c984b20ac7243a3 Mon Sep 17 00:00:00 2001 From: !antona Date: Thu, 22 Aug 2002 18:09:47 +0000 Subject: [PATCH] Change sizedetection for files so we work with sparse files, too. 2002/07/15 16:41:48-00:00 !flatcap AT_NONAME -> AT_UNNAMED 2002/07/15 15:09:21-00:00 !flatcap lingering attribute name problems 2002/07/11 16:20:34-00:00 !flatcap whitespace and include guards 2002/07/09 19:17:49-00:00 !flatcap move the runlist functions from attrib.c to runlist.c 2002/07/08 23:27:16-00:00 !flatcap added AT_NONAME so we can search for a (un)named attribute or just iterate through all attributes 2002/07/06 20:07:59-00:00 !antona New API for compressing run lists into mapping pairs arrays and adapt mkntfs to that API. Addition of ntfs_walk_attrs(). 2002/07/03 21:56:01-00:00 !antona Updates 2002/07/02 23:47:11-00:00 !antona Global replacement of __[su]{8,16,32,64} with [su]{8,16,32,64} and layout.h define it. 2002/04/27 19:49:10-00:00 !antona Update library, new APIs ntfs_attr_find_vcn(), misc fixes and cleanups, make all the utilities compile, fix bugs I noticed in ntfslabel and it now works properly. 2002/04/23 23:27:33-00:00 !antona Fixup the force option in mkntfs.c. Change the ntfs_check_if_mounted so it works on system not implementing {set,get,end}mntent, too. Also make it more powerful in telling us not only if something is mounted but also if it is the fs root and if it is read-only. 2002/04/23 19:37:18-00:00 !mattjf Removed the check_mount() function and added ntfs_check_if_mounted() to mkntfs 2002/04/22 10:34:32-00:00 !antona Attribute list support (merging done, part 2, some stuff still incomplete). mkntfs ntfs volume creation. See the changelog... 2002/04/21 01:26:39-00:00 !antona Cleanup/streamline include file dependencies. 2002/04/21 01:12:55-00:00 !antona Fix mkntfs again. 2002/04/20 23:09:43-00:00 !antona Port attribute lookup functions with attribute list support from ntfs tng driver. Port/reimplement extent mft record handling code as well. Rename out all dollar signs from type names and constants. Adapt all callers to new API. Note mkntfs is currently broken due to some needed work. 2002/04/19 21:09:55-00:00 !antona Finished provisional inode.c::ntfs_{open,close}_inode() functions. Also, started defining API provided by attrib.[ch], so far only done search context related stuff. 2002/04/18 18:15:46-00:00 !antona Define API for bootsect.[ch]: is_boot_sector_ntfs(). 2002/04/16 15:34:32-00:00 !antona Fix the library... 2002/04/15 21:41:16-00:00 !antona Little fix of a fix. (-: 2002/04/15 20:04:29-00:00 !antona Fix all compiler warnings that came up with -Wall. Enabled -Wall for ./configure --enable-debug everywhere. Fix a few bugs in mkntfs that came up in the warnings (just error code paths, nothing major). 2002/04/15 19:02:41-00:00 !antona Really fix the library and mkntfs while at it. 2002/04/14 15:26:24-00:00 !antona Remove find_first_attr and make all users use get_attr_search_ctx + find_attr instead. 2002/03/12 22:11:02-00:00 !antona Final tidyups. 2002/03/12 22:00:44-00:00 !antona Fix a few small mistakes 2002/03/12 21:48:27-00:00 !antona Change version numbers of mkntfs and ntfsfix to match linux-ntfs release and add options to mkntfs to disable content indexing on the volume and to enable compression on the volume. 2002/03/12 00:34:35-00:00 !antona Fix typo in mkntfs usage information. 2002/02/01 02:46:16-00:00 !antona Attempt to fix compile warnings on powerpc. 2002/01/10 10:54:27-00:00 !antona Updates 2002/01/10 10:11:46-00:00 !antona Fix logfile size calculation. 2001/12/15 05:26:07-00:00 !antona Hm, it would appear that on my SuSE 7.2 + many other thins + 2.5.1-pre11 kernel mkntfs gets an EINVAL from seek instead of ENOSPACE crashes out. Converted the crash into a warning only. 2001/11/10 16:24:13-00:00 !antona More logfile size updates so we are more sane with floppies. 2001/11/10 14:47:39-00:00 !antona Debug display bug fixes. 2001/11/10 14:27:38-00:00 !antona And more typos. 2001/11/10 14:25:33-00:00 !antona Oops. Didn't compile. Typo 2001/11/10 14:22:15-00:00 !antona Remove obsoleted disklabel.h stuff. 2001/11/10 14:17:39-00:00 !antona Enhance mkntfs' device size determination. 2001/11/10 03:06:05-00:00 !antona Bug fixes and debug output enhancements. 2001/11/09 23:36:17-00:00 !antona Bug fixes 2001/11/09 21:18:22-00:00 !antona More fixes and allow small volumes down to 1MiB, scaling down the $LogFile as much as necessary. 2001/11/09 19:21:09-00:00 !antona Fix directories on large clusters and allow 4MB volumes. 2001/08/27 16:58:07-00:00 !antona Updates. 2001/07/25 22:49:25-00:00 !antona Small tidying up of a misspelling. 2001/07/25 22:30:34-00:00 !antona Bug fixes for cluster sizes > 4kb involving corrections to mft mirror size and contents, mft data attribute position and mft bitmap size. Some of those were nasty so this is a major improvement. 2001/06/16 19:22:45-00:00 !antona Get rid of logfile stuff for mkntfs as it is clearly not needed. 2001/06/16 18:26:34-00:00 !antona Make mft bitmap non resident and located just after $Boot. This fixes all the crashes experienced! This makes mkntfs complete and bug free AFAIK. 2001/06/15 16:47:47-00:00 !antona Integrate logfile.c into mkntfs 2001/06/13 19:00:56-00:00 !antona More cleanups and man page final updates/polishing. 2001/06/13 18:07:00-00:00 !antona Some output cleanup. 2001/06/13 12:21:51-00:00 !flatcap the backup boot sector is in no-mans-land beyond the normal clusters 2001/06/13 11:51:17-00:00 !flatcap fixed sector/cluster typo 2001/06/13 11:47:05-00:00 !flatcap $bitmap - set bits beyond volume 2001/06/13 01:35:02-00:00 !antona fix backup boot sector problem with error message instead of crashing out. we rely on chkdsk to fix this as it is a kernel limitation we can't do anything about atm. 2001/06/13 00:13:38-00:00 !antona Fix run list corruption stupidity. 2001/06/12 22:39:35-00:00 !antona Testing 2001/06/12 22:17:30-00:00 !antona Testing. 2001/06/12 21:58:32-00:00 !antona Fix stupid bugs in calculating the clusters per mft/index record values. 2001/06/12 20:13:35-00:00 !antona Fix nr_sectors / sector_size problems with previous commit. 2001/06/12 20:02:50-00:00 !antona Enable automatic determination of file size of non-block devices instead of crashing out. A user specified size stil overrides the actual size, we assume the file size will be adjusted automatically by the seek to the last sector and write of the backup boot sector. 2001/06/11 20:31:29-00:00 !antona Bugfixing of mkntfs.c. Loads of it. (-8 2001/06/11 04:02:09-00:00 !antona Linux-NTFS 1.0.0-pre-1 - FEATURE FREEZE ======================================= mkntfs complete with option parsing and more cool things. mkntfs man page complete. info files updated. TODO Before 1.0.0 final: - Test mkntfs options & mkntfs itself. - Test tar ball generation. - Test rpm generation. 2001/06/10 15:54:20-00:00 !antona Linux-NTFS 0.1.0-pre1 ===================== -fixed up ntfsfix and ntfsdump_logfile -corrected stuff -several bug fixes -fixed (hopefully) final bug with mkntfs (sd generator was wrong due to brain'o) -mkntfs now completed, only need to add a few command line options before first public release. -rpm generation file updated and autostrip modified to make use of install-stip make target instead of stripping manually -made bootsector check verbosity during mount dependent on --enable-debug configure option. 2001/06/10 14:00:12-00:00 !antona mkntfs alpha 4 ============== - set back up boot sector as used in volume bitmap - almost no errors left. only thing chkdsk now complains about is the root dir security descriptor. 2001/06/10 02:25:38-00:00 !antona mkntfs alpha 3 ============== - Several bugfixes (root dir link count wasn't incremented, mftmirror usns weren't correct [off by one too high], etc). - Implement new $UpCase generation using flatcap (Richard Russon)'s algorithm for generating it. This dropped the stripped mkntfs executable from 204kb down to 78kb in size. A whopping 62% size decrease! Yey! And the source code dropped ny over 600kb in size as well. And compilation got quicker, too. 2001/06/09 18:32:57-00:00 !antona mkntfs alpha 2 create_hardlink() was forgetting to increment the use count! 2001/06/09 16:31:13-00:00 !antona mkntfs alpha release is here! Yey! The only thing I am worried about is the fact that the system call to get the number of sectors on the device returns a value rounded to the nearest 1024 bytes (converted to 512 byte blocks) thus we might be writting the backup boot sector too early instead of on the real last sector but there is nothing I can do apart from starting to play games like accessing the main device for hds instead of the partition device which wouldn't help in the case of the last partition though... Alternatively have to place the backup boot sector in the middle of the disk like WinNT3.51 and earlier did but I need an image to see exactly how they did it and even then we have the problem of not knowing where the middle of the disk is as we don't really know how many sectors there are for real with Linux kernel lying to us. 2001/06/09 00:25:56-00:00 !antona mkntfs delayed. more reverse engineering required to determine exact method of index entry collation. first few helper functions are already done and entered into ntfslib in unistr.c 2001/06/08 19:58:25-00:00 !antona getting closer to mkntfs alpha. 2001/06/08 14:09:52-00:00 !antona mkntfs compiles. But untested. Also there are still things to do... 2001/06/07 23:31:54-00:00 !antona mkntfs - the end to the saga draws closer... 2001/06/06 22:55:49-00:00 !antona And some more mkntfs + some updates to layout.h concerning directories and alignment requirements. 2001/06/05 23:45:47-00:00 !antona The mkntfs saga continues. 2001/06/05 10:33:02-00:00 !antona Update automatic config/make process and mkntfs to use the cvs release tag for version reporting. 2001/06/05 10:19:53-00:00 !antona mkntfs update. 2001/06/04 23:38:55-00:00 !antona Integrate ldm.c into automatic build process. A bit more on mkntfs and make it compile (do _not_ run as it is - am working on directory creation so chances are it will hang if you run it or something). 2001/06/04 14:04:31-00:00 !antona More work on mkntfs dir stuff. 2001/06/04 11:29:45-00:00 !antona *** empty log message *** 2001/06/03 12:04:30-00:00 !antona More directory work in mkntfs 2001/06/03 02:09:08-00:00 !antona Fix/expand dircetory info in layout.h and add creation of index root attribute to mkntfs.c. 2001/06/01 19:04:08-00:00 !antona Updates & fixes. 2001/06/01 02:07:26-00:00 !antona It has been a long time since last commit. At moment have done a lot of work on mkntfs but also at the moment ntfsfix and ntfsdump_logfile and libntfs are broken. Basically only mkntfs works and that is not complete either. 2001/04/08 03:02:55-00:00 !antona Added cvs Id header. 2001/03/02 15:09:25-00:00 !antona Added begin of mkntfs utility. (Doesn't do anything, including doesn't compile at the moment.) (Logical change 1.5) --- ntfstools/mkntfs.c | 3554 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3554 insertions(+) diff --git a/ntfstools/mkntfs.c b/ntfstools/mkntfs.c index e69de29b..47357c76 100644 --- a/ntfstools/mkntfs.c +++ b/ntfstools/mkntfs.c @@ -0,0 +1,3554 @@ +/* + * $Id$ + * + * mkntfs - Part of the Linux-NTFS project. + * + * Copyright (c) 2000-2002 Anton Altaparmakov. + * Copyright (C) 2001-2002 Richard Russon. + * + * This utility will create an NTFS 1.2 (Windows NT 4.0) 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. + * + * Anton Altaparmakov + * + * 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 + */ + +/* + * WARNING: This program might not work on architectures which do not allow + * unaligned access. For those, the program would need to start using + * get/put_unaligned macros (#include ), but not doing it yet, + * since NTFS really mostly applies to ia32 only, which does allow unaligned + * accesses. We might not actually have a problem though, since the structs are + * defined as being packed so that might be enough for gcc to insert the + * correct code. + * + * If anyone using a non-little endian and/or an aligned access only CPU tries + * this program please let me know whether it works or not! + * + * Anton Altaparmakov + */ + +#include "config.h" + +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STDIO_H +# include +#endif +#ifdef HAVE_STDARG_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_ERRNO_H +# include +#endif +#include +#ifdef HAVE_LINUX_FD_H +# include +# include +#endif +#include +#ifdef HAVE_GETOPT_H +# include +#else + extern char *optarg; + extern int optind; +#endif +#include +#ifdef HAVE_LINUX_MAJOR_H +# include +#endif +#ifndef MAJOR +# define MAJOR(dev) ((dev) >> 8) +# define MINOR(dev) ((dev) & 0xff) +#endif +#ifndef SCSI_BLK_MAJOR +# define SCSI_BLK_MAJOR(m) ((m) == SCSI_DISK_MAJOR || \ + (m) == SCSI_CDROM_MAJOR) +#endif +#include + +#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE) +# define BLKGETSIZE _IO(0x12,96) /* Get device size in 512byte blocks. */ +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET) +# define BLKSSZGET _IO(0x12,104) /* Get device sector size in bytse. */ +#endif + +#include "types.h" +#include "bootsect.h" +#include "disk_io.h" +#include "attrib.h" +#include "bitmap.h" +#include "mst.h" +#include "dir.h" +#include "runlist.h" + +extern const unsigned char attrdef_ntfs12_array[2400]; +extern const unsigned char boot_array[3429]; +extern void init_system_file_sd(int sys_file_no, char **sd_val, + int *sd_val_len); +extern void init_upcase_table(uchar_t *uc, u32 uc_len); + +/* Page size on ia32. Can change to 8192 on Alpha. */ +#define NTFS_PAGE_SIZE 4096 + +const char *EXEC_NAME = "mkntfs"; + +/* Need these global so mkntfs_exit can access them. */ +struct flock flk; +char *buf = NULL; +char *buf2 = NULL; +int buf2_size = 0; +int mft_bitmap_size, mft_bitmap_byte_size; +unsigned char *mft_bitmap = NULL; +int lcn_bitmap_byte_size; +unsigned char *lcn_bitmap = NULL; +run_list *rl = NULL, *rl_mft = NULL, *rl_mft_bmp = NULL, *rl_mftmirr = NULL; +run_list *rl_logfile = NULL, *rl_boot = NULL, *rl_bad = NULL, *rl_index; +INDEX_ALLOCATION *index_block = NULL; +ntfs_volume *vol; + +struct { + int sector_size; /* -s, in bytes, power of 2, default is + 512 bytes. */ + long long nr_sectors; /* size of device in sectors */ + long long nr_clusters; /* Note: Win2k treats clusters as + 32-bit entities! */ + long long volume_size; /* in bytes, or suffixed + with k for kB, m or M for MB, or + g or G for GB, or t or T for TB */ + int index_block_size; /* in bytes. */ + int mft_size; /* The bigger of 16kB & one cluster. */ + long long mft_lcn; /* lcn of $MFT, $DATA attribute. */ + long long mftmirr_lcn; /* lcn of $MFTMirr, $DATA. */ + long long logfile_lcn; /* lcn of $LogFile, $DATA. */ + int logfile_size; /* in bytes, determined from + volume_size. */ + char mft_zone_multiplier; /* -z, value from 1 to 4. Default is + 1. */ + long long mft_zone_end; /* Determined from volume_size and + mft_zone_multiplier, in clusters. */ + char no_action; /* -n, do not write to device, only + display what would be done. */ + char check_bad_blocks; /* read-only test for bad + clusters. */ + long long *bad_blocks; /* Array of bad clusters. */ + long long nr_bad_blocks; /* Number of bad clusters. */ + char *bad_blocks_filename; /* filename, file to read list of + bad clusters from. */ + ATTR_DEF *attr_defs; /* filename, attribute defs. */ + int attr_defs_len; /* in bytes */ + uchar_t *upcase; /* filename, upcase table. */ + u32 upcase_len; /* Determined automatically. */ + char quiet; /* -q, quiet execution. */ + char verbose; /* -v, verbose execution, given twice, + * really verbose execution (debug + * mode). */ + char force; /* -F, force fs creation. */ + char quick_format; /* -f or -Q, fast format, don't zero + the volume first. */ + char enable_compression; /* -C, enables compression of all files + on the volume by default. */ + char disable_indexing; /* -I, disables indexing of file + contents on the volume by default. */ + /* -V, print version and exit. */ +} opt; + +/* Error output. Ignores quiet (-q). */ +void Eprintf(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void err_exit(const char *fmt, ...) __attribute__ ((noreturn)); + +/* Error output and terminate. Ignores quiet (-q). */ +void err_exit(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "Aborting...\n"); + exit(1); +} + +/* Debugging output (-vv). Overriden by quiet (-q). */ +void Dprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet && opt.verbose > 1) { + printf("DEBUG: "); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +/* Verbose output (-v). */ +void Vprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet && opt.verbose > 0) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +/* Quietable output (if not -q). */ +void Qprintf(const char *fmt, ...) +{ + va_list ap; + + if (!opt.quiet) { + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } +} + +void append_to_bad_blocks(unsigned long block) +{ + long long *new_buf; + + if (!(opt.nr_bad_blocks & 15)) { + new_buf = realloc(opt.bad_blocks, (opt.nr_bad_blocks + 16) * + sizeof(long long)); + if (!new_buf) + err_exit("Reallocating memory for bad blocks list " + "failed: %s\n", strerror(errno)); + if (opt.bad_blocks != new_buf) + free(opt.bad_blocks); + opt.bad_blocks = new_buf; + } + opt.bad_blocks[opt.nr_bad_blocks++] = block; +} + +__inline__ long long mkntfs_write(int fd, const void *buf, long long count) +{ + long long bytes_written, total; + int retry; + + if (opt.no_action) + return count; + total = 0LL; + retry = 0; + do { + bytes_written = write(fd, buf, count); + if (bytes_written == -1LL) { + retry = errno; + Eprintf("Error writing to %s: %s\n", vol->dev_name, + strerror(errno)); + errno = retry; + return bytes_written; + } else if (!bytes_written) + ++retry; + else { + count -= bytes_written; + total += bytes_written; + } + } while (count && retry < 3); + if (count) + Eprintf("Failed to complete writing to %s after three retries." + "\n", vol->dev_name); + return total; +} + +/* + * Write to disk the clusters contained in the run list @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. + */ +s64 ntfs_rlwrite(int fd, const run_list *rl, const char *val, + const s64 val_len, s64 *inited_size) +{ + s64 bytes_written, total, length, delta; + int retry, i; + + if (inited_size) + *inited_size = 0LL; + if (opt.no_action) + return val_len; + total = delta = 0LL; + for (i = 0; rl[i].length; i++) { + length = rl[i].length * 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; + } + if (lseek(fd, rl[i].lcn * vol->cluster_size, SEEK_SET) == + (off_t)-1) + return -1LL; + retry = 0; + do { + if (total + length > val_len) { + delta = length; + length = val_len - total; + delta -= length; + } + bytes_written = write(fd, val + total, length); + if (bytes_written == -1LL) { + retry = errno; + Eprintf("Error writing to %s: %s\n", + vol->dev_name, strerror(errno)); + 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) { + Eprintf("Failed to complete writing to %s after three " + "retries.\n", vol->dev_name); + return total; + } + } + if (delta) { + char *buf = (char*)calloc(1, delta); + if (!buf) + err_exit("Error allocating internal buffer: " + "%s\n", strerror(errno)); + bytes_written = mkntfs_write(fd, buf, delta); + free(buf); + if (bytes_written == -1LL) + return bytes_written; + } + return total; +} + +/** + * ucslen - determine the length of a fixed-size unicode-character string + * @s: pointer to unicode-character string + * + * Return the number of unicode-characters in @s up to a maximum of maxlen + * unicode-characters, not including the terminating (uchar_t)'\0'. If there + * is no (uchar_t)'\0' between s and s+maxlen, maxlen is returned. + * + * This function never looks beyond s+maxlen. + */ +int ucsnlen(const uchar_t *s, int maxlen) +{ + int i; + + for (i = 0; i < maxlen; i++) + if (!s[i]) + break; + return i; +} + +/** + * ucstos - convert unicode-character string to ASCII + * @dest: points to buffer to receive the converted string + * @src: points to string to convert + * @maxlen: size of @dest buffer in bytes + * + * Return the number of characters written to @dest, not including the + * terminating null byte. If a unicode character was encountered which could + * not be converted -1 is returned. + */ +int ucstos(char *dest, const uchar_t *src, int maxlen) +{ + uchar_t u; + int i; + + /* Need one byte for null terminator. */ + maxlen--; + for (i = 0; i < maxlen; i++) { + u = le16_to_cpu(src[i]); + if (!u) + break; + if (u & 0xff00) + return -1; + dest[i] = u & 0xff; + } + dest[i] = 0; + return i; +} + +/** + * stoucs - convert ASCII string to unicode-character string + * @dest: points to buffer to receive the converted string + * @src: points to string to convert + * @maxlen: size of @dest buffer in bytes + * + * Return the number of characters written to @dest, not including the + * terminating null unicode character. + */ +int stoucs(uchar_t *dest, const char *src, int maxlen) +{ + char c; + int i; + + /* Need two bytes for null terminator. */ + maxlen -= 2; + for (i = 0; i < maxlen; i++) { + c = src[i]; + if (!c) + break; + dest[i] = cpu_to_le16(c); + } + dest[i] = cpu_to_le16('\0'); + return i; +} + +void dump_resident_attr_val(ATTR_TYPES type, char *val, u32 val_len) +{ + const char *don_t_know = "Don't know what to do with this attribute " + "type yet."; + const char *skip = "Skipping display of $%s attribute value.\n"; + const char *todo = "This is still work in progress."; + char *buf; + int i, j; + + switch (type) { + case AT_STANDARD_INFORMATION: + // TODO + printf("%s\n", todo); + return; + case AT_ATTRIBUTE_LIST: + // TODO + printf("%s\n", todo); + return; + case AT_FILE_NAME: + // TODO + printf("%s\n", todo); + return; + case AT_OBJECT_ID: + // TODO + printf("%s\n", todo); + return; + case AT_SECURITY_DESCRIPTOR: + // TODO + printf("%s\n", todo); + return; + case AT_VOLUME_NAME: + printf("Volume name length = %i\n", val_len); + if (val_len) { + buf = calloc(1, val_len); + if (!buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(buf, (uchar_t*)val, val_len); + if (i == -1) + printf("Volume name contains non-displayable " + "Unicode characters.\n"); + printf("Volume name = %s\n", buf); + free(buf); + } + return; + case AT_VOLUME_INFORMATION: +#define VOL_INF(x) ((VOLUME_INFORMATION *)(x)) + printf("NTFS version %i.%i\n", VOL_INF(val)->major_ver, + VOL_INF(val)->minor_ver); + i = VOL_INF(val)->flags; +#undef VOL_INF + printf("Volume flags = 0x%x: ", i); + if (!i) { + printf("NONE\n"); + return; + } + j = 0; + if (i & VOLUME_MODIFIED_BY_CHKDSK) { + j = 1; + printf("VOLUME_MODIFIED_BY_CHKDSK"); + } + if (i & VOLUME_REPAIR_OBJECT_ID) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_REPAIR_OBJECT_ID"); + } + if (i & VOLUME_DELETE_USN_UNDERWAY) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_DELETE_USN_UNDERWAY"); + } + if (i & VOLUME_MOUNTED_ON_NT4) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_MOUNTED_ON_NT4"); + } + if (i & VOLUME_UPGRADE_ON_MOUNT) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_UPGRADE_ON_MOUNT"); + } + if (i & VOLUME_RESIZE_LOG_FILE) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_RESIZE_LOG_FILE"); + } + if (i & VOLUME_IS_DIRTY) { + if (j) + printf(" | "); + else + j = 0; + printf("VOLUME_IS_DIRTY"); + } + printf("\n"); + return; + case AT_DATA: + printf(skip, "DATA"); + return; + case AT_INDEX_ROOT: + // TODO + printf("%s\n", todo); + return; + case AT_INDEX_ALLOCATION: + // TODO + printf("%s\n", todo); + return; + case AT_BITMAP: + printf(skip, "BITMAP"); + return; + case AT_REPARSE_POINT: + // TODO + printf("%s\n", todo); + return; + case AT_EA_INFORMATION: + // TODO + printf("%s\n", don_t_know); + return; + case AT_EA: + // TODO + printf("%s\n", don_t_know); + return; + case AT_LOGGED_UTILITY_STREAM: + // TODO + printf("%s\n", don_t_know); + return; + default: + i = le32_to_cpu(type); + printf("Cannot display unknown %s defined attribute type 0x%x" + ".\n", i >= + le32_to_cpu(AT_FIRST_USER_DEFINED_ATTRIBUTE) ? + "user" : "system", i); + } +} + +void dump_resident_attr(ATTR_RECORD *a) +{ + int i; + + i = le32_to_cpu(a->value_length); + printf("Attribute value length = %u (0x%x)\n", i, i); + i = le16_to_cpu(a->value_offset); + printf("Attribute value offset = %u (0x%x)\n", i, i); + i = a->resident_flags; + printf("Resident flags = 0x%x: ", i); + if (!i) + printf("NONE\n"); + else if (i & ~RESIDENT_ATTR_IS_INDEXED) + printf("UNKNOWN FLAG(S)\n"); + else + printf("RESIDENT_ATTR_IS_INDEXED\n"); + dump_resident_attr_val(a->type, (char*)a + le16_to_cpu(a->value_offset), + le32_to_cpu(a->value_length)); +} + +void dump_mapping_pairs_array(char *b, unsigned int max_len) +{ + // TODO + return; +} + +void dump_non_resident_attr(ATTR_RECORD *a) +{ + s64 l; + int i; + + l = sle64_to_cpu(a->lowest_vcn); + printf("Lowest VCN = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->highest_vcn); + printf("Highest VCN = %Li (0x%Lx)\n", l, l); + printf("Mapping pairs array offset = 0x%x\n", + le16_to_cpu(a->mapping_pairs_offset)); + printf("Compression unit = 0x%x: %sCOMPRESSED\n", a->compression_unit, + a->compression_unit ? "" : "NOT "); + if (sle64_to_cpu(a->lowest_vcn)) + printf("Attribute is not the first extent. The following " + "sizes are meaningless:\n"); + l = sle64_to_cpu(a->allocated_size); + printf("Allocated size = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->data_size); + printf("Data size = %Li (0x%Lx)\n", l, l); + l = sle64_to_cpu(a->initialized_size); + printf("Initialized size = %Li (0x%Lx)\n", l, l); + if (a->flags & ATTR_COMPRESSION_MASK) { + l = sle64_to_cpu(a->compressed_size); + printf("Compressed size = %Li (0x%Lx)\n", l, l); + } + i = le16_to_cpu(a->mapping_pairs_offset); + dump_mapping_pairs_array((char*)a + i, le32_to_cpu(a->length) - i); +} + +void dump_attr_record(ATTR_RECORD *a) +{ + unsigned int u; + char s[0x200]; + int i; + + printf("-- Beginning dump of attribute record. --\n"); + if (a->type == AT_END) { + printf("Attribute type = 0x%x ($END)\n", le32_to_cpu(AT_END)); + u = le32_to_cpu(a->length); + printf("Length of resident part = %u (0x%x)\n", u, u); + return; + } + u = le32_to_cpu(a->type); + for (i = 0; opt.attr_defs[i].type; i++) + if (le32_to_cpu(opt.attr_defs[i].type) >= u) + break; + if (opt.attr_defs[i].type) { +// printf("type = 0x%x\n", le32_to_cpu(opt.attr_defs[i].type)); +// { char *p = (char*)opt.attr_defs[i].name; +// printf("name = %c%c%c%c%c\n", *p, p[1], p[2], p[3], p[4]); +// } + if (ucstos(s, opt.attr_defs[i].name, sizeof(s)) == -1) { + Eprintf("Could not convert Unicode string to single " + "byte string in current locale.\n"); + strncpy(s, "Error converting Unicode string", + sizeof(s)); + } + } else + strncpy(s, "UNKNOWN_TYPE", sizeof(s)); + printf("Attribute type = 0x%x (%s)\n", u, s); + u = le32_to_cpu(a->length); + printf("Length of resident part = %u (0x%x)\n", u, u); + printf("Attribute is %sresident\n", a->non_resident ? "non-" : ""); + printf("Name length = %u unicode characters\n", a->name_length); + printf("Name offset = %u (0x%x)\n", cpu_to_le16(a->name_offset), + cpu_to_le16(a->name_offset)); + u = a->flags; + if (a->name_length) { + if (ucstos(s, (uchar_t*)((char*)a + + cpu_to_le16(a->name_offset)), + min(sizeof(s), a->name_length + 1)) == -1) { + Eprintf("Could not convert Unicode string to single " + "byte string in current locale.\n"); + strncpy(s, "Error converting Unicode string", + sizeof(s)); + + } + printf("Name = %s\n", s); + } + printf("Attribute flags = 0x%x: ", le16_to_cpu(u)); + if (!u) + printf("NONE"); + else { + int first = TRUE; + if (u & ATTR_COMPRESSION_MASK) { + if (u & ATTR_IS_COMPRESSED) { + printf("ATTR_IS_COMPRESSED"); + first = FALSE; + } + if ((u & ATTR_COMPRESSION_MASK) & ~ATTR_IS_COMPRESSED) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_UNKNOWN_COMPRESSION"); + } + } + if (u & ATTR_IS_ENCRYPTED) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_IS_ENCRYPTED"); + } + if (u & ATTR_IS_SPARSE) { + if (!first) + printf(" | "); + else + first = FALSE; + printf("ATTR_IS_SPARSE"); + } + } + printf("\n"); + printf("Attribute instance = %u\n", le16_to_cpu(a->instance)); + if (a->non_resident) { + dump_non_resident_attr(a); + } else { + dump_resident_attr(a); + } +} + +void dump_mft_record(MFT_RECORD *m) +{ + ATTR_RECORD *a; + unsigned int u; + MFT_REF r; + + printf("-- Beginning dump of mft record. --\n"); + u = le32_to_cpu(m->magic); + printf("Mft record signature (magic) = %c%c%c%c\n", u & 0xff, + u >> 8 & 0xff, u >> 16 & 0xff, u >> 24 & 0xff); + u = le16_to_cpu(m->usa_ofs); + printf("Update sequence array offset = %u (0x%x)\n", u, u); + printf("Update sequence array size = %u\n", le16_to_cpu(m->usa_count)); + printf("$LogFile sequence number (lsn) = %Lu\n", le64_to_cpu(m->lsn)); + printf("Sequence number = %u\n", le16_to_cpu(m->sequence_number)); + printf("Reference (hard link) count = %u\n", + le16_to_cpu(m->link_count)); + u = le16_to_cpu(m->attrs_offset); + printf("First attribute offset = %u (0x%x)\n", u, u); + printf("Flags = %u: ", le16_to_cpu(m->flags)); + if (m->flags & MFT_RECORD_IN_USE) + printf("MFT_RECORD_IN_USE"); + else + printf("MFT_RECORD_NOT_IN_USE"); + if (m->flags & MFT_RECORD_IS_DIRECTORY) + printf(" | MFT_RECORD_IS_DIRECTORY"); + printf("\n"); + u = le32_to_cpu(m->bytes_in_use); + printf("Bytes in use = %u (0x%x)\n", u, u); + u = le32_to_cpu(m->bytes_allocated); + printf("Bytes allocated = %u (0x%x)\n", u, u); + r = le64_to_cpu(m->base_mft_record); + printf("Base mft record reference:\n\tMft record number = %Lu\n\t" + "Sequence number = %u\n", MREF(r), MSEQNO(r)); + printf("Next attribute instance = %u\n", + le16_to_cpu(m->next_attr_instance)); + a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); + printf("-- Beginning dump of attributes within mft record. --\n"); + while ((char*)a < (char*)m + le32_to_cpu(m->bytes_in_use)) { + dump_attr_record(a); + if (a->type == AT_END) + break; + a = (ATTR_RECORD*)((char*)a + le32_to_cpu(a->length)); + }; + printf("-- End of attributes. --\n"); +} + +void format_mft_record(MFT_RECORD *m) +{ + ATTR_RECORD *a; + + memset(m, 0, vol->mft_record_size); + m->magic = magic_FILE; + /* Aligned to 2-byte boundary. */ + m->usa_ofs = cpu_to_le16((sizeof(MFT_RECORD) + 1) & ~1); + if (vol->mft_record_size >= NTFS_SECTOR_SIZE) + m->usa_count = cpu_to_le16(vol->mft_record_size / + NTFS_SECTOR_SIZE + 1); + else { + m->usa_count = cpu_to_le16(1); + Qprintf("Sector size is bigger than MFT record size. Setting " + "usa_count to 1. If Windows\nchkdsk reports this as " + "corruption, please email linux-ntfs-dev@lists.sf.net\n" + "stating that you saw this message and that the file " + "system created was corrupt.\nThank you."); + } + /* Set the update sequence number to 1. */ + *(u16*)((char*)m + ((sizeof(MFT_RECORD) + 1) & ~1)) = cpu_to_le16(1); + m->lsn = cpu_to_le64(0LL); + m->sequence_number = cpu_to_le16(1); + m->link_count = cpu_to_le16(0); + /* Aligned to 8-byte boundary. */ + m->attrs_offset = cpu_to_le16((le16_to_cpu(m->usa_ofs) + + (le16_to_cpu(m->usa_count) << 1) + 7) & ~7); + m->flags = cpu_to_le16(0); + /* + * Using attrs_offset plus eight bytes (for the termination attribute), + * aligned to 8-byte boundary. + */ + m->bytes_in_use = cpu_to_le32((le16_to_cpu(m->attrs_offset) + 8 + 7) & + ~7); + m->bytes_allocated = cpu_to_le32(vol->mft_record_size); + m->base_mft_record = cpu_to_le64((MFT_REF)0); + m->next_attr_instance = cpu_to_le16(0); + a = (ATTR_RECORD*)((char*)m + le16_to_cpu(m->attrs_offset)); + a->type = AT_END; + a->length = cpu_to_le32(0); +#if 0 + if (!opt.quiet && opt.verbose > 1) + dump_mft_record(m); +#endif +} + +/** + * 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 -DEBUG. Means + * the input parameters were faulty. + */ +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)) { + Eprintf("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 > 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; +} + +/* Return 0 on success and -errno on error. */ +int resize_resident_attribute_value(MFT_RECORD *m, ATTR_RECORD *a, + const u32 new_vsize) +{ + int new_alen, new_muse; + + /* New attribute length and mft record bytes used. */ + new_alen = (le32_to_cpu(a->length) - le32_to_cpu(a->value_length) + + new_vsize + 7) & ~7; + new_muse = le32_to_cpu(m->bytes_in_use) - le32_to_cpu(a->length) + + new_alen; + /* Check for sufficient space. */ + if (new_muse > le32_to_cpu(m->bytes_allocated) ) { + // Aarrgghh! Need to make space. Probably want generic function + // for this as we need to call it from other places, too. + return -ENOTSUP; + } + /* Move attributes behind @a to their new location. */ + memmove((char*)a + new_alen, (char*)a + le32_to_cpu(a->length), + le32_to_cpu(m->bytes_in_use) - ((char*)a - (char*)m) - + le32_to_cpu(a->length)); + /* Adjust @m to reflect change in used space. */ + m->bytes_in_use = cpu_to_le32(new_muse); + /* Adjust @a to reflect new value size. */ + a->length = cpu_to_le32(new_alen); + a->value_length = cpu_to_le32(new_vsize); + return 0; +} + +void deallocate_scattered_clusters(const run_list *rl) +{ + LCN j; + int i; + + if (!rl) + return; + /* Iterate over all runs in the run list @rl. */ + for (i = 0; rl[i].length; i++) { + /* Skip sparse runs. */ + if (rl[i].lcn == -1LL) + continue; + /* Deallocate the current run. */ + for (j = rl[i].lcn; j < rl[i].lcn + rl[i].length; j++) + ntfs_set_bit(lcn_bitmap, j, 0); + } +} + +/* + * Allocate @clusters and create a run list of the allocated clusters. + * + * Return the allocated run list. Caller has to free the run list 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. + */ +run_list *allocate_scattered_clusters(s64 clusters) +{ + run_list *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 = opt.nr_clusters; + /* Loop until all clusters are allocated. */ + while (clusters) { + /* Loop in current zone until we run out of free clusters. */ + for (lcn = opt.mft_zone_end; lcn < end; lcn++) { + bit = ntfs_get_and_set_bit(lcn_bitmap, lcn, 1); + if (bit) + continue; + /* + * Reallocate memory if necessary. Make sure we have + * enough for the terminator entry as well. + */ + if ((rlpos + 2) * sizeof(run_list) >= 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 = prev_lcn = lcn; + rl[rlpos].length = prev_run_len = 1LL; + rlpos++; + } + /* Done? */ + if (!--clusters) { + /* Add terminator element and return. */ + rl[rlpos].vcn = vcn; + rl[rlpos].lcn = rl[rlpos].length = 0LL; + return rl; + } + + } + /* Switch to next zone, decreasing mft zone by factor 2. */ + end = opt.mft_zone_end; + opt.mft_zone_end >>= 1; + /* Have we run out of space on the volume? */ + if (opt.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 run list. */ + free(rl); + } + return NULL; +} + +/* + * Create a non-resident attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success and -errno on error. + */ +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 run_list *rl, + const char *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; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = NULL; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #1.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, NULL, 0, ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + if (flags & ATTR_COMPRESSION_MASK) { + Eprintf("Compressed attributes not supported yet.\n"); + // FIXME: Compress attribute into a temporary buffer, set + // val accordingly and save the compressed size. + err = -ENOTSUP; + goto err_out; + } + if (flags & (ATTR_IS_ENCRYPTED || ATTR_IS_SPARSE)) { + Eprintf("Encrypted/sparse attributes not supported yet.\n"); + err = -ENOTSUP; + 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(vol, rl); + if (mpa_size < 0) { + err = -errno; + Eprintf("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 * vol->cluster_size < val_len) { + Eprintf("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 run_list 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 = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + else if (err == -EINVAL) { + fprintf(stderr, "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_le64(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_le64(highest_vcn * vol->cluster_size); + a->data_size = cpu_to_le64(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) { + Eprintf("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 = -ENOTSUP; + } else { + a->compression_unit = 0; + bw = ntfs_rlwrite(vol->fd, rl, val, val_len, &inited_size); + if (bw != val_len) + Eprintf("Error writing non-resident attribute value." + "\n"); + err = ntfs_build_mapping_pairs(vol, (s8*)a + hdr_size + + ((name_len + 7) & ~7), mpa_size, rl); + } + a->initialized_size = cpu_to_le64(inited_size); + if (err < 0 || bw != val_len) { + // FIXME: Handle error. + // deallocate clusters + // remove attribute + if (err >= 0) + err = -EIO; + Eprintf("insert_positioned_attr_in_mft_record failed with " + "error %i.\n", err < 0 ? err : bw); + } +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (uname) + free(uname); + return err; +} + +/* Return 0 on success and -errno on error. */ +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 char *val, const s64 val_len) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + u16 hdr_size; + int asize, mpa_size, err, i; + run_list *rl = NULL; + s64 bw = 0; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = AT_UNNAMED; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #2.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, NULL, 0, ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + if (flags & ATTR_COMPRESSION_MASK) { + Eprintf("Compressed attributes not supported yet.\n"); + // FIXME: Compress attribute into a temporary buffer, set + // val accordingly and save the compressed size. + err = -ENOTSUP; + goto err_out; + } + if (flags & (ATTR_IS_ENCRYPTED || ATTR_IS_SPARSE)) { + Eprintf("Encrypted/sparse attributes not supported yet.\n"); + err = -ENOTSUP; + goto err_out; + } + if (val_len) { + rl = allocate_scattered_clusters((val_len + + vol->cluster_size - 1) / vol->cluster_size); + if (!rl) { + err = -errno; + Eprintf("Failed to allocate scattered clusters: %s\n", + strerror(-err)); + 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(vol, rl); + if (mpa_size < 0) { + err = -errno; + Eprintf("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 run_list 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 = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + else if (err == -EINVAL) { + fprintf(stderr, "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_le64(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_le64((val_len + (vol->cluster_size - 1)) & + ~(vol->cluster_size - 1)); + a->data_size = cpu_to_le64(val_len); + a->initialized_size = cpu_to_le64(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) { + Eprintf("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 = -ENOTSUP; + } else { + a->compression_unit = 0; + bw = ntfs_rlwrite(vol->fd, rl, val, val_len, NULL); + if (bw != val_len) + Eprintf("Error writing non-resident attribute value." + "\n"); + err = ntfs_build_mapping_pairs(vol, (s8*)a + hdr_size + + ((name_len + 7) & ~7), mpa_size, rl); + } + if (err < 0 || bw != val_len) { + // FIXME: Handle error. + // deallocate clusters + // remove attribute + if (err >= 0) + err = -EIO; + Eprintf("insert_non_resident_attr_in_mft_record failed with " + "error %i.\n", err < 0 ? err : bw); + } +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (uname && (uname != AT_UNNAMED)) + free(uname); + if (rl) + free(rl); + return err; +} + +/* Return 0 on success and -errno on error. */ +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 char *val, const u32 val_len) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + int asize, err, i; + uchar_t *uname; +/* + if (base record) + lookup_attr(); + else +*/ + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + name_len = stoucs(uname, name, i); + if (name_len > 0xff) + return -ENAMETOOLONG; + } else + uname = AT_UNNAMED; + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + err = -ENOMEM; + goto err_out; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #3.\n"); + err = -ENOTSUP; + goto err_out; + } + if (!ntfs_lookup_attr(type, uname, name_len, ic, 0, val, val_len, + ctx)) { + err = -EEXIST; + goto err_out; + } + if (errno != ENOENT) { + Eprintf("Corrupt inode.\n"); + err = -errno; + goto err_out; + } + a = ctx->attr; + /* sizeof(resident attribute record header) == 24 */ + asize = ((24 + ((name_len + 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 run_list 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 = -ENOTSUP; + goto err_out; + } +#ifdef DEBUG + if (err == -EINVAL) { + fprintf(stderr, "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; + a->name_offset = cpu_to_le16(24); + a->flags = cpu_to_le16(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 + 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_put_attr_search_ctx(ctx); + if (uname && (uname != AT_UNNAMED)) + free(uname); + return err; +} + +s64 time2ntfs(s64 time) +{ + return cpu_to_le64((time + (s64)(369 * 365 + 89) * 24 * 3600) + * 10000000); +} + +/* Return 0 on success or -errno on error. */ +int add_attr_std_info(MFT_RECORD *m, const FILE_ATTR_FLAGS flags) +{ + STANDARD_INFORMATION si; + int err; + + si.creation_time = time2ntfs(time(NULL)); + 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 */ + if (vol->major_ver < 3) + memset(&si.reserved12, 0, sizeof(si.reserved12)); + else { + si.maximum_versions = cpu_to_le32(0); + si.version_number = cpu_to_le32(0); + si.class_id = cpu_to_le32(0); + /* FIXME: $Secure support... */ + si.security_id = cpu_to_le32(0); + /* FIXME: $Quota support... */ + si.owner_id = cpu_to_le32(0); + si.quota_charged = cpu_to_le64(0ULL); + /* FIXME: $UsnJrnl support... */ + si.usn = cpu_to_le64(0ULL); + } + /* NTFS 1.2: size of si = 48, NTFS 3.0: size of si = 72 */ + err = insert_resident_attr_in_mft_record(m, AT_STANDARD_INFORMATION, + NULL, 0, 0, 0, 0, (char*)&si, + vol->major_ver < 3 ? 48 : 72); + if (err < 0) + Eprintf("add_attr_std_info failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_file_name(MFT_RECORD *m, const MFT_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; + + /* Check if the attribute is already there. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + return -ENOMEM; + } + if (ntfs_lookup_attr(AT_STANDARD_INFORMATION, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + int eo = errno; + Eprintf("BUG: Standard information attribute not present in " + "file record\n"); + ntfs_put_attr_search_ctx(ctx); + return -eo; + } + si = (STANDARD_INFORMATION*)((char*)ctx->attr + + le16_to_cpu(ctx->attr->value_offset)); + i = (strlen(file_name) + 1) * sizeof(uchar_t); + fn_size = sizeof(FILE_NAME_ATTR) + i; + fn = (FILE_NAME_ATTR*)malloc(fn_size); + if (!fn) { + ntfs_put_attr_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_put_attr_search_ctx(ctx); + + fn->allocated_size = cpu_to_le64(allocated_size); + fn->data_size = cpu_to_le64(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; + i = stoucs(fn->file_name, file_name, 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(uchar_t); + i = insert_resident_attr_in_mft_record(m, AT_FILE_NAME, NULL, 0, 0, + 0, RESIDENT_ATTR_IS_INDEXED, (char*)fn, fn_size); + free(fn); + if (i < 0) + Eprintf("add_attr_file_name failed: %s\n", strerror(-i)); + return i; +} + +/* + * 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. + */ +int add_attr_sd(MFT_RECORD *m, const char *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, 0, 0, sd, + sd_len); + else + err = insert_resident_attr_in_mft_record(m, + AT_SECURITY_DESCRIPTOR, NULL, 0, 0, 0, 0, sd, + sd_len); + if (err < 0) + Eprintf("add_attr_sd failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_data(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const ATTR_FLAGS flags, + const char *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); + else + err = insert_resident_attr_in_mft_record(m, AT_DATA, name, + name_len, ic, flags, 0, val, val_len); + + if (err < 0) + Eprintf("add_attr_data failed: %s\n", strerror(-err)); + return err; +} + +/* + * Create a non-resident data attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success or -errno on error. + */ +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 run_list *rl, + const char *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) + Eprintf("add_attr_data_positioned failed: %s\n", + strerror(-err)); + return err; +} + +/* + * 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 uchar_t string. The name is at least 1 character long and at most + * 0xff characters long (not counting the terminating null). + * + * Return 0 on success or -errno on error. + */ +int add_attr_vol_name(MFT_RECORD *m, const char *vol_name, + const int vol_name_len) +{ + uchar_t *uname; + int i, len; + + if (vol_name_len) { + len = (vol_name_len + 1) * sizeof(uchar_t); + uname = calloc(1, len); + if (!uname) + return -errno; + i = (stoucs(uname, vol_name, len) + 1) * sizeof(uchar_t); + if (!i) { + free(uname); + return -EINVAL; + } + if (i > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else { + uname = NULL; + len = 0; + } + i = insert_resident_attr_in_mft_record(m, AT_VOLUME_NAME, NULL, 0, 0, + 0, 0, (char*)uname, len); + if (uname) + free(uname); + if (i < 0) + Eprintf("add_attr_vol_name failed: %s\n", strerror(-i)); + return i; +} + +/* Return 0 on success or -errno on error. */ +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, 0, 0, 0, (char*)&vi, sizeof(vi)); + if (err < 0) + Eprintf("add_attr_vol_info failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +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 = (INDEX_ROOT*)malloc(val_len); + if (!r) + return -errno; + r->type = indexed_attr_type == AT_FILE_NAME ? AT_FILE_NAME : 0; + if (indexed_attr_type == AT_FILE_NAME && + collation_rule != COLLATION_FILE_NAME) { + free(r); + Eprintf("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 >= vol->cluster_size) { + if (index_block_size % vol->cluster_size) { + Eprintf("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 / + vol->cluster_size; + } else /* if (vol->cluster_size > index_block_size) */ { + if (index_block_size & (index_block_size - 1)) { + Eprintf("add_attr_index_root: index block size is not " + "a power of 2.\n"); + free(r); + return -EINVAL; + } + if (index_block_size < opt.sector_size) { + Eprintf("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 / + opt.sector_size; + } + memset(&r->reserved, 0, sizeof(r->reserved)); + r->index.entries_offset = cpu_to_le32(sizeof(INDEX_HEADER)); + r->index.index_length = cpu_to_le32(sizeof(INDEX_HEADER) + + sizeof(INDEX_ENTRY_HEADER)); + r->index.allocated_size = r->index.index_length; + r->index.flags = SMALL_INDEX; + memset(&r->index.reserved, 0, sizeof(r->index.reserved)); + e = (INDEX_ENTRY_HEADER*)((char*)&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 = cpu_to_le64(0LL); + e->length = cpu_to_le16(sizeof(INDEX_ENTRY_HEADER)); + e->key_length = cpu_to_le16(0); + e->flags = INDEX_ENTRY_END; + e->reserved = cpu_to_le16(0); + err = insert_resident_attr_in_mft_record(m, AT_INDEX_ROOT, name, + name_len, ic, 0, 0, (char*)r, val_len); + free(r); + if (err < 0) + Eprintf("add_attr_index_root failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_index_alloc(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const char *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, 0, index_alloc_val, + index_alloc_val_len); + if (err < 0) + Eprintf("add_attr_index_alloc failed: %s\n", strerror(-err)); + return err; +} + +/* Return 0 on success or -errno on error. */ +int add_attr_bitmap(MFT_RECORD *m, const char *name, const u32 name_len, + const IGNORE_CASE_BOOL ic, const char *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, 0, bitmap, bitmap_len); + else + err = insert_resident_attr_in_mft_record(m, AT_BITMAP, name, + name_len, ic, 0, 0, bitmap, bitmap_len); + + if (err < 0) + Eprintf("add_attr_bitmap failed: %s\n", strerror(-err)); + return err; +} + +/* + * Create a non-resident bitmap attribute with a predefined on disk location + * specified by the run_list @rl. The clusters specified by @rl are assumed to + * be allocated already. + * + * Return 0 on success or -errno on error. + */ +int add_attr_bitmap_positioned(MFT_RECORD *m, const char *name, + const u32 name_len, const IGNORE_CASE_BOOL ic, + const run_list *rl, const char *bitmap, const u32 bitmap_len) +{ + int err; + + err = insert_positioned_attr_in_mft_record(m, AT_BITMAP, name, name_len, + ic, 0, rl, bitmap, bitmap_len); + if (err < 0) + Eprintf("add_attr_bitmap_positioned failed: %s\n", + strerror(-err)); + return err; +} + +/* + * 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. + */ +int upgrade_to_large_index(MFT_RECORD *m, const char *name, + u32 name_len, const IGNORE_CASE_BOOL ic, + INDEX_ALLOCATION **index) +{ + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + INDEX_ROOT *r; + INDEX_ENTRY *re; + INDEX_ALLOCATION *ia_val = NULL; + uchar_t *uname; + char bmp[8]; + char *re_start, *re_end; + int i, err, index_block_size; + + if (name_len) { + i = (name_len + 1) * sizeof(uchar_t); + uname = (uchar_t*)calloc(1, i); + if (!uname) + return -errno; + name_len = stoucs(uname, name, i); + if (name_len > 0xff) { + free(uname); + return -ENAMETOOLONG; + } + } else + uname = NULL; + /* Find the index root attribute. */ + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) { + Eprintf("Failed to allocate attribute search context.\n"); + return -ENOMEM; + } + if (ic == IGNORE_CASE) { + Eprintf("FIXME: Hit unimplemented code path #4.\n"); + err = -ENOTSUP; + goto err_out; + } + err = ntfs_lookup_attr(AT_INDEX_ROOT, uname, name_len, ic, 0, NULL, 0, + ctx); + if (uname) + free(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_set_bit(bmp, 0ULL, 1); + /* Bitmap has to be at least 8 bytes in size. */ + err = add_attr_bitmap(m, name, name_len, ic, (char*)&bmp, sizeof(bmp)); + if (err) + goto err_out; + ia_val = calloc(1, 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_SECTOR_SIZE) + ia_val->usa_count = cpu_to_le16(index_block_size / + NTFS_SECTOR_SIZE + 1); + else { + ia_val->usa_count = cpu_to_le16(1); + Qprintf("Sector size is bigger than index block size. Setting " + "usa_count to 1. If Windows\nchkdsk reports this as " + "corruption, please email linux-ntfs-dev@lists.sf.net\n" + "stating that you saw this message and that the file " + "system created was corrupt.\nThank you."); + } + /* Set USN to 1. */ + *(u16*)((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.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->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.flags = LARGE_INDEX; + re->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. */ + err = resize_resident_attribute_value(m, a, sizeof(INDEX_ROOT) - + sizeof(INDEX_HEADER) + + le32_to_cpu(r->index.allocated_size)); + if (err) { + // TODO: Remove the added bitmap! + // Revert index root from index allocation. + goto err_out; + } + /* Set VCN pointer to 0LL. */ + *(VCN*)((char*)re + cpu_to_le16(re->length) - sizeof(VCN)) = + cpu_to_le64(0); + err = ntfs_pre_write_mst_fixup((NTFS_RECORD*)ia_val, index_block_size); + if (err) { + err = -errno; + Eprintf("ntfs_pre_write_mst_fixup() failed in " + "upgrade_to_large_index.\n"); + goto err_out; + } + err = add_attr_index_alloc(m, name, name_len, ic, (char*)ia_val, + index_block_size); + ntfs_post_write_mst_fixup((NTFS_RECORD*)ia_val); + if (err) { + // TODO: Remove the added bitmap! + // Revert index root from index allocation. + goto err_out; + } + *index = ia_val; + return 0; +err_out: + if (ctx) + ntfs_put_attr_search_ctx(ctx); + if (ia_val) + free(ia_val); + return err; +} + +/* + * Create space of @size bytes at position @pos inside the index block @index. + * + * Return 0 on success or -errno on error. + */ +int make_room_for_index_entry_in_index_block(INDEX_BLOCK *index, + 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)) { + Eprintf("make_room_for_index_entry_in_index_block() received " + "non 8-byte aligned size.\n"); + return -EINVAL; + } + if (!index || !pos) + return -EINVAL; + if ((char*)pos < (char*)index || (char*)pos + size < (char*)index || + (char*)pos > (char*)index + sizeof(INDEX_BLOCK) - + sizeof(INDEX_HEADER) + + le32_to_cpu(index->index.allocated_size) || + (char*)pos + size > (char*)index + sizeof(INDEX_BLOCK) - + sizeof(INDEX_HEADER) + + le32_to_cpu(index->index.allocated_size)) + return -EINVAL; + /* The - sizeof(INDEX_ENTRY_HEADER) is for the index terminator. */ + if ((char*)pos - (char*)&index->index > + le32_to_cpu(index->index.index_length) + - sizeof(INDEX_ENTRY_HEADER)) + return -EINVAL; +#endif + biu = le32_to_cpu(index->index.index_length); + /* Do we have enough space? */ + if (biu + size > le32_to_cpu(index->index.allocated_size)) + return -ENOSPC; + /* Move everything after pos to pos + size. */ + memmove((char*)pos + size, (char*)pos, biu - ((char*)pos - + (char*)&index->index)); + /* Update index block. */ + index->index.index_length = cpu_to_le32(biu + size); + return 0; +} + +/* + * Insert the fully completed FILE_NAME_ATTR @file_name which is inside + * the file with mft reference @file_ref into the index (allocation) block + * @index (which belongs to @file_ref's parent directory). + * + * Return 0 on success or -errno on error. + */ +int insert_file_link_in_dir_index(INDEX_BLOCK *index, MFT_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 @index 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*)&index->index + + le32_to_cpu(index->index.index_length); + ie = (INDEX_ENTRY*)((char*)&index->index + + le32_to_cpu(index->index.entries_offset)); + /* + * Loop until we exceed valid memory (corruption case) or until we + * reach the last entry. + */ + while ((char*)ie < index_end && !(ie->flags & INDEX_ENTRY_END)) { +/* +#ifdef DEBUG + Dprintf("file_name_attr1->file_name_length = %i\n", + file_name->file_name_length); + if (file_name->file_name_length) { + char *__buf; + __buf = (char*)calloc(1, file_name->file_name_length + + 1); + if (!__buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(__buf, (uchar_t*)&file_name->file_name, + file_name->file_name_length + 1); + if (i == -1) + Dprintf("Name contains non-displayable " + "Unicode characters.\n"); + Dprintf("file_name_attr1->file_name = %s\n", __buf); + free(__buf); + } + Dprintf("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; + __buf = (char*)calloc(1, + ie->key.file_name.file_name_length + 1); + if (!__buf) + err_exit("Failed to allocate internal buffer: " + "%s\n", strerror(errno)); + i = ucstos(__buf, ie->key.file_name.file_name, + ie->key.file_name.file_name_length + 1); + if (i == -1) + Dprintf("Name contains non-displayable " + "Unicode characters.\n"); + Dprintf("file_name_attr2->file_name = %s\n", __buf); + free(__buf); + } +#endif +*/ + i = ntfs_file_compare_values(file_name, + (FILE_NAME_ATTR*)&ie->key.file_name, 1, + IGNORE_CASE, vol->upcase, 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_compare_values(file_name, + (FILE_NAME_ATTR*)&ie->key.file_name, 1, + CASE_SENSITIVE, vol->upcase, 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) { + Dprintf("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(index, ie, i); + if (err) { + Eprintf("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->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 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 @index 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. + */ +int create_hardlink(INDEX_BLOCK *index, const MFT_REF ref_parent, + MFT_RECORD *m_file, const MFT_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; + + /* Create the file_name attribute. */ + i = (strlen(file_name) + 1) * sizeof(uchar_t); + fn_size = sizeof(FILE_NAME_ATTR) + i; + fn = (FILE_NAME_ATTR*)malloc(fn_size); + if (!fn) + return -errno; + fn->parent_directory = ref_parent; + // FIXME: Is this correct? Or do we have to copy the creation_time + // from the std info? + fn->creation_time = time2ntfs(time(NULL)); + 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_le64(allocated_size); + fn->data_size = cpu_to_le64(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; + i = stoucs(fn->file_name, file_name, 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(uchar_t); + /* Increment the link count of @m_file. */ + i = le16_to_cpu(m_file->link_count); + if (i == 0xffff) { + Eprintf("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, 0, + 0, RESIDENT_ATTR_IS_INDEXED, (char*)fn, fn_size); + if (i < 0) { + Eprintf("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 @index. */ + i = insert_file_link_in_dir_index(index, ref_file, fn, fn_size); + if (i < 0) { + Eprintf("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; +} + +void init_options() +{ + memset(&opt, 0, sizeof(opt)); + opt.index_block_size = 4096; + opt.attr_defs = (ATTR_DEF*)&attrdef_ntfs12_array; + opt.attr_defs_len = sizeof(attrdef_ntfs12_array); + //Dprintf("Attr_defs table length = %u\n", opt.attr_defs_len); +} + +void usage(void) __attribute__ ((noreturn)); + +void usage(void) +{ + fprintf(stderr, "Copyright (c) 2001,2002 Anton Altaparmakov.\n" + "Create an NTFS volume on a user specified (block) device.\n" + "Usage: %s [-s sector-size] [-c cluster-size] " + "[-L volume-label]\n\t[-z mft-zone-multiplier] " + "[-fnqvvCFIQV] device [number-of-sectors]\n", + EXEC_NAME); + exit(1); +} + +void parse_options(int argc, char *argv[]) +{ + int c; + long l; + unsigned long u; + char *s; + +// Need to have: mft record size, index record size, ntfs version, mft size, +// logfile size, list of bad blocks, check for bad blocks, ... + if (argc && *argv) + EXEC_NAME = *argv; + fprintf(stderr, "%s v%s\n", EXEC_NAME, VERSION); + while ((c = getopt(argc, argv, "c:fnqs:vz:CFIL:QV")) != EOF) + switch (c) { + case 'n': + opt.no_action = 1; + break; + case 'c': + l = strtol(optarg, &s, 0); + if (!l || l > INT_MAX || *s) + err_exit("Invalid cluster size.\n"); + vol->cluster_size = l; + break; + case 'f': + case 'Q': + opt.quick_format = 1; + break; + case 'q': + opt.quiet = 1; + break; + case 's': + l = strtol(optarg, &s, 0); + if (!l || l > INT_MAX || *s) + err_exit("Invalid sector size.\n"); + opt.sector_size = l; + break; + case 'v': + opt.verbose++; + break; + case 'z': + l = strtol(optarg, &s, 0); + if (l < 1 || l > 4 || *s) + err_exit("Invalid MFT zone multiplier.\n"); + opt.mft_zone_multiplier = l; + break; + case 'C': + opt.enable_compression = 1; + break; + case 'F': + opt.force = 1; + break; + case 'I': + opt.disable_indexing = 1; + break; + case 'L': + vol->vol_name = optarg; + break; + case 'V': + /* Version number already printed, so just exit. */ + exit(0); + default: + usage(); + } + if (optind == argc) + usage(); + vol->dev_name = argv[optind++]; + if (optind < argc) { + u = strtoul(argv[optind++], &s, 0); + if (*s || !u || (u >= ULONG_MAX && errno == ERANGE)) + err_exit("Invalid number of sectors: %s\n", + argv[optind - 1]); + opt.nr_sectors = u; + } + if (optind < argc) + usage(); +} + +void mkntfs_exit(void) +{ + int err; + + if (index_block) + free(index_block); + if (buf) + free(buf); + if (buf2) + free(buf2); + if (lcn_bitmap) + free(lcn_bitmap); + if (mft_bitmap) + free(mft_bitmap); + if (rl) + free(rl); + if (rl_mft) + free(rl_mft); + if (rl_mft_bmp) + free(rl_mft_bmp); + if (rl_mftmirr) + free(rl_mftmirr); + if (rl_logfile) + free(rl_logfile); + if (rl_boot) + free(rl_boot); + if (rl_bad) + free(rl_bad); + if (rl_index) + free(rl_index); + if (opt.bad_blocks) + free(opt.bad_blocks); + if (opt.attr_defs != (ATTR_DEF*)attrdef_ntfs12_array) + free(opt.attr_defs); + if (vol->upcase) + free(vol->upcase); + flk.l_type = F_UNLCK; + err = fcntl(vol->fd, F_SETLK, &flk); + if (err == -1) + Eprintf("Warning: Could not unlock %s: %s\n", vol->dev_name, + strerror(errno)); + err = close(vol->fd); + if (err == -1) + Eprintf("Warning: Could not close %s: %s\n", vol->dev_name, + strerror(errno)); + if (vol) + free(vol); +} + +#define MAKE_MFT_REF(_ref, _seqno) cpu_to_le64((((u64)(_seqno)) << 48) \ + | ((u64)(_ref))) + +static inline int valid_offset(int f, long long ofs) +{ + char ch; + + if (lseek(f, ofs, SEEK_SET) >= 0 && read(f, &ch, 1) == 1) + return 1; + return 0; +} + +/* + * Returns the number of bs sized blocks in a partition. Adapted from + * e2fsutils-1.19, Copyright (C) 1995 Theodore Ts'o. + */ +long long get_device_size(int f, int bs) +{ + long long high, low; +#ifdef BLKGETSIZE + long size; + + if (ioctl(f, BLKGETSIZE, &size) >= 0) { + Dprintf("BLKGETSIZE nr 512 byte blocks = %ld (0x%ld)\n", size, + size); + return (long long)size * 512 / bs; + } +#endif +#ifdef FDGETPRM + { struct floppy_struct this_floppy; + + if (ioctl(f, FDGETPRM, &this_floppy) >= 0) { + Dprintf("FDGETPRM nr 512 byte blocks = %ld (0x%ld)\n", + this_floppy.size, this_floppy.size); + return (long long)this_floppy.size * 512 / bs; + } + } +#endif + /* + * We couldn't figure it out by using a specialized ioctl, + * so do binary search to find the size of the partition. + */ + low = 0LL; + for (high = 1024LL; valid_offset(f, high); high <<= 1) + low = high; + while (low < high - 1LL) { + const long long mid = (low + high) / 2; + + if (valid_offset(f, mid)) + low = mid; + else + high = mid; + } + lseek(f, 0LL, SEEK_SET); + return (low + 1LL) / bs; +} + +int main(int argc, char **argv) +{ + int i, j, err; + ssize_t bw; + struct stat sbuf; + long long lw, pos; + MFT_RECORD *m; + ATTR_RECORD *a; + MFT_REF root_ref; + ntfs_attr_search_ctx *ctx; + char *sd; + NTFS_BOOT_SECTOR *bs; + unsigned long mnt_flags; + + /* Initialize the random number generator with the current time. */ + srandom(time(NULL)); + /* Initialize ntfs_volume structure vol. */ + vol = (ntfs_volume*)calloc(1, sizeof(*vol)); + if (!vol) + err_exit("Could not allocate memory for internal buffer.\n"); + vol->major_ver = 1; + vol->minor_ver = 2; + vol->mft_record_size = 1024; + vol->mft_record_size_bits = 10; + /* Length is in unicode characters. */ + vol->upcase_len = 65536; + vol->upcase = (uchar_t*)malloc(vol->upcase_len * sizeof(uchar_t)); + if (!vol->upcase) + err_exit("Could not allocate memory for internal buffer.\n"); + init_upcase_table(vol->upcase, vol->upcase_len * sizeof(uchar_t)); + /* Initialize opt to zero / required values. */ + init_options(); + /* Parse command line options. */ + parse_options(argc, argv); + /* Verify we are dealing with a block device. */ + if (stat(vol->dev_name, &sbuf) == -1) { + if (errno == ENOENT) + err_exit("The device doesn't exist; did you specify " + "it correctly?\n"); + err_exit("Error getting information about %s: %s\n", + vol->dev_name, strerror(errno)); + } + if (!S_ISBLK(sbuf.st_mode)) { + Eprintf("%s is not a block device.\n", vol->dev_name); + if (!opt.force) + err_exit("Refusing to make a filesystem here!\n"); + if (!opt.nr_sectors) { + if (!sbuf.st_size && !sbuf.st_blocks) + err_exit("You must specify the number of " + "sectors.\n"); + if (opt.sector_size) { + if (sbuf.st_size) + opt.nr_sectors = sbuf.st_size / + opt.sector_size; + else + opt.nr_sectors = ((s64)sbuf.st_blocks + << 9) / opt.sector_size; + } else { + if (sbuf.st_size) + opt.nr_sectors = sbuf.st_size / 512; + else + opt.nr_sectors = sbuf.st_blocks; + opt.sector_size = 512; + } + } + fprintf(stderr, "mkntfs forced anyway.\n"); + } +#ifdef HAVE_LINUX_MAJOR_H + else if ((MAJOR(sbuf.st_rdev) == HD_MAJOR && + MINOR(sbuf.st_rdev) % 64 == 0) || + (SCSI_BLK_MAJOR(MAJOR(sbuf.st_rdev)) && + MINOR(sbuf.st_rdev) % 16 == 0)) { + err_exit("%s is entire device, not just one partition!\n", + vol->dev_name); + } +#endif + /* Make sure the file system is not mounted. */ + if (ntfs_check_if_mounted(vol->dev_name, &mnt_flags)) + Eprintf("Failed to determine whether %s is mounted: %s\n", + vol->dev_name, strerror(errno)); + else if (mnt_flags & NTFS_MF_MOUNTED) { + Eprintf("%s is mounted.\n", vol->dev_name); + if (!opt.force) + err_exit("Refusing to make a filesystem here!\n"); + fprintf(stderr, "mkntfs forced anyway. Hope /etc/mtab is " + "incorrect.\n"); + } + + /* Open the device for reading or reading and writing. */ + if (opt.no_action) { + Qprintf("Running in READ-ONLY mode!\n"); + i = O_RDONLY; + } else + i = O_RDWR; + vol->fd = open(vol->dev_name, i); + if (vol->fd == -1) + err_exit("Could not open %s: %s\n", vol->dev_name, + strerror(errno)); + /* Acquire exlusive (mandatory) write lock on the whole device. */ + memset(&flk, 0, sizeof(flk)); + if (opt.no_action) + flk.l_type = F_RDLCK; + else + flk.l_type = F_WRLCK; + flk.l_whence = SEEK_SET; + flk.l_start = flk.l_len = 0LL; + err = fcntl(vol->fd, F_SETLK, &flk); + if (err == -1) { + Eprintf("Could not lock %s for %s: %s\n", vol->dev_name, + opt.no_action ? "reading" : "writing", + strerror(errno)); + err = close(vol->fd); + if (err == -1) + Eprintf("Warning: Could not close %s: %s\n", + vol->dev_name, strerror(errno)); + exit(1); + } + /* Register our exit function which will unlock and close the device. */ + err = atexit(&mkntfs_exit); + if (err == -1) { + Eprintf("Could not set up exit() function because atexit() " + "failed. Aborting...\n"); + mkntfs_exit(); + exit(1); + } + /* If user didn't specify the sector size, determine it now. */ + if (!opt.sector_size) { +#ifdef BLKSSZGET + int _sect_size = 0; + + if (ioctl(vol->fd, BLKSSZGET, &_sect_size) >= 0) + opt.sector_size = _sect_size; + else +#endif + { + Eprintf("No sector size specified for %s and it could " + "not be obtained automatically.\n" + "Assuming sector size is 512 bytes.\n", + vol->dev_name); + opt.sector_size = 512; + } + } + /* Validate sector size. */ + if ((opt.sector_size - 1) & opt.sector_size || + opt.sector_size < 256 || opt.sector_size > 4096) + err_exit("Error: sector_size is invalid. It must be a power " + "of two, and it must be\n greater or equal 256 and " + "less than or equal 4096 bytes.\n"); + Dprintf("sector size = %i bytes\n", opt.sector_size); + /* If user didn't specify the number of sectors, determine it now. */ + if (!opt.nr_sectors) { + opt.nr_sectors = get_device_size(vol->fd, opt.sector_size); + if (opt.nr_sectors <= 0) + err_exit("get_device_size(%s) failed. Please specify " + "it manually.\n", vol->dev_name); + } + Dprintf("number of sectors = %Ld (0x%Lx)\n", opt.nr_sectors, + opt.nr_sectors); + /* Reserve the last sector for the backup boot sector. */ + opt.nr_sectors--; + /* If user didn't specify the volume size, determine it now. */ + if (!opt.volume_size) + opt.volume_size = opt.nr_sectors * opt.sector_size; + else if (opt.volume_size & (opt.sector_size - 1)) + err_exit("Error: volume_size is not a multiple of " + "sector_size.\n"); + /* Validate volume size. */ + if (opt.volume_size < 1 << 20 /* 1MiB */) + err_exit("Error: device is too small (%ikiB). Minimum NTFS " + "volume size is 1MiB.\n", opt.volume_size / 1024); + Dprintf("volume size = %LikiB\n", opt.volume_size / 1024); + /* If user didn't specify the cluster size, determine it now. */ + if (!vol->cluster_size) { + if (opt.volume_size <= 512LL << 20) /* <= 512MB */ + vol->cluster_size = 512; + else if (opt.volume_size <= 1LL << 30) /* ]512MB-1GB] */ + vol->cluster_size = 1024; + else if (opt.volume_size <= 2LL << 30) /* ]1GB-2GB] */ + vol->cluster_size = 2048; + else + vol->cluster_size = 4096; + /* For small volumes on devices with large sector sizes. */ + if (vol->cluster_size < opt.sector_size) + vol->cluster_size = opt.sector_size; + } + /* Validate cluster size. */ + if (vol->cluster_size & (vol->cluster_size - 1) || + vol->cluster_size < opt.sector_size || + vol->cluster_size > 128 * opt.sector_size || + vol->cluster_size > 65536) + err_exit("Error: cluster_size is invalid. It must be a power " + "of two, be at least\nthe same as sector_size, be " + "maximum 64kB, and the sectors per cluster value " + "has\nto fit inside eight bits. (We do not support " + "larger cluster sizes yet.)\n"); + vol->cluster_size_bits = ffs(vol->cluster_size) - 1; + Dprintf("cluster size = %i bytes\n", vol->cluster_size); + if (vol->cluster_size > 4096) { + if (opt.enable_compression) { + if (!opt.force) + err_exit("Error: cluster_size is above 4096 " + "bytes and compression is " + "requested.\nThis is not " + "possible due to limitations " + "in the compression algorithm " + "used by\nWindows.\n"); + opt.enable_compression = 0; + } + Qprintf("Warning: compression will be disabled on this volume " + "because it is not\nsupported when the cluster " + "size is above 4096 bytes. This is due to \n" + "limitations in the compression algorithm used " + "by Windows.\n"); + } + /* If user didn't specify the number of clusters, determine it now. */ + if (!opt.nr_clusters) + opt.nr_clusters = opt.volume_size / vol->cluster_size; + /* + * Check the cluster_size and nr_sectors for consistency with + * sector_size and nr_sectors. And check both of these for consistency + * with volume_size. + */ + if (opt.nr_clusters != (opt.nr_sectors * opt.sector_size) / + vol->cluster_size || + opt.volume_size / opt.sector_size != opt.nr_sectors || + opt.volume_size / vol->cluster_size != opt.nr_clusters) + err_exit("Illegal combination of volume/cluster/sector size " + "and/or cluster/sector number.\n"); + Dprintf("number of clusters = %Lu (0x%Lx)\n", opt.nr_clusters, + opt.nr_clusters); + /* Determine lcn bitmap byte size and allocate it. */ + lcn_bitmap_byte_size = (opt.nr_clusters + 7) >> 3; + /* Needs to be multiple of 8 bytes. */ + lcn_bitmap_byte_size = (lcn_bitmap_byte_size + 7) & ~7; + i = (lcn_bitmap_byte_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1); + Dprintf("lcn_bitmap_byte_size = %i, allocated = %i\n", + lcn_bitmap_byte_size, i); + lcn_bitmap = (unsigned char *)calloc(1, lcn_bitmap_byte_size); + if (!lcn_bitmap) + err_exit("Failed to allocate internal buffer: %s", + strerror(errno)); + /* + * $Bitmap can overlap the end of the volume. Any bits in this region + * must be set. This region also encompasses the backup boot sector. + */ + for (i = opt.nr_clusters; i < lcn_bitmap_byte_size << 3; i++) + ntfs_set_bit(lcn_bitmap, (u64)i, 1); + /* + * Determine mft_size: 16 mft records or 1 cluster, which ever is + * bigger, rounded to multiples of cluster size. + */ + opt.mft_size = (16 * vol->mft_record_size + vol->cluster_size - 1) + & ~(vol->cluster_size - 1); + Dprintf("MFT size = %i (0x%x) bytes\n", opt.mft_size, opt.mft_size); + /* Determine mft bitmap size and allocate it. */ + mft_bitmap_size = opt.mft_size / vol->mft_record_size; + /* Convert to bytes, at least one. */ + mft_bitmap_byte_size = (mft_bitmap_size + 7) >> 3; + /* Mft bitmap is allocated in multiples of 8 bytes. */ + mft_bitmap_byte_size = (mft_bitmap_byte_size + 7) & ~7; + Dprintf("mft_bitmap_size = %i, mft_bitmap_byte_size = %i\n", + mft_bitmap_size, mft_bitmap_byte_size); + mft_bitmap = (unsigned char *)calloc(1, mft_bitmap_byte_size); + if (!mft_bitmap) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + /* Create run list for mft bitmap. */ + rl_mft_bmp = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mft_bmp) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mft_bmp[0].vcn = 0LL; + /* Mft bitmap is right after $Boot's data. */ + j = (8192 + vol->cluster_size - 1) / vol->cluster_size; + rl_mft_bmp[0].lcn = j; + /* + * Size is always one cluster, even though valid data size and + * initialized data size are only 8 bytes. + */ + rl_mft_bmp[1].vcn = rl_mft_bmp[0].length = 1LL; + rl_mft_bmp[1].lcn = -1LL; + rl_mft_bmp[1].length = 0LL; + /* Allocate cluster for mft bitmap. */ + ntfs_set_bit(lcn_bitmap, (s64)j, 1); + /* If user didn't specify the mft lcn, determine it now. */ + if (!opt.mft_lcn) { + /* + * We start at the higher value out of 16kiB and just after the + * mft bitmap. + */ + opt.mft_lcn = rl_mft_bmp[0].lcn + rl_mft_bmp[0].length; + if (opt.mft_lcn * vol->cluster_size < 16 * 1024) + opt.mft_lcn = (16 * 1024 + vol->cluster_size - 1) / + vol->cluster_size; + } + Dprintf("$MFT logical cluster number = 0x%x\n", opt.mft_lcn); + /* Determine MFT zone size. */ + opt.mft_zone_end = opt.nr_clusters; + switch (opt.mft_zone_multiplier) { /* % of volume size in clusters */ + case 4: + opt.mft_zone_end = opt.mft_zone_end >> 1; /* 50% */ + break; + case 3: + opt.mft_zone_end = opt.mft_zone_end * 3 >> 3; /* 37.5% */ + break; + case 2: + opt.mft_zone_end = opt.mft_zone_end >> 2; /* 25% */ + break; + /* case 1: */ + default: + opt.mft_zone_end = opt.mft_zone_end >> 3; /* 12.5% */ + break; + } + Dprintf("MFT zone size = %lukiB\n", opt.mft_zone_end / 1024); + /* + * The mft zone begins with the mft data attribute, not at the beginning + * of the device. + */ + opt.mft_zone_end += opt.mft_lcn; + /* Create run list for mft. */ + rl_mft = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mft) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mft[0].vcn = 0LL; + rl_mft[0].lcn = opt.mft_lcn; + /* We already rounded mft size up to a cluster. */ + j = opt.mft_size / vol->cluster_size; + rl_mft[1].vcn = rl_mft[0].length = j; + rl_mft[1].lcn = -1LL; + rl_mft[1].length = 0LL; + /* Allocate clusters for mft. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.mft_lcn + i, 1); + /* Determine mftmirr_lcn (middle of volume). */ + opt.mftmirr_lcn = (opt.nr_sectors * opt.sector_size >> 1) + / vol->cluster_size; + Dprintf("$MFTMirr logical cluster number = 0x%x\n", opt.mftmirr_lcn); + /* Create run list for mft mirror. */ + rl_mftmirr = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_mftmirr) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_mftmirr[0].vcn = 0LL; + rl_mftmirr[0].lcn = opt.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 + * ther first four records (4kb) or the first cluster worth of records, + * whichever is bigger). + */ + j = (4 * vol->mft_record_size + vol->cluster_size - 1) / vol->cluster_size; + rl_mftmirr[1].vcn = rl_mftmirr[0].length = j; + rl_mftmirr[1].lcn = -1LL; + rl_mftmirr[1].length = 0LL; + /* Allocate clusters for mft mirror. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.mftmirr_lcn + i, 1); + opt.logfile_lcn = opt.mftmirr_lcn + j; + Dprintf("$LogFile logical cluster number = 0x%x\n", opt.logfile_lcn); + /* Create run list for log file. */ + rl_logfile = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_logfile) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_logfile[0].vcn = 0LL; + rl_logfile[0].lcn = opt.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 (opt.volume_size < 2048LL * 1024) /* < 2MiB */ + opt.logfile_size = 256LL * 1024; /* -> 256kiB */ + else if (opt.volume_size < 4000000LL) /* < 4MB */ + opt.logfile_size = 512LL * 1024; /* -> 512kiB */ + else if (opt.volume_size <= 200LL * 1024 * 1024)/* < 200MiB */ + opt.logfile_size = 2048LL * 1024; /* -> 2MiB */ + else if (opt.volume_size >= 400LL << 20) /* > 400MiB */ + opt.logfile_size = 4 << 20; /* -> 4MiB */ + else + opt.logfile_size = (opt.volume_size / 100) & + ~(vol->cluster_size - 1); + j = opt.logfile_size / vol->cluster_size; + while (rl_logfile[0].lcn + j >= opt.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. + */ + opt.logfile_size >>= 1; + j = opt.logfile_size / vol->cluster_size; + } + opt.logfile_size = (opt.logfile_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1); + Dprintf("$LogFile (journal) size = %ikiB\n", opt.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 (opt.logfile_size < 256 << 10) + err_exit("$LogFile would be created with invalid size. This " + "is not allowed as it would cause Windows to " + "blue screen and during boot.\n"); + rl_logfile[1].vcn = rl_logfile[0].length = j; + rl_logfile[1].lcn = -1LL; + rl_logfile[1].length = 0LL; + /* Allocate clusters for log file. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, opt.logfile_lcn + i, 1); + /* Create run list for $Boot. */ + rl_boot = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_boot) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_boot[0].vcn = 0LL; + rl_boot[0].lcn = 0LL; + /* + * $Boot is always 8192 (0x2000) bytes or 1 cluster, whichever is + * bigger. + */ + j = (8192 + vol->cluster_size - 1) / vol->cluster_size; + rl_boot[1].vcn = rl_boot[0].length = j; + rl_boot[1].lcn = -1LL; + rl_boot[1].length = 0LL; + /* Allocate clusters for $Boot. */ + for (i = 0; i < j; i++) + ntfs_set_bit(lcn_bitmap, 0LL + i, 1); + /* Allocate a buffer large enough to hold the mft. */ + buf = calloc(1, opt.mft_size); + if (!buf) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + /* Create run list for $BadClus, $DATA named stream $Bad. */ + rl_bad = (run_list *)malloc(2 * sizeof(run_list)); + if (!rl_bad) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + rl_bad[0].vcn = 0LL; + rl_bad[0].lcn = -1LL; + /* + * $BadClus named stream $Bad contains the whole volume as a single + * sparse run list entry. + */ + rl_bad[1].vcn = rl_bad[0].length = opt.nr_clusters; + rl_bad[1].lcn = -1LL; + rl_bad[1].length = 0LL; + + // TODO: Mark bad blocks as such. + + /* + * If not quick format, fill the device with 0s. + * FIXME: Except bad blocks! (AIA) + */ + if (!opt.quick_format) { + unsigned long position; + unsigned long mid_clust; + float progress_inc = (float)opt.nr_clusters / 100; + + Qprintf("Initialising device with zeroes: 0%%"); + fflush(stdout); + mid_clust = (opt.volume_size >> 1) / vol->cluster_size; + for (position = 0; position < opt.nr_clusters; position++) { + if (!(position % (int)(progress_inc+1))) { + Qprintf("\b\b\b\b%3.0f%%", position / + progress_inc); + fflush(stdout); + } + bw = mkntfs_write(vol->fd, buf, vol->cluster_size); + if (bw != vol->cluster_size) { + if (bw != -1 || errno != EIO) + err_exit("This should not happen.\n"); + if (!position) + err_exit("Error: Cluster zero is bad. " + "Cannot create NTFS file " + "system.\n"); + if (position == mid_clust && + (vol->major_ver < 1 || + (vol->major_ver == 1 && + vol->minor_ver < 2))) + err_exit("Error: Bad cluster found in " + "location reserved for system " + "file $Boot.\n"); + /* Add the baddie to our bad blocks list. */ + append_to_bad_blocks(position); + Qprintf("\nFound bad cluster (%ld). Adding to " + "list of bad blocks.\nInitialising " + "device with zeroes: %3.0i%%", position, + position / progress_inc); + /* Seek to next cluster. */ + lseek(vol->fd, ((off_t)position + 1) * + vol->cluster_size, SEEK_SET); + } + } + Qprintf("\b\b\b\b100%%"); + position = (opt.volume_size & (vol->cluster_size - 1)) / + opt.sector_size; + for (i = 0; i < position; i++) { + bw = mkntfs_write(vol->fd, buf, opt.sector_size); + if (bw != opt.sector_size) { + if (bw != -1 || errno != EIO) + err_exit("This should not happen.\n"); + else if (i + 1 == position && + (vol->major_ver >= 2 || + (vol->major_ver == 1 && + vol->minor_ver >= 2))) + err_exit("Error: Bad cluster found in " + "location reserved for system " + "file $Boot.\n"); + /* Seek to next sector. */ + lseek(vol->fd, opt.sector_size, SEEK_CUR); + } + } + Qprintf(" - Done.\n"); + } + Qprintf("Creating NTFS volume structures.\n"); + /* Setup an empty mft record. */ + format_mft_record((MFT_RECORD*)buf); + /* + * 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 = 1; i < 16; i++) { + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + memcpy(m, buf, vol->mft_record_size); + m->sequence_number = cpu_to_le16(i); + } + /* + * If a cluster contains more than the 16 system files, fill the rest + * with empty, formatted records. + */ + if (vol->cluster_size > 16 * vol->mft_record_size) { + for (i = 16; i * vol->mft_record_size < vol->cluster_size; i++) + memcpy(buf + i * vol->mft_record_size, buf, + vol->mft_record_size); + } + /* + * 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 < 16; i++) { + u32 file_attrs; + + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + m->flags |= MFT_RECORD_IN_USE; + ntfs_set_bit(mft_bitmap, 0LL + i, 1); + file_attrs = FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM; + if (i == FILE_root) { + if (opt.disable_indexing) + file_attrs |= FILE_ATTR_NOT_CONTENT_INDEXED; + if (opt.enable_compression) + file_attrs |= FILE_ATTR_COMPRESSED; + } + add_attr_std_info(m, file_attrs); + // dump_mft_record(m); + } + /* The root directory mft reference. */ + root_ref = MAKE_MFT_REF(FILE_root, FILE_root); + Vprintf("Creating root directory (mft record 5)\n"); + m = (MFT_RECORD*)(buf + 5 * 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_DUP_FILE_NAME_INDEX_PRESENT, 0, 0, + ".", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_root, &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, 0, AT_FILE_NAME, + COLLATION_FILE_NAME, opt.index_block_size); + // FIXME: This should be IGNORE_CASE + if (!err) + err = upgrade_to_large_index(m, "$I30", 4, 0, &index_block); + if (!err) { + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search " + "context: %s\n", strerror(errno)); + /* There is exactly one file name so this is ok. */ + if (ntfs_lookup_attr(AT_FILE_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0, + ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $FILE_NAME attribute not found.\n"); + } + a = ctx->attr; + err = insert_file_link_in_dir_index(index_block, root_ref, + (FILE_NAME_ATTR*)((char*)a + + le16_to_cpu(a->value_offset)), + le32_to_cpu(a->value_length)); + ntfs_put_attr_search_ctx(ctx); + } + if (err) + err_exit("Couldn't create root directory: %s\n", + strerror(-err)); + // dump_mft_record(m); + /* Add all other attributes, on a per-file basis for clarity. */ + Vprintf("Creating $MFT (mft record 0)\n"); + m = (MFT_RECORD*)buf; + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_mft, buf, + opt.mft_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_MFT, 1), opt.mft_size, + opt.mft_size, FILE_ATTR_HIDDEN | + FILE_ATTR_SYSTEM, 0, 0, "$MFT", + FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_MFT, &sd, &i); + err = add_attr_sd(m, sd, i); + } + /* mft_bitmap is not modified in mkntfs; no need to sync it later. */ + if (!err) + err = add_attr_bitmap_positioned(m, NULL, 0, 0, rl_mft_bmp, + mft_bitmap, mft_bitmap_byte_size); + if (err < 0) + err_exit("Couldn't create $MFT: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $MFTMirr (mft record 1)\n"); + m = (MFT_RECORD*)(buf + 1 * vol->mft_record_size); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_mftmirr, buf, + rl_mftmirr[0].length * vol->cluster_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_MFTMirr, FILE_MFTMirr), + rl_mftmirr[0].length * vol->cluster_size, + rl_mftmirr[0].length * vol->cluster_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$MFTMirr", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_MFTMirr, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $MFTMirr: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $LogFile (mft record 2)\n"); + m = (MFT_RECORD*)(buf + 2 * vol->mft_record_size); + buf2 = malloc(opt.logfile_size); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memset(buf2, -1, opt.logfile_size); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_logfile, buf2, + opt.logfile_size); + free(buf2); + buf2 = NULL; + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_LogFile, FILE_LogFile), + opt.logfile_size, opt.logfile_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$LogFile", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_LogFile, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $LogFile: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Volume (mft record 3)\n"); + m = (MFT_RECORD*)(buf + 3 * vol->mft_record_size); + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(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, 0, 0, NULL, 0); + if (!err) + err = add_attr_vol_name(m, vol->vol_name, vol->vol_name ? + strlen(vol->vol_name) : 0); + if (!err) { + Qprintf("Setting the volume dirty so check disk runs on next " + "reboot into Windows.\n"); + err = add_attr_vol_info(m, VOLUME_IS_DIRTY, vol->major_ver, + vol->minor_ver); + } + if (err < 0) + err_exit("Couldn't create $Volume: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $AttrDef (mft record 4)\n"); + m = (MFT_RECORD*)(buf + 4 * vol->mft_record_size); + if (vol->major_ver < 3) + buf2_size = 36000; + else + buf2_size = opt.attr_defs_len; + buf2 = (char*)calloc(1, buf2_size); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memcpy(buf2, opt.attr_defs, opt.attr_defs_len); + err = add_attr_data(m, NULL, 0, 0, 0, buf2, buf2_size); + free(buf2); + buf2 = NULL; + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_AttrDef, FILE_AttrDef), + (buf2_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), buf2_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$AttrDef", FILE_NAME_WIN32_AND_DOS); + buf2_size = 0; + if (!err) { + init_system_file_sd(FILE_AttrDef, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $AttrDef: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Bitmap (mft record 6)\n"); + m = (MFT_RECORD*)(buf + 6 * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, lcn_bitmap, lcn_bitmap_byte_size); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_Bitmap, FILE_Bitmap), + (lcn_bitmap_byte_size + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), lcn_bitmap_byte_size, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$Bitmap", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Bitmap, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $Bitmap: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Boot (mft record 7)\n"); + m = (MFT_RECORD*)(buf + 7 * vol->mft_record_size); + buf2 = calloc(1, 8192); + if (!buf2) + err_exit("Failed to allocate internal buffer: %s\n", + strerror(errno)); + memcpy(buf2, boot_array, sizeof(boot_array)); + /* + * Create the boot sector into buf2. Note, that buf2 already is 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 = (NTFS_BOOT_SECTOR*)buf2; + bs->bpb.bytes_per_sector = cpu_to_le16(opt.sector_size); + bs->bpb.sectors_per_cluster = (u8)(vol->cluster_size / + opt.sector_size); + bs->bpb.media_type = 0xf8; /* hard disk */ + /* + * If there are problems go back to bs->unused[0-3] and set them. See + * ../include/bootsect.h for details. Other fields to also consider + * setting are: bs->bpb.sectors_per_track, .heads, and .hidden_sectors. + */ + bs->number_of_sectors = scpu_to_le64(opt.nr_sectors); + bs->mft_lcn = scpu_to_le64(opt.mft_lcn); + bs->mftmirr_lcn = scpu_to_le64(opt.mftmirr_lcn); + if (vol->mft_record_size >= vol->cluster_size) + bs->clusters_per_mft_record = vol->mft_record_size / + vol->cluster_size; + else { + bs->clusters_per_mft_record = -(ffs(vol->mft_record_size) - 1); + if ((1 << -bs->clusters_per_mft_record) != vol->mft_record_size) + err_exit("BUG: calculated clusters_per_mft_record " + "is wrong (= 0x%x)\n", + bs->clusters_per_mft_record); + } + Dprintf("Clusters per mft record = %i (0x%x)\n", + bs->clusters_per_mft_record, + bs->clusters_per_mft_record); + if (opt.index_block_size >= vol->cluster_size) + bs->clusters_per_index_record = opt.index_block_size / + vol->cluster_size; + else { + bs->clusters_per_index_record = -(ffs(opt.index_block_size) - 1); + if ((1 << -bs->clusters_per_index_record) != + opt.index_block_size) + err_exit("BUG: calculated clusters_per_index_record " + "is wrong (= 0x%x)\n", + bs->clusters_per_index_record); + } + Dprintf("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 = scpu_to_le64(((s64)random() << 32) | + ((s64)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 (!is_boot_sector_ntfs(bs, opt.verbose > 0 ? 0 : 1)) + err_exit("FATAL: Generated boot sector is invalid!\n"); + err = add_attr_data_positioned(m, NULL, 0, 0, 0, rl_boot, buf2, 8192); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_Boot, FILE_Boot), + (8192 + vol->cluster_size - 1) & + ~(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) + err_exit("Couldn't create $Boot: %s\n", strerror(-err)); + Vprintf("Creating backup boot sector.\n"); + /* + * Write the first max(512, opt.sector_size) bytes from buf2 to the + * last sector. + */ + if (lseek(vol->fd, (opt.nr_sectors + 1) * opt.sector_size - i, + SEEK_SET) == (off_t)-1) + goto bb_err; + bw = mkntfs_write(vol->fd, buf2, i); + free(buf2); + buf2 = NULL; + if (bw != i) { + int _e = errno; + char *_s; + + if (bw == -1LL) + _s = strerror(_e); + else + _s = "unknown error"; + if (bw != -1LL || (bw == -1LL && _e != ENOSPC)) { + err_exit("Couldn't write backup boot sector: %s\n", _s); +bb_err: + Eprintf("Seek failed: %s\n", strerror(errno)); + } + Eprintf("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\nbackup boot sector when it " + "is run on your next boot into Windows.\n"); + } + //dump_mft_record(m); + Vprintf("Creating $BadClus (mft record 8)\n"); + m = (MFT_RECORD*)(buf + 8 * 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, 0, 0, rl_bad, NULL, + opt.nr_clusters * vol->cluster_size); + if (!err) { + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + } + if (!err) { + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_BadClus, FILE_BadClus), + 0LL, 0LL, FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, + 0, 0, "$BadClus", FILE_NAME_WIN32_AND_DOS); + } + if (!err) { + init_system_file_sd(FILE_BadClus, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $BadClus: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $Quota (mft record 9)\n"); + m = (MFT_RECORD*)(buf + 9 * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(9, 9), 0LL, 0LL, FILE_ATTR_HIDDEN + | FILE_ATTR_SYSTEM, 0, 0, "$Quota", + FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_Secure, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $Quota: %s\n", strerror(-err)); + //dump_mft_record(m); + Vprintf("Creating $UpCase (mft record 0xa)\n"); + m = (MFT_RECORD*)(buf + 0xa * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, (char*)vol->upcase, + vol->upcase_len << 1); + if (!err) + err = create_hardlink(index_block, root_ref, m, + MAKE_MFT_REF(FILE_UpCase, FILE_UpCase), + ((vol->upcase_len << 1) + vol->cluster_size - 1) & + ~(vol->cluster_size - 1), vol->upcase_len << 1, + FILE_ATTR_HIDDEN | FILE_ATTR_SYSTEM, 0, 0, + "$UpCase", FILE_NAME_WIN32_AND_DOS); + if (!err) { + init_system_file_sd(FILE_UpCase, &sd, &i); + err = add_attr_sd(m, sd, i); + } + if (err < 0) + err_exit("Couldn't create $UpCase: %s\n", strerror(-err)); + //dump_mft_record(m); + /* NTFS 1.2 reserved system files (mft records 0xb-0xf) */ + for (i = 0xb; i < 0x10; i++) { + Vprintf("Creating system file (mft record 0x%x)\n", i, i); + m = (MFT_RECORD*)(buf + i * vol->mft_record_size); + err = add_attr_data(m, NULL, 0, 0, 0, NULL, 0); + if (!err) { + init_system_file_sd(i, &sd, &j); + err = add_attr_sd(m, sd, j); + } + if (err < 0) + err_exit("Couldn't create system file %i (0x%x): %s\n", + i, i, strerror(-err)); + //dump_mft_record(m); + } +// - 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. + Vprintf("Syncing root directory index record.\n"); + m = (MFT_RECORD*)(buf + 5 * vol->mft_record_size); + i = 5 * sizeof(uchar_t); + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search context: %s\n", + strerror(errno)); + // FIXME: This should be IGNORE_CASE! + if (ntfs_lookup_attr(AT_INDEX_ALLOCATION, I30, 4, 0, 0, + NULL, 0, ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $INDEX_ALLOCATION attribute not found.\n"); + } + a = ctx->attr; + rl_index = ntfs_decompress_mapping_pairs(vol, a, NULL); + if (!rl_index) { + ntfs_put_attr_search_ctx(ctx); + err_exit("Failed to decompress run list of $INDEX_ALLOCATION " + "attribute.\n"); + } + if (sle64_to_cpu(a->initialized_size) < i) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $INDEX_ALLOCATION attribute too short.\n"); + } + ntfs_put_attr_search_ctx(ctx); + i = sizeof(INDEX_BLOCK) - sizeof(INDEX_HEADER) + + le32_to_cpu(index_block->index.allocated_size); + err = ntfs_pre_write_mst_fixup((NTFS_RECORD*)index_block, i); + if (err) + err_exit("ntfs_pre_write_mst_fixup() failed while syncing " + "root directory index block.\n"); + lw = ntfs_rlwrite(vol->fd, rl_index, (char*)index_block, i, NULL); + if (lw != i) + err_exit("Error writing $INDEX_ALLOCATION.\n"); + /* No more changes to @index_block below here so no need for fixup: */ + // ntfs_post_write_mst_fixup((NTFS_RECORD*)index_block); + Vprintf("Syncing $Bitmap.\n"); + m = (MFT_RECORD*)(buf + 6 * vol->mft_record_size); + ctx = ntfs_get_attr_search_ctx(NULL, m); + if (!ctx) + err_exit("Failed to allocate attribute search context: %s\n", + strerror(errno)); + if (ntfs_lookup_attr(AT_DATA, AT_UNNAMED, 0, 0, 0, NULL, 0, ctx)) { + ntfs_put_attr_search_ctx(ctx); + err_exit("BUG: $DATA attribute not found.\n"); + } + a = ctx->attr; + if (a->non_resident) { + rl = ntfs_decompress_mapping_pairs(vol, a, NULL); + ntfs_put_attr_search_ctx(ctx); + if (!rl) + err_exit("ntfs_decompress_mapping_pairs() failed\n"); + lw = ntfs_rlwrite(vol->fd, rl, lcn_bitmap, + lcn_bitmap_byte_size, NULL); + if (lw != lcn_bitmap_byte_size) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + } else { + memcpy((char*)a + le16_to_cpu(a->value_offset), lcn_bitmap, + le32_to_cpu(a->value_length)); + ntfs_put_attr_search_ctx(ctx); + } + /* + * No need to sync $MFT/$BITMAP as that has never been modified since + * its creation. + */ + Vprintf("Syncing $MFT.\n"); + pos = opt.mft_lcn * vol->cluster_size; + lw = 1; + for (i = 0; i < opt.mft_size / vol->mft_record_size; i++) { + if (!opt.no_action) + lw = ntfs_mst_pwrite(vol->fd, pos, 1, + vol->mft_record_size, + buf + i * vol->mft_record_size); + if (lw != 1) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + pos += vol->mft_record_size; + } + Vprintf("Updating $MFTMirr.\n"); + pos = opt.mftmirr_lcn * vol->cluster_size; + lw = 1; + for (i = 0; i < rl_mftmirr[0].length * vol->cluster_size / + vol->mft_record_size; i++) { + u16 usn, *usnp; + m = (MFT_RECORD*)(buf + i * 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. + */ + usnp = (u16*)((char*)m + le16_to_cpu(m->usa_ofs)); + usn = le16_to_cpup(usnp); + if (usn-- <= 1) + usn = 0xfffe; + *usnp = cpu_to_le16(usn); + if (!opt.no_action) + lw = ntfs_mst_pwrite(vol->fd, pos, 1, + vol->mft_record_size, + buf + i * vol->mft_record_size); + if (lw != 1) + err_exit("%s\n", lw == -1 ? strerror(errno) : + "unknown error"); + pos += vol->mft_record_size; + } + Vprintf("Syncing device.\n"); + if (fdatasync(vol->fd) == -1) + err_exit("Syncing device. FAILED: %s", strerror(errno)); + Qprintf("mkntfs completed successfully. Have a nice day.\n"); + /* + * Device is unlocked and closed by the registered exit function + * mkntfs_exit(). + */ + return 0; +} +