1/*
2  File: setfattr.c
3  (Linux Extended Attributes)
4
5  Copyright (C) 2001-2002 Andreas Gruenbacher <a.gruenbacher@computer.org>
6  Copyright (C) 2001-2002 Silicon Graphics, Inc.  All Rights Reserved.
7
8  This program is free software; you can redistribute it and/or
9  modify it under the terms of the GNU Library General Public
10  License as published by the Free Software Foundation; either
11  version 2 of the License, or (at your option) any later version.
12
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  Library General Public License for more details.
17
18  You should have received a copy of the GNU Library General Public
19  License along with this library; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21*/
22
23#include <limits.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <getopt.h>
28#include <locale.h>
29#include <ctype.h>
30
31#include <attr/xattr.h>
32#include "config.h"
33#include "misc.h"
34
35#define CMD_LINE_OPTIONS "n:x:v:h"
36#define CMD_LINE_SPEC "{-n name|-x name} [-v value] [-h] file..."
37
38struct option long_options[] = {
39	{ "name",		1, 0, 'n' },
40	{ "remove",		1, 0, 'x' },
41	{ "value",		1, 0, 'v' },
42	{ "no-dereference",	0, 0, 'h' },
43	{ "restore",		1, 0, 'B' },
44	{ "version",		0, 0, 'V' },
45	{ "help",		0, 0, 'H' },
46	{ NULL,			0, 0, 0 }
47};
48
49char *opt_name;  /* attribute name to set */
50char *opt_value;  /* attribute value */
51int opt_set;  /* set an attribute */
52int opt_remove;  /* remove an attribute */
53int opt_restore;  /* restore has been run */
54int opt_deref = 1;  /* dereference symbolic links */
55
56int had_errors;
57const char *progname;
58
59int do_set(const char *path, const char *name, const char *value);
60const char *decode(const char *value, size_t *size);
61int restore(const char *filename);
62char *next_line(FILE *file);
63int hex_digit(char c);
64int base64_digit(char c);
65
66const char *strerror_ea(int err)
67{
68	if (err == ENODATA)
69		return _("No such attribute");
70	return strerror(err);
71}
72
73static const char *xquote(const char *str)
74{
75	const char *q = quote(str);
76	if (q == NULL) {
77		fprintf(stderr, "%s: %s\n", progname, strerror(errno));
78		exit(1);
79	}
80	return q;
81}
82
83int do_setxattr(const char *path, const char *name,
84		const void *value, size_t size)
85{
86	return (opt_deref ? setxattr : lsetxattr)(path, name, value, size, 0);
87}
88
89int do_removexattr(const char *path, const char *name)
90{
91	return (opt_deref ? removexattr : lremovexattr)(path, name);
92}
93
94int restore(const char *filename)
95{
96	static char *path;
97	static size_t path_size;
98	FILE *file;
99	char *l;
100	int line = 0, backup_line, status = 0;
101
102	if (strcmp(filename, "-") == 0)
103		file = stdin;
104	else {
105		file = fopen(filename, "r");
106		if (file == NULL) {
107				fprintf(stderr, "%s: %s: %s\n",
108					progname, filename, strerror_ea(errno));
109				return 1;
110		}
111	}
112
113	for(;;) {
114		backup_line = line;
115		while ((l = next_line(file)) != NULL && *l == '\0')
116			line++;
117		if (l == NULL)
118			break;
119		line++;
120		if (strncmp(l, "# file: ", 8) != 0) {
121			if (filename) {
122				fprintf(stderr, _("%s: %s: No filename found "
123				                  "in line %d, aborting\n"),
124					progname, filename, backup_line);
125			} else {
126				fprintf(stderr, _("%s: No filename found in"
127			                          "line %d of standard input, "
128						  "aborting\n"),
129					  progname, backup_line);
130			}
131			status = 1;
132			goto cleanup;
133		} else
134			l += 8;
135		l = unquote(l);
136		if (high_water_alloc((void **)&path, &path_size, strlen(l)+1)) {
137			perror(progname);
138			status = 1;
139			goto cleanup;
140		}
141		strcpy(path, l);
142
143		while ((l = next_line(file)) != NULL && *l != '\0') {
144			char *name = l, *value = strchr(l, '=');
145			line++;
146			if (value)
147				*value++ = '\0';
148			status = do_set(path, unquote(name), value);
149		}
150		if (l != NULL)
151			line++;
152	}
153
154cleanup:
155	if (path)
156		free(path);
157	if (file != stdin)
158		fclose(file);
159	if (status)
160		had_errors++;
161	return status;
162}
163
164void help(void)
165{
166	printf(_("%s %s -- set extended attributes\n"), progname, VERSION);
167	printf(_("Usage: %s %s\n"), progname, CMD_LINE_SPEC);
168	printf(_(
169"  -n, --name=name         set the value of the named extended attribute\n"
170"  -x, --remove=name       remove the named extended attribute\n"
171"  -v, --value=value       use value as the attribute value\n"
172"  -h, --no-dereference    do not dereference symbolic links\n"
173"      --restore=file      restore extended attributes\n"
174"      --version           print version and exit\n"
175"      --help              this help text\n"));
176}
177
178char *next_line(FILE *file)
179{
180	static char line[_POSIX_PATH_MAX+32], *c;
181	if (!fgets(line, sizeof(line), file))
182		return NULL;
183
184	c = strrchr(line, '\0');
185	while (c > line && (*(c-1) == '\n' ||
186			   *(c-1) == '\r')) {
187		c--;
188		*c = '\0';
189	}
190	return line;
191}
192
193int main(int argc, char *argv[])
194{
195	int opt;
196
197	progname = basename(argv[0]);
198
199	setlocale(LC_CTYPE, "");
200	setlocale(LC_MESSAGES, "");
201	bindtextdomain(PACKAGE, LOCALEDIR);
202	textdomain(PACKAGE);
203
204	while ((opt = getopt_long(argc, argv, CMD_LINE_OPTIONS,
205		                  long_options, NULL)) != -1) {
206		switch(opt) {
207			case 'n':  /* attribute name */
208				if (opt_name || opt_remove)
209					goto synopsis;
210				opt_name = optarg;
211				opt_set = 1;
212				break;
213
214			case 'h':  /* set attribute on symlink itself */
215				opt_deref = 0;
216				break;
217
218			case 'v':  /* attribute value */
219				if (opt_value || opt_remove)
220					goto synopsis;
221				opt_value = optarg;
222				break;
223
224			case 'x':  /* remove attribute */
225				if (opt_name || opt_set)
226					goto synopsis;
227				opt_name = optarg;
228				opt_remove = 1;
229				break;
230
231			case 'B':  /* restore */
232				opt_restore = 1;
233				restore(optarg);
234				break;
235
236			case 'V':
237				printf("%s " VERSION "\n", progname);
238				return 0;
239
240			case 'H':
241				help();
242				return 0;
243
244			default:
245				goto synopsis;
246		}
247	}
248	if (!(((opt_remove || opt_set) && optind < argc) || opt_restore))
249		goto synopsis;
250
251	while (optind < argc) {
252		do_set(argv[optind], unquote(opt_name), opt_value);
253		optind++;
254	}
255
256	return (had_errors ? 1 : 0);
257
258synopsis:
259	fprintf(stderr, _("Usage: %s %s\n"
260	                  "Try `%s --help' for more information.\n"),
261		progname, CMD_LINE_SPEC, progname);
262	return 2;
263}
264
265int do_set(const char *path, const char *name, const char *value)
266{
267	size_t size = 0;
268	int error;
269
270	if (value) {
271		size = strlen(value);
272		value = decode(value, &size);
273		if (!value)
274			return 1;
275	}
276	if (opt_set || opt_restore)
277		error = do_setxattr(path, name, value, size);
278	else
279		error = do_removexattr(path, name);
280
281	if (error < 0) {
282		fprintf(stderr, "%s: %s: %s\n",
283			progname, xquote(path), strerror_ea(errno));
284		had_errors++;
285		return 1;
286	}
287	return 0;
288}
289
290const char *decode(const char *value, size_t *size)
291{
292	static char *decoded;
293	static size_t decoded_size;
294
295	if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) {
296		const char *v = value+2, *end = value + *size;
297		char *d;
298
299		if (high_water_alloc((void **)&decoded, &decoded_size,
300				     *size / 2)) {
301			fprintf(stderr, "%s: %s\n",
302				progname, strerror_ea(errno));
303			had_errors++;
304			return NULL;
305		}
306		d = decoded;
307		while (v < end) {
308			int d1, d0;
309
310			while (v < end && isspace(*v))
311				v++;
312			if (v == end)
313				break;
314			d1 = hex_digit(*v++);
315			while (v < end && isspace(*v))
316				v++;
317			if (v == end) {
318		bad_hex_encoding:
319				fprintf(stderr, "bad input encoding\n");
320				had_errors++;
321				return NULL;
322			}
323			d0 = hex_digit(*v++);
324			if (d1 < 0 || d0 < 0)
325				goto bad_hex_encoding;
326			*d++ = ((d1 << 4) | d0);
327		}
328		*size = d - decoded;
329	} else if (value[0] == '0' && (value[1] == 's' || value[1] == 'S')) {
330		const char *v = value+2, *end = value + *size;
331		int d0, d1, d2, d3;
332		char *d;
333
334		if (high_water_alloc((void **)&decoded, &decoded_size,
335				     *size / 4 * 3)) {
336			fprintf(stderr, "%s: %s\n",
337				progname, strerror_ea(errno));
338			had_errors++;
339			return NULL;
340		}
341		d = decoded;
342		for(;;) {
343			while (v < end && isspace(*v))
344				v++;
345			if (v == end) {
346				d0 = d1 = d2 = d3 = -2;
347				break;
348			}
349			if (v + 4 > end) {
350		bad_base64_encoding:
351				fprintf(stderr, "bad input encoding\n");
352				had_errors++;
353				return NULL;
354			}
355			d0 = base64_digit(*v++);
356			d1 = base64_digit(*v++);
357			d2 = base64_digit(*v++);
358			d3 = base64_digit(*v++);
359			if (d0 < 0 || d1 < 0 || d2 < 0 || d3 < 0)
360				break;
361
362			*d++ = (char)((d0 << 2) | (d1 >> 4));
363			*d++ = (char)((d1 << 4) | (d2 >> 2));
364			*d++ = (char)((d2 << 6) | d3);
365		}
366		if (d0 == -2) {
367			if (d1 != -2 || d2 != -2 || d3 != -2)
368				goto bad_base64_encoding;
369			goto base64_end;
370		}
371		if (d0 == -1 || d1 < 0 || d2 == -1 || d3 == -1)
372			goto bad_base64_encoding;
373		*d++ = (char)((d0 << 2) | (d1 >> 4));
374		if (d2 != -2)
375			*d++ = (char)((d1 << 4) | (d2 >> 2));
376		else {
377			if (d1 & 0x0F || d3 != -2)
378				goto bad_base64_encoding;
379			goto base64_end;
380		}
381		if (d3 != -2)
382			*d++ = (char)((d2 << 6) | d3);
383		else if (d2 & 0x03)
384			goto bad_base64_encoding;
385	base64_end:
386		while (v < end && isspace(*v))
387			v++;
388		if (v + 4 <= end && *v == '=') {
389			if (*++v != '=' || *++v != '=' || *++v != '=')
390				goto bad_base64_encoding;
391			v++;
392		}
393		while (v < end && isspace(*v))
394			v++;
395		if (v < end)
396			goto bad_base64_encoding;
397		*size = d - decoded;
398	} else {
399		const char *v = value, *end = value + *size;
400		char *d;
401
402		if (end > v+1 && *v == '"' && *(end-1) == '"') {
403			v++;
404			end--;
405		}
406
407		if (high_water_alloc((void **)&decoded, &decoded_size, *size)) {
408			fprintf(stderr, "%s: %s\n",
409				progname, strerror_ea(errno));
410			had_errors++;
411			return NULL;
412		}
413		d = decoded;
414
415		while (v < end) {
416			if (v[0] == '\\') {
417				if (v[1] == '\\' || v[1] == '"') {
418					*d++ = *++v; v++;
419				} else if (v[1] >= '0' && v[1] <= '7') {
420					int c = 0;
421					v++;
422					c = (*v++ - '0');
423					if (*v >= '0' && *v <= '7')
424						c = (c << 3) + (*v++ - '0');
425					if (*v >= '0' && *v <= '7')
426						c = (c << 3) + (*v++ - '0');
427					*d++ = c;
428				} else
429					*d++ = *v++;
430			} else
431				*d++ = *v++;
432		}
433		*size = d - decoded;
434	}
435	return decoded;
436}
437
438int hex_digit(char c)
439{
440	if (c >= '0' && c <= '9')
441		return c - '0';
442	else if (c >= 'A' && c <= 'F')
443		return c - 'A' + 10;
444	else if (c >= 'a' && c <= 'f')
445		return c - 'a' + 10;
446	else
447		return -1;
448}
449
450int base64_digit(char c)
451{
452	if (c >= 'A' && c <= 'Z')
453		return c - 'A';
454	else if (c >= 'a' && c <= 'z')
455		return 26 + c - 'a';
456	else if (c >= '0' && c <= '9')
457		return 52 + c - '0';
458	else if (c == '+')
459		return 62;
460	else if (c == '/')
461		return 63;
462	else if (c == '=')
463		return -2;
464	else
465		return -1;
466}
467
468