diff --git a/ntfsprogs/Makefile.am b/ntfsprogs/Makefile.am index 803dd98e..9abb67b4 100644 --- a/ntfsprogs/Makefile.am +++ b/ntfsprogs/Makefile.am @@ -19,7 +19,8 @@ sbin_PROGRAMS = mkntfs ntfslabel ntfsundelete ntfsresize ntfsclone \ ntfscp EXTRA_PROGRAM_NAMES = ntfswipe ntfstruncate -QUARANTINED_PROGRAM_NAMES = ntfsdump_logfile ntfsmftalloc ntfsmove ntfsck +QUARANTINED_PROGRAM_NAMES = ntfsdump_logfile ntfsmftalloc ntfsmove ntfsck \ + ntfsfallocate man_MANS = mkntfs.8 ntfsfix.8 ntfslabel.8 ntfsinfo.8 \ ntfsundelete.8 ntfsresize.8 ntfsprogs.8 ntfsls.8 \ @@ -122,6 +123,10 @@ ntfsdump_logfile_SOURCES= ntfsdump_logfile.c ntfsdump_logfile_LDADD = $(AM_LIBS) ntfsdump_logfile_LDFLAGS= $(AM_LFLAGS) +ntfsfallocate_SOURCES = ntfsfallocate.c utils.c utils.h +ntfsfallocate_LDADD = $(AM_LIBS) +ntfsfallocate_LDFLAGS = $(AM_LFLAGS) + if ENABLE_CRYPTO ntfsdecrypt_SOURCES = ntfsdecrypt.c utils.c utils.h ntfsdecrypt_LDADD = $(AM_LIBS) $(GNUTLS_LIBS) $(LIBGCRYPT_LIBS) diff --git a/ntfsprogs/ntfsfallocate.c b/ntfsprogs/ntfsfallocate.c new file mode 100644 index 00000000..1b243ae6 --- /dev/null +++ b/ntfsprogs/ntfsfallocate.c @@ -0,0 +1,903 @@ +/** + * ntfsfallocate + * + * Copyright (c) 2013-2014 Jean-Pierre Andre + * + * This utility will allocate clusters to a specified attribute belonging + * to a specified file or directory, to a specified length. + * + * WARNING : this can lead to configurations not supported by Windows + * and Windows may crash (BSOD) when writing to preallocated clusters + * which were not written to. + * + * 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 + */ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDARG_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_GETOPT_H +#include +#else +extern char *optarg; +extern int optind; +#endif +#ifdef HAVE_LIMITS_H +#include +#endif + +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif + +#include "types.h" +#include "attrib.h" +#include "inode.h" +#include "layout.h" +#include "volume.h" +#include "logging.h" +#include "runlist.h" +#include "dir.h" +#include "bitmap.h" +#include "lcnalloc.h" +#include "utils.h" +#include "misc.h" + +const char *EXEC_NAME = "ntfsfallocate"; + +char *dev_name; +const char *file_name; +le32 attr_type; +ntfschar *attr_name = NULL; +u32 attr_name_len; +s64 opt_alloc_offs; +s64 opt_alloc_len; + +ATTR_DEF *attr_defs; + +static struct { + /* -h, print usage and exit. */ + int no_action; /* do not write to device, only display + what would be done. */ + int no_size_change; /* -n, do not change the apparent size */ + int quiet; /* -q, quiet execution. */ + int verbose; /* -v, verbose execution, given twice, really + verbose execution (debug mode). */ + int force; /* -f, force allocation. */ + /* -V, print version and exit. */ +} opts; + +static const struct option lopt[] = { + { "offset", required_argument, NULL, 'o' }, + { "length", required_argument, NULL, 'l' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "no-action", no_argument, NULL, 'N' }, + { "no-size-change", no_argument, NULL, 'n' }, + { "quiet", no_argument, NULL, 'q' }, + { "version", no_argument, NULL, 'V' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } +}; + +/** + * err_exit - error output and terminate; ignores quiet (-q) + * + * DO NOT USE when allocations are not in initial state + */ +__attribute__((noreturn)) +__attribute__((format(printf, 2, 3))) +static void err_exit(ntfs_volume *vol, const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "Aborting...\n"); + if (vol && ntfs_umount(vol, 0)) + fprintf(stderr, "Warning: Could not umount %s\n", dev_name); + exit(1); +} + +/** + * copyright - print copyright statements + */ +static void copyright(void) +{ + fprintf(stderr, "Copyright (c) 2013-2014 Jean-Pierre Andre\n" + "Allocate clusters to a specified attribute of " + "a specified file.\n"); +} + +/** + * license - print license statement + */ +static void license(void) +{ + fprintf(stderr, "%s", ntfs_gpl); +} + +/** + * usage - print a list of the parameters to the program + */ +__attribute__((noreturn)) +static void usage(int ret) +{ + copyright(); + fprintf(stderr, "Usage: %s [options] -l length device file [attr-type " + "[attr-name]]\n" + " If attr-type is not specified, 0x80 (i.e. $DATA) " + "is assumed.\n" + " If attr-name is not specified, an unnamed " + "attribute is assumed.\n" + " -f Force execution despite errors\n" + " -n Do not change the apparent size of file\n" + " -l length Allocate length bytes\n" + " -o offset Start allocating at offset\n" + " -v Verbose execution\n" + " -vv Very verbose execution\n" + " -V Display version information\n" + " -h Display this help\n", EXEC_NAME); + fprintf(stderr, "%s%s", ntfs_bugs, ntfs_home); + exit(ret); +} + +/** + * err_exit - error output, display usage and exit + */ +__attribute__((noreturn)) +__attribute__((format(printf, 1, 2))) +static void err_usage(const char *fmt, ...) +{ + va_list ap; + + fprintf(stderr, "ERROR: "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + usage(1); +} + +/* + * Get a value option with a possible multiple suffix + */ + +static s64 option_value(const char *arg) +{ + s64 ll; + char *s; + s64 fact; + int count; + BOOL err; + + err = FALSE; + ll = strtoll(arg, &s, 0); + if ((ll >= LLONG_MAX) && (errno == ERANGE)) + err_exit((ntfs_volume*)NULL, "Too big value : %s\n",arg); + if (*s) { + count = 0; + switch (*s++) { + case 'E' : count++; + case 'P' : count++; + case 'T' : count++; + case 'G' : count++; + case 'M' : count++; + case 'K' : count++; + switch (*s++) { + case 'i' : + fact = 1024; + if (*s++ != 'B') + err = TRUE; + break; + case 'B' : + fact = 1000; + break; + case '\0' : + fact = 1024; + s--; + break; + default : + err = TRUE; + fact = 1; + break; + } + if (*s) + err = TRUE; + break; + default : + err = TRUE; + break; + } + if (err) + err_exit((ntfs_volume*)NULL, + "Invalid suffix in : %s\n",arg); + else + while (count-- > 0) { + if (ll > LLONG_MAX/1024) + err_exit((ntfs_volume*)NULL, + "Too big value : %s\n",arg); + ll *= fact; + } + } + return (ll); +} + + +/** + * parse_options + */ +static void parse_options(int argc, char *argv[]) +{ + long long ll; + char *s, *s2; + int c; + + opt_alloc_len = 0; + opt_alloc_offs = 0; + if (argc && *argv) + EXEC_NAME = *argv; + fprintf(stderr, "%s v%s (libntfs-3g)\n", EXEC_NAME, VERSION); + while ((c = getopt_long(argc, argv, "fh?no:qvVl:", lopt, NULL)) != EOF) { + switch (c) { + case 'f': + opts.force = 1; + break; + case 'n': + opts.no_size_change = 1; + break; + case 'N': /* Not proposed as a short option */ + opts.no_action = 1; + break; + case 'q': + opts.quiet = 1; + ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET); + break; + case 'v': + opts.verbose++; + ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); + break; + case 'V': + /* Version number already printed */ + license(); + exit(0); + case 'l': + ll = option_value(argv[optind - 1]); + if ((ll <= 0) + || (ll >= LLONG_MAX && errno == ERANGE)) + err_usage("Invalid length : %s\n", + argv[optind - 1]); + opt_alloc_len = ll; + break; + case 'o': + ll = option_value(argv[optind - 1]); + if ((ll < 0) + || (ll >= LLONG_MAX && errno == ERANGE)) + err_usage("Invalid offset : %s\n", + argv[optind - 1]); + opt_alloc_offs = ll; + break; + case 'h': + usage(0); + case '?': + default: + usage(1); + } + } + if (!opt_alloc_len) { + err_usage("Missing allocation length\n"); + } + + ntfs_log_verbose("length = %lli = 0x%llx\n", + (long long)opt_alloc_len, (long long)opt_alloc_len); + ntfs_log_verbose("offset = %lli = 0x%llx\n", + (long long)opt_alloc_offs, (long long)opt_alloc_offs); + + if (optind == argc) + usage(1); + + if (opts.verbose > 1) + ntfs_log_set_levels(NTFS_LOG_LEVEL_DEBUG | NTFS_LOG_LEVEL_TRACE | + NTFS_LOG_LEVEL_VERBOSE | NTFS_LOG_LEVEL_QUIET); + + /* Get the device. */ + dev_name = argv[optind++]; + ntfs_log_verbose("device name = %s\n", dev_name); + + if (optind == argc) + usage(1); + + /* Get the file name. */ + file_name = argv[optind++]; + ntfs_log_verbose("file name = \"%s\"\n", file_name); + + /* Get the attribute type, if specified. */ + if (optind == argc) { + attr_type = AT_DATA; + attr_name = AT_UNNAMED; + attr_name_len = 0; + } else { + unsigned long ul; + + s = argv[optind++]; + ul = strtoul(s, &s2, 0); + if (*s2 || !ul || (ul >= ULONG_MAX && errno == ERANGE)) + err_usage("Invalid attribute type %s: %s\n", s, + strerror(errno)); + attr_type = cpu_to_le32(ul); + + /* Get the attribute name, if specified. */ + if (optind != argc) { + s = argv[optind++]; + /* Convert the string to little endian Unicode. */ + attr_name_len = ntfs_mbstoucs(s, &attr_name); + if ((int)attr_name_len < 0) + err_usage("Invalid attribute name " + "\"%s\": %s\n", + s, strerror(errno)); + + /* Keep hold of the original string. */ + s2 = s; + + s = argv[optind++]; + if (optind != argc) + usage(1); + } else { + attr_name = AT_UNNAMED; + attr_name_len = 0; + } + } + ntfs_log_verbose("attribute type = 0x%lx\n", + (unsigned long)le32_to_cpu(attr_type)); + if (attr_name == AT_UNNAMED) + ntfs_log_verbose("attribute name = \"\" (UNNAMED)\n"); + else + ntfs_log_verbose("attribute name = \"%s\" (length %u " + "Unicode characters)\n", s2, + (unsigned int)attr_name_len); +} + +/* + * Save the initial runlist, to be restored on error + */ + +static runlist_element *ntfs_save_rl(runlist_element *rl) +{ + runlist_element *save; + int n; + + n = 0; + save = (runlist_element*)NULL; + if (rl) { + while (rl[n].length) + n++; + save = (runlist_element*)malloc((n + 1)*sizeof(runlist_element)); + if (save) { + memcpy(save, rl, (n + 1)*sizeof(runlist_element)); + } + } + return (save); +} + +/* + * Free the common part of two runs + */ + +static void free_common(ntfs_volume *vol, runlist_element *brl, s64 blth, + runlist_element *grl, s64 glth) +{ + VCN begin_common; + VCN end_common; + + begin_common = max(grl->vcn, brl->vcn); + end_common = min(grl->vcn + glth, brl->vcn + blth); + if (end_common > begin_common) { + if (ntfs_bitmap_clear_run(vol->lcnbmp_na, + brl->lcn + begin_common - brl->vcn, + end_common - begin_common)) + ntfs_log_error("Failed to free %lld clusters " + "from 0x%llx\n", + (long long)end_common - begin_common, + (long long)(brl->lcn + begin_common + - brl->vcn)); + } +} + +/* + * Restore the cluster allocations to initial state + * + * If a new error occurs, only output a message + */ + +static void ntfs_restore_rl(ntfs_attr *na, runlist_element *oldrl) +{ + runlist_element *brl; /* Pointer to bad runlist */ + runlist_element *grl; /* Pointer to good runlist */ + ntfs_volume *vol; + + vol = na->ni->vol; + /* Examine allocated entries from the bad runlist */ + for (brl=na->rl; brl->length; brl++) { + if (brl->lcn != LCN_HOLE) { +// TODO improve by examining both list in parallel + /* Find the holes in the good runlist which overlap */ + for (grl=oldrl; grl->length + && (grl->vcn<=(brl->vcn+brl->length)); grl++) { + if (grl->lcn == LCN_HOLE) { + free_common(vol, brl, brl->length, grl, + grl->length); + } + } + /* Free allocations beyond the end of good runlist */ + if (grl && !grl->length + && ((brl->vcn + brl->length) > grl->vcn)) { + free_common(vol, brl, brl->length, grl, + brl->vcn + brl->length - grl->vcn); + } + } + } + free(na->rl); + na->rl = oldrl; + if (ntfs_attr_update_mapping_pairs(na, 0)) { + ntfs_log_error("Failed to restore the original runlist\n"); + } +} + +/* + * Zero newly allocated runs up to initialized_size + */ + +static int ntfs_inner_zero(ntfs_attr *na, runlist_element *rl) +{ + ntfs_volume *vol; + char *buf; + runlist_element *zrl; + s64 cofs; + s64 pos; + s64 zeroed; + int err; + + err = 0; + vol = na->ni->vol; + buf = (char*)malloc(vol->cluster_size); + if (buf) { + memset(buf, 0, vol->cluster_size); + zrl = rl; + pos = zrl->vcn << vol->cluster_size_bits; + while (zrl->length + && !err + && (pos < na->initialized_size)) { + for (cofs=0; cofslength && !err; cofs++) { + zeroed = ntfs_pwrite(vol->dev, + (rl->lcn + cofs) + << vol->cluster_size_bits, + vol->cluster_size, buf); + if (zeroed != vol->cluster_size) { + ntfs_log_error("Failed to zero at " + "offset %lld\n", + (long long)pos); + errno = EIO; + err = -1; + } + pos += vol->cluster_size; + } + zrl++; + pos = zrl->vcn << vol->cluster_size_bits; + } + free(buf); + } else { + ntfs_log_error("Failed to allocate memory\n"); + errno = ENOSPC; + err = -1; + } + return (err); +} + +/* + * Merge newly allocated runs into runlist + */ + +static int ntfs_merge_allocation(ntfs_attr *na, runlist_element *rl, + s64 size) +{ + ntfs_volume *vol; + int err; + + err = 0; + vol = na->ni->vol; + /* Newly allocated clusters before initialized size need be zeroed */ + if ((rl->vcn << vol->cluster_size_bits) < na->initialized_size) { + err = ntfs_inner_zero(na, rl); + } + if (!err) { + if (na->data_flags & ATTR_IS_SPARSE) { + na->compressed_size += size; + if (na->compressed_size >= na->allocated_size) { + na->data_flags &= ~ATTR_IS_SPARSE; + if (na->compressed_size > na->allocated_size) { + ntfs_log_error("File size error : " + "apparent %lld, " + "compressed %lld > " + "allocated %lld", + (long long)na->data_size, + (long long)na->compressed_size, + (long long)na->allocated_size); + errno = EIO; + err = -1; + } + } + } + } + if (!err) { + rl = ntfs_runlists_merge(na->rl, rl); + if (!rl) { + ntfs_log_error("Failed to merge the new allocation\n"); + err = -1; + } else { + na->rl = rl; + /* Update the runlist */ + if (ntfs_attr_update_mapping_pairs(na, 0)) { + ntfs_log_error( + "Failed to update the runlist\n"); + err = -1; + } + } + } + return (err); +} + +static int ntfs_inner_allocation(ntfs_attr *na, s64 alloc_offs, s64 alloc_len) +{ + ntfs_volume *vol; + runlist_element *rl; + runlist_element *prl; + runlist_element *rlc; + VCN from_vcn; + VCN end_vcn; + LCN lcn_seek_from; + VCN from_hole; + VCN end_hole; + s64 need; + int err; + BOOL done; + + err = 0; + vol = na->ni->vol; + /* Find holes which overlap the requested allocation */ + from_vcn = alloc_offs >> vol->cluster_size_bits; + end_vcn = (alloc_offs + alloc_len + vol->cluster_size - 1) + >> vol->cluster_size_bits; + do { + done = FALSE; + rl = na->rl; + while (rl->length + && ((rl->lcn >= 0) + || ((rl->vcn + rl->length) <= from_vcn) + || (rl->vcn >= end_vcn))) + rl++; + if (!rl->length) + done = TRUE; + else { + from_hole = max(from_vcn, rl->vcn); + end_hole = min(end_vcn, rl->vcn + rl->length); + need = end_hole - from_hole; + lcn_seek_from = -1; + if (rl->vcn) { + /* Avoid fragmentation when possible */ + prl = rl; + if ((--prl)->lcn >= 0) { + lcn_seek_from = prl->lcn + + from_hole - prl->vcn; + } + } + if (need <= 0) { + ntfs_log_error("Wrong hole size %lld\n", + (long long)need); + errno = EIO; + err = -1; + } else { + rlc = ntfs_cluster_alloc(vol, from_hole, need, + lcn_seek_from, DATA_ZONE); + if (!rlc) + err = -1; + else + err = ntfs_merge_allocation(na, rlc, + need << vol->cluster_size_bits); + } + } + } while (!err && !done); + return (err); +} + +static int ntfs_full_allocation(ntfs_attr *na, ntfs_attr_search_ctx *ctx, + s64 alloc_offs, s64 alloc_len) +{ + ATTR_RECORD *attr; + ntfs_inode *ni; + s64 initialized_size; + s64 data_size; + int err; + + err = 0; + initialized_size = na->initialized_size; + data_size = na->data_size; + + if (na->allocated_size <= alloc_offs) { + /* + * Request is fully beyond what was already allocated : + * only need to expand the attribute + */ + err = ntfs_attr_truncate(na, alloc_offs); + if (!err) + err = ntfs_attr_truncate_solid(na, + alloc_offs + alloc_len); + } else { + /* + * Request overlaps what was already allocated : + * We may have to fill existing holes, and force zeroes + * into clusters which are visible. + */ + if ((alloc_offs + alloc_len) > na->allocated_size) + err = ntfs_attr_truncate(na, alloc_offs + alloc_len); + if (!err) + err = ntfs_inner_allocation(na, alloc_offs, alloc_len); + } + /* Set the sizes, even after an error, to keep consistency */ + na->initialized_size = initialized_size; + /* Restore the original apparent size if requested or error */ + if (err || opts.no_size_change + || ((alloc_offs + alloc_len) < data_size)) + na->data_size = data_size; + else { + /* + * "man 1 fallocate" does not define the new apparent size + * when size change is allowed (no --keep-size). + * Assuming the same as no FALLOC_FL_KEEP_SIZE in fallocate(2) : + * "the file size will be changed if offset + len is greater + * than the file size" +// TODO check the behavior of another file system + */ + na->data_size = alloc_offs + alloc_len; + } + + if (!err) { + /* Find the attribute, which may have been relocated for allocations */ + if (ntfs_attr_lookup(attr_type, attr_name, attr_name_len, + CASE_SENSITIVE, 0, NULL, 0, ctx)) { + err = -1; + ntfs_log_error("Failed to locate the attribute\n"); + } else { + /* Feed the sizes into the attribute */ + attr = ctx->attr; + attr->data_size = cpu_to_le64(na->data_size); + attr->initialized_size + = cpu_to_le64(na->initialized_size); + attr->allocated_size + = cpu_to_le64(na->allocated_size); + if (na->data_flags & ATTR_IS_SPARSE) + attr->compressed_size + = cpu_to_le64(na->compressed_size); + /* Copy the unnamed data attribute sizes to inode */ + if ((attr_type == AT_DATA) && !attr_name_len) { + ni = na->ni; + ni->data_size = na->data_size; + if (na->data_flags & ATTR_IS_SPARSE) { + ni->allocated_size + = na->compressed_size; + ni->flags |= FILE_ATTR_SPARSE_FILE; + } else + ni->allocated_size + = na->allocated_size; + } + } + } + return (err); +} + + +/* + * Do the actual allocations + */ + +static int ntfs_fallocate(ntfs_inode *ni, s64 alloc_offs, s64 alloc_len) +{ + s64 allocated_size; + s64 data_size; + ntfs_attr_search_ctx *ctx; + ntfs_attr *na; + runlist_element *oldrl; + const char *errmess; + int save_errno; + int err; + + err = 0; + /* Open the specified attribute. */ + na = ntfs_attr_open(ni, attr_type, attr_name, attr_name_len); + if (!na) { + ntfs_log_perror("Failed to open attribute 0x%lx: ", + (unsigned long)le32_to_cpu(attr_type)); + err = -1; + } else { + errmess = (const char*)NULL; + if (na->data_flags & ATTR_IS_COMPRESSED) { + errmess= "Cannot fallocate a compressed file"; + } + + /* Locate the attribute record, needed for updating sizes */ + ctx = ntfs_attr_get_search_ctx(ni, NULL); + if (!ctx) { + errmess = "Failed to allocate a search context"; + } + if (errmess) { + ntfs_log_error("%s\n",errmess); + err = -1; + } else { + /* Get and save the initial allocations */ + allocated_size = na->allocated_size; + data_size = ni->data_size; + err = ntfs_attr_map_whole_runlist(na); + if (!err) { + oldrl = ntfs_save_rl(na->rl); + if (oldrl) { + err = ntfs_full_allocation(na, ctx, + alloc_offs, alloc_len); + if (err) { + save_errno = errno; + ni->allocated_size + = allocated_size; + ni->data_size = data_size; + ntfs_restore_rl(na, oldrl); + errno = save_errno; + } else { + free(oldrl); + /* Mark file name dirty, to update the sizes in directories */ + NInoFileNameSetDirty(ni); + NInoSetDirty(ni); + } + } else + err = -1; + } + ntfs_attr_put_search_ctx(ctx); + } + /* Close the attribute. */ + ntfs_attr_close(na); + } + return (err); +} + +/** + * main + */ +int main(int argc, char **argv) +{ + unsigned long mnt_flags, ul; + int err; + ntfs_inode *ni; + ntfs_volume *vol; +#ifdef HAVE_WINDOWS_H + char *unix_name; +#endif + + vol = (ntfs_volume*)NULL; + ntfs_log_set_handler(ntfs_log_handler_outerr); + + /* Initialize opts to zero / required values. */ + memset(&opts, 0, sizeof(opts)); + + /* Parse command line options. */ + parse_options(argc, argv); + + utils_set_locale(); + + /* Make sure the file system is not mounted. */ + if (ntfs_check_if_mounted(dev_name, &mnt_flags)) + ntfs_log_perror("Failed to determine whether %s is mounted", + dev_name); + else if (mnt_flags & NTFS_MF_MOUNTED) { + ntfs_log_error("%s is mounted.\n", dev_name); + if (!opts.force) + err_exit((ntfs_volume*)NULL, "Refusing to run!\n"); + fprintf(stderr, "ntfsfallocate forced anyway. Hope /etc/mtab " + "is incorrect.\n"); + } + + /* Mount the device. */ + if (opts.no_action) { + ntfs_log_quiet("Running in READ-ONLY mode!\n"); + ul = NTFS_MNT_RDONLY; + } else + if (opts.force) + ul = NTFS_MNT_RECOVER; + else + ul = 0; + vol = ntfs_mount(dev_name, ul); + if (!vol) + err_exit(vol, "Failed to mount %s: %s\n", dev_name, + strerror(errno)); + + if ((vol->flags & VOLUME_IS_DIRTY) && !opts.force) + err_exit(vol, "Volume is dirty, please run chkdsk.\n"); + + if (ntfs_volume_get_free_space(vol)) + err_exit(vol, "Failed to get free clusters %s: %s\n", + dev_name, strerror(errno)); + + /* Open the specified inode. */ +#ifdef HAVE_WINDOWS_H + unix_name = (char*)malloc(strlen(file_name) + 1); + if (unix_name) { + int i; + for (i=0; file_name[i]; i++) + if (file_name[i] == '\\') + unix_name[i] = '/'; + else + unix_name[i] = file_name[i]; + unix_name[i] = 0; + ni = ntfs_pathname_to_inode(vol, NULL, unix_name); + free(unix_name); + } else + ni = (ntfs_inode*)NULL; +#else + ni = ntfs_pathname_to_inode(vol, NULL, file_name); +#endif + if (!ni) + err_exit(vol, "Failed to open file \"%s\": %s\n", file_name, + strerror(errno)); + if (!opts.no_action) + err = ntfs_fallocate(ni, opt_alloc_offs, opt_alloc_len); + + /* Close the inode. */ + if (ntfs_inode_close(ni)) { + err = -1; + err_exit(vol, "Failed to close inode \"%s\" : %s\n", file_name, + strerror(errno)); + } + + /* Unmount the volume. */ + err = ntfs_umount(vol, 0); + vol = (ntfs_volume*)NULL; + if (err) + ntfs_log_perror("Warning: Failed to umount %s", dev_name); + + /* Free the attribute name if it exists. */ + if (attr_name_len) + ntfs_ucsfree(attr_name); + + ntfs_log_quiet("ntfsfallocate completed successfully. Have a nice day.\n"); + return 0; +}