1 /**
   2  * ntfslabel - Part of the Linux-NTFS project.
   3  *
   4  * Copyright (c) 2002 Matthew J. Fanto
   5  * Copyright (c) 2002-2005 Anton Altaparmakov
   6  * Copyright (c) 2002-2003 Richard Russon
   7  *
   8  * This utility will display/change the label on an NTFS partition.
   9  *
  10  * This program is free software; you can redistribute it and/or modify
  11  * it under the terms of the GNU General Public License as published by
  12  * the Free Software Foundation; either version 2 of the License, or
  13  * (at your option) any later version.
  14  *
  15  * This program is distributed in the hope that it will be useful,
  16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18  * GNU General Public License for more details.
  19  *
  20  * You should have received a copy of the GNU General Public License
  21  * along with this program (in the main directory of the Linux-NTFS
  22  * distribution in the file COPYING); if not, write to the Free Software
  23  * Foundation,Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  24  */
  25 
  26 #include "config.h"
  27 
  28 #ifdef HAVE_STDLIB_H
  29 #include <stdlib.h>
  30 #endif
  31 #ifdef HAVE_STDIO_H
  32 #include <stdio.h>
  33 #endif
  34 #ifdef HAVE_STRING_H
  35 #include <string.h>
  36 #endif
  37 #ifdef HAVE_ERRNO_H
  38 #include <errno.h>
  39 #endif
  40 #ifdef HAVE_LOCALE_H
  41 #include <locale.h>
  42 #endif
  43 #ifdef HAVE_GETOPT_H
  44 #include <getopt.h>
  45 #endif
  46 
  47 #include "compat.h"
  48 #include "debug.h"
  49 #include "mft.h"
  50 #include "utils.h"
  51 #include "version.h"
  52 #include "logging.h"
  53 
  54 static const char *EXEC_NAME = "ntfslabel";
  55 
  56 static struct options {
  57         char    *device;        /* Device/File to work with */
  58         char    *label;         /* Set the label to this */
  59         int      quiet;         /* Less output */
  60         int      verbose;       /* Extra output */
  61         int      force;         /* Override common sense */
  62         int      noaction;      /* Do not write to disk */
  63 } opts;
  64 
  65 /**
  66  * version - Print version information about the program
  67  *
  68  * Print a copyright statement and a brief description of the program.
  69  *
  70  * Return:  none
  71  */
  72 static void version(void)
  73 {
  74         ntfs_log_info("\n%s v%s (libntfs %s) - Display, or set, the label for an "
  75                         "NTFS Volume.\n\n", EXEC_NAME, VERSION,
  76                         ntfs_libntfs_version());
  77         ntfs_log_info("Copyright (c)\n");
  78         ntfs_log_info("    2002      Matthew J. Fanto\n");
  79         ntfs_log_info("    2002-2005 Anton Altaparmakov\n");
  80         ntfs_log_info("    2002-2003 Richard Russon\n");
  81         ntfs_log_info("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
  82 }
  83 
  84 /**
  85  * usage - Print a list of the parameters to the program
  86  *
  87  * Print a list of the parameters and options for the program.
  88  *
  89  * Return:  none
  90  */
  91 static void usage(void)
  92 {
  93         ntfs_log_info("\nUsage: %s [options] device [label]\n"
  94                "    -n, --no-action    Do not write to disk\n"
  95                "    -f, --force        Use less caution\n"
  96                "    -q, --quiet        Less output\n"
  97                "    -v, --verbose      More output\n"
  98                "    -V, --version      Display version information\n"
  99                "    -h, --help         Display this help\n\n",
 100                EXEC_NAME);
 101         ntfs_log_info("%s%s\n", ntfs_bugs, ntfs_home);
 102 }
 103 
 104 /**
 105  * parse_options - Read and validate the programs command line
 106  *
 107  * Read the command line, verify the syntax and parse the options.
 108  * This function is very long, but quite simple.
 109  *
 110  * Return:  1 Success
 111  *          0 Error, one or more problems
 112  */
 113 static int parse_options(int argc, char *argv[])
 114 {
 115         static const char *sopt = "-fh?nqvV";
 116         static const struct option lopt[] = {
 117                 { "force",       no_argument,           NULL, 'f' },
 118                 { "help",        no_argument,           NULL, 'h' },
 119                 { "no-action",   no_argument,           NULL, 'n' },
 120                 { "quiet",       no_argument,           NULL, 'q' },
 121                 { "verbose",     no_argument,           NULL, 'v' },
 122                 { "version",     no_argument,           NULL, 'V' },
 123                 { NULL, 0, NULL, 0 },
 124         };
 125 
 126         int c = -1;
 127         int err  = 0;
 128         int ver  = 0;
 129         int help = 0;
 130         int levels = 0;
 131 
 132         opterr = 0; /* We'll handle the errors, thank you. */
 133 
 134         while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) {
 135                 switch (c) {
 136                 case 1: /* A non-option argument */
 137                         if (!err && !opts.device)
 138                                 opts.device = argv[optind-1];
 139                         else if (!err && !opts.label)
 140                                 opts.label = argv[optind-1];
 141                         else
 142                                 err++;
 143                         break;
 144                 case 'f':
 145                         opts.force++;
 146                         break;
 147                 case 'h':
 148                 case '?':
 149                         if (strncmp (argv[optind-1], "--log-", 6) == 0) {
 150                                 if (!ntfs_log_parse_option (argv[optind-1]))
 151                                         err++;
 152                                 break;
 153                         }
 154                         help++;
 155                         break;
 156                 case 'n':
 157                         opts.noaction++;
 158                         break;
 159                 case 'q':
 160                         opts.quiet++;
 161                         ntfs_log_clear_levels(NTFS_LOG_LEVEL_QUIET);
 162                         break;
 163                 case 'v':
 164                         opts.verbose++;
 165                         ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE);
 166                         break;
 167                 case 'V':
 168                         ver++;
 169                         break;
 170                 default:
 171                         ntfs_log_error("Unknown option '%s'.\n", argv[optind-1]);
 172                         err++;
 173                         break;
 174                 }
 175         }
 176 
 177         /* Make sure we're in sync with the log levels */
 178         levels = ntfs_log_get_levels();
 179         if (levels & NTFS_LOG_LEVEL_VERBOSE)
 180                 opts.verbose++;
 181         if (!(levels & NTFS_LOG_LEVEL_QUIET))
 182                 opts.quiet++;
 183 
 184         if (help || ver) {
 185                 opts.quiet = 0;
 186         } else {
 187                 if (opts.device == NULL) {
 188                         if (argc > 1)
 189                                 ntfs_log_error("You must specify a device.\n");
 190                         err++;
 191                 }
 192 
 193                 if (opts.quiet && opts.verbose) {
 194                         ntfs_log_error("You may not use --quiet and --verbose at "
 195                                         "the same time.\n");
 196                         err++;
 197                 }
 198         }
 199 
 200         if (ver)
 201                 version();
 202         if (help || err)
 203                 usage();
 204 
 205         return (!err && !help && !ver);
 206 }
 207 
 208 
 209 /**
 210  * print_label - display the current label of a mounted ntfs partition.
 211  * @dev:        device to read the label from
 212  * @mnt_flags:  mount flags of the device or 0 if not mounted
 213  * @mnt_point:  mount point of the device or NULL
 214  *
 215  * Print the label of the device @dev.
 216  */
 217 static int print_label(ntfs_volume *vol, unsigned long mnt_flags)
 218 {
 219         int result = 0;
 220         //XXX significant?
 221         if ((mnt_flags & (NTFS_MF_MOUNTED | NTFS_MF_READONLY)) ==
 222                         NTFS_MF_MOUNTED) {
 223                 ntfs_log_error("%s is mounted read-write, results may be "
 224                         "unreliable.\n", opts.device);
 225                 result = 1;
 226         }
 227 
 228         ntfs_log_info("%s\n", vol->vol_name);
 229         return result;
 230 }
 231 
 232 /**
 233  * resize_resident_attribute_value - resize a resident attribute
 234  * @m:          mft record containing attribute to resize
 235  * @a:          attribute record (inside @m) which to resize
 236  * @new_vsize:  the new attribute value size to resize the attribute to
 237  *
 238  * Return 0 on success and -1 with errno = ENOSPC if not enough space in the
 239  * mft record.
 240  */
 241 static int resize_resident_attribute_value(MFT_RECORD *m, ATTR_RECORD *a,
 242                 const u32 new_vsize)
 243 {
 244         int new_alen, new_muse;
 245 
 246         /* New attribute length and mft record bytes used. */
 247         new_alen = (le16_to_cpu(a->u.res.value_offset) + new_vsize + 7) & ~7;
 248         new_muse = le32_to_cpu(m->bytes_in_use) - le32_to_cpu(a->length) +
 249                         new_alen;
 250         /* Check for sufficient space. */
 251         if ((u32)new_muse > le32_to_cpu(m->bytes_allocated)) {
 252                 errno = ENOSPC;
 253                 return -1;
 254         }
 255         /* Move attributes behind @a to their new location. */
 256         memmove((char*)a + new_alen, (char*)a + le32_to_cpu(a->length),
 257                         le32_to_cpu(m->bytes_in_use) - ((char*)a - (char*)m) -
 258                         le32_to_cpu(a->length));
 259         /* Adjust @m to reflect change in used space. */
 260         m->bytes_in_use = cpu_to_le32(new_muse);
 261         /* Adjust @a to reflect new value size. */
 262         a->length = cpu_to_le32(new_alen);
 263         a->u.res.value_length = cpu_to_le32(new_vsize);
 264         return 0;
 265 }
 266 
 267 /**
 268  * change_label - change the current label on a device
 269  * @dev:        device to change the label on
 270  * @mnt_flags:  mount flags of the device or 0 if not mounted
 271  * @mnt_point:  mount point of the device or NULL
 272  * @label:      the new label
 273  *
 274  * Change the label on the device @dev to @label.
 275  */
 276 static int change_label(ntfs_volume *vol, unsigned long mnt_flags, char *label, BOOL force)
 277 {
 278         ntfs_attr_search_ctx *ctx;
 279         ntfschar *new_label = NULL;
 280         ATTR_RECORD *a;
 281         int label_len;
 282         int result = 0;
 283 
 284         //XXX significant?
 285         if (mnt_flags & NTFS_MF_MOUNTED) {
 286                 /* If not the root fs or mounted read/write, refuse change. */
 287                 if (!(mnt_flags & NTFS_MF_ISROOT) ||
 288                                 !(mnt_flags & NTFS_MF_READONLY)) {
 289                         if (!force) {
 290                                 ntfs_log_error("Refusing to change label on "
 291                                                 "read-%s mounted device %s.\n",
 292                                                 mnt_flags & NTFS_MF_READONLY ?
 293                                                 "only" : "write", opts.device);
 294                                 return 1;
 295                         }
 296                 }
 297         }
 298         ctx = ntfs_attr_get_search_ctx(vol->vol_ni, NULL);
 299         if (!ctx) {
 300                 ntfs_log_perror("Failed to get attribute search context");
 301                 goto err_out;
 302         }
 303         if (ntfs_attr_lookup(AT_VOLUME_NAME, AT_UNNAMED, 0, 0, 0, NULL, 0,
 304                         ctx)) {
 305                 if (errno != ENOENT) {
 306                         ntfs_log_perror("Lookup of $VOLUME_NAME attribute failed");
 307                         goto err_out;
 308                 }
 309                 /* The volume name attribute does not exist.  Need to add it. */
 310                 a = NULL;
 311         } else {
 312                 a = ctx->attr;
 313                 if (a->non_resident) {
 314                         ntfs_log_error("Error: Attribute $VOLUME_NAME must be "
 315                                         "resident.\n");
 316                         goto err_out;
 317                 }
 318         }
 319         label_len = ntfs_mbstoucs(label, &new_label, 0);
 320         if (label_len == -1) {
 321                 ntfs_log_perror("Unable to convert label string to Unicode");
 322                 goto err_out;
 323         }
 324         label_len *= sizeof(ntfschar);
 325         if (label_len > 0x100) {
 326                 ntfs_log_error("New label is too long. Maximum %u characters "
 327                                 "allowed. Truncating excess characters.\n",
 328                                 (unsigned)(0x100 / sizeof(ntfschar)));
 329                 label_len = 0x100;
 330                 new_label[label_len / sizeof(ntfschar)] = 0;
 331         }
 332         if (a) {
 333                 if (resize_resident_attribute_value(ctx->mrec, a, label_len)) {
 334                         ntfs_log_perror("Error resizing resident attribute");
 335                         goto err_out;
 336                 }
 337         } else {
 338                 /* sizeof(resident attribute record header) == 24 */
 339                 int asize = (24 + label_len + 7) & ~7;
 340                 u32 biu = le32_to_cpu(ctx->mrec->bytes_in_use);
 341                 if (biu + asize > le32_to_cpu(ctx->mrec->bytes_allocated)) {
 342                         errno = ENOSPC;
 343                         ntfs_log_perror("Error adding resident attribute");
 344                         goto err_out;
 345                 }
 346                 a = ctx->attr;
 347                 memmove((u8*)a + asize, a, biu - ((u8*)a - (u8*)ctx->mrec));
 348                 ctx->mrec->bytes_in_use = cpu_to_le32(biu + asize);
 349                 a->type = AT_VOLUME_NAME;
 350                 a->length = cpu_to_le32(asize);
 351                 a->non_resident = 0;
 352                 a->name_length = 0;
 353                 a->name_offset = cpu_to_le16(24);
 354                 a->flags = cpu_to_le16(0);
 355                 a->instance = ctx->mrec->next_attr_instance;
 356                 ctx->mrec->next_attr_instance = cpu_to_le16((le16_to_cpu(
 357                                 ctx->mrec->next_attr_instance) + 1) & 0xffff);
 358                 a->u.res.value_length = cpu_to_le32(label_len);
 359                 a->u.res.value_offset = a->name_offset;
 360                 a->u.res.resident_flags = 0;
 361                 a->u.res.reservedR = 0;
 362         }
 363         memcpy((u8*)a + le16_to_cpu(a->u.res.value_offset), new_label, label_len);
 364         if (!opts.noaction && ntfs_inode_sync(vol->vol_ni)) {
 365                 ntfs_log_perror("Error writing MFT Record to disk");
 366                 goto err_out;
 367         }
 368         result = 0;
 369 err_out:
 370         free(new_label);
 371         return result;
 372 }
 373 
 374 /**
 375  * main - Begin here
 376  *
 377  * Start from here.
 378  *
 379  * Return:  0  Success, the program worked
 380  *          1  Error, something went wrong
 381  */
 382 int main(int argc, char **argv)
 383 {
 384         unsigned long mnt_flags = 0;
 385         int result = 0;
 386         ntfs_volume *vol;
 387 
 388         ntfs_log_set_handler(ntfs_log_handler_outerr);
 389 
 390         if (!parse_options(argc, argv))
 391                 return 1;
 392 
 393         utils_set_locale();
 394 
 395         if (!opts.label)
 396                 opts.noaction++;
 397 
 398         vol = utils_mount_volume(opts.device,
 399                         (opts.noaction ? NTFS_MNT_RDONLY : 0) |
 400                         (opts.force ? NTFS_MNT_FORCE : 0));
 401         if (!vol)
 402                 return 1;
 403 
 404         if (opts.label)
 405                 result = change_label(vol, mnt_flags, opts.label, opts.force);
 406         else
 407                 result = print_label(vol, mnt_flags);
 408 
 409         ntfs_umount(vol, FALSE);
 410         return result;
 411 }
 412