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