1/*
2  File: getfattr.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 <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <errno.h>
27#include <ctype.h>
28#include <getopt.h>
29#include <regex.h>
30#include <ftw.h>
31#include <locale.h>
32
33#include <attr/xattr.h>
34#include "config.h"
35#include "misc.h"
36
37#define CMD_LINE_OPTIONS "n:de:m:hRLP"
38#define CMD_LINE_SPEC "[-hRLP] [-n name|-d] [-e en] [-m pattern] path..."
39
40struct option long_options[] = {
41	{ "name",		1, 0, 'n' },
42	{ "dump",		0, 0, 'd' },
43	{ "encoding",		1, 0, 'e' },
44	{ "match",		1, 0, 'm' },
45	{ "only-values",	0, 0, 'v' },
46	{ "no-dereference",	0, 0, 'h' },
47	{ "absolute-names",	0, 0, 'a' },
48	{ "recursive",		0, 0, 'R' },
49	{ "logical",		0, 0, 'L' },
50	{ "physical",		0, 0, 'P' },
51	{ "version",		0, 0, 'V' },
52	{ "help",		0, 0, 'H' },
53	{ NULL,			0, 0, 0 }
54};
55
56int opt_recursive;  /* recurse into sub-directories? */
57int opt_walk_logical;  /* always follow symbolic links */
58int opt_walk_physical;  /* never follow symbolic links */
59int opt_dump;  /* dump attribute values (or only list the names) */
60int opt_deref = 1;  /* dereference symbolic links */
61char *opt_name;  /* dump named attributes */
62char *opt_name_pattern = "^user\\.";  /* include only matching names */
63char *opt_encoding;  /* encode values automatically (NULL), or as "text",
64                        "hex", or "base64" */
65char opt_value_only;  /* dump the value only, without any decoration */
66int opt_strip_leading_slash = 1;  /* strip leading '/' from path names */
67
68const char *progname;
69int absolute_warning;
70int had_errors;
71regex_t name_regex;
72
73
74static const char *xquote(const char *str)
75{
76	const char *q = quote(str);
77	if (q == NULL) {
78		fprintf(stderr, "%s: %s\n", progname, strerror(errno));
79		exit(1);
80	}
81	return q;
82}
83
84int do_getxattr(const char *path, const char *name, void *value, size_t size)
85{
86	return (opt_deref ? getxattr : lgetxattr)(path, name, value, size);
87}
88
89int do_listxattr(const char *path, char *list, size_t size)
90{
91	return (opt_deref ? listxattr : llistxattr)(path, list, size);
92}
93
94const char *strerror_ea(int err)
95{
96	if (err == ENODATA)
97		return _("No such attribute");
98	return strerror(err);
99}
100
101int pstrcmp(const void *a, const void *b)
102{
103	return strcmp(*(const char **)a, *(const char **)b);
104}
105
106int well_enough_printable(const char *value, size_t size)
107{
108	size_t n, nonpr = 0;
109
110	for (n=0; n < size; n++)
111		if (!isprint(*value++))
112			nonpr++;
113
114	return (size >= nonpr*8);  /* no more than 1/8 non-printable chars */
115}
116
117const char *encode(const char *value, size_t *size)
118{
119	static char *encoded;
120	static size_t encoded_size;
121	char *enc, *e;
122
123	if (opt_encoding == NULL) {
124		if (well_enough_printable(value, *size))
125			enc = "text";
126		else
127			enc = "base64";
128	} else
129		enc = opt_encoding;
130
131	if (strcmp(enc, "text") == 0) {
132		size_t n, extra = 0;
133
134		for (e=(char *)value; e < value + *size; e++) {
135			if (!isprint(*e))
136				extra += 4;
137			else if (*e == '\\' || *e == '"')
138				extra++;
139		}
140		if (high_water_alloc((void **)&encoded, &encoded_size,
141				     *size + extra + 3)) {
142			perror(progname);
143			had_errors++;
144			return NULL;
145		}
146		e = encoded;
147		*e++='"';
148		for (n = 0; n < *size; n++, value++) {
149			if (!isprint(*value)) {
150				*e++ = '\\';
151				*e++ = '0' + ((unsigned char)*value >> 6);
152				*e++ = '0' + (((unsigned char)*value & 070) >> 3);
153				*e++ = '0' + ((unsigned char)*value & 07);
154			} else if (*value == '\\' || *value == '"') {
155				*e++ = '\\';
156				*e++ = *value;
157			} else {
158				*e++ = *value;
159			}
160		}
161		*e++ = '"';
162		*e = '\0';
163		*size = (e - encoded);
164	} else if (strcmp(enc, "hex") == 0) {
165		static const char *digits = "0123456789abcdef";
166		size_t n;
167
168		if (high_water_alloc((void **)&encoded, &encoded_size,
169				     *size * 2 + 4)) {
170			perror(progname);
171			had_errors++;
172			return NULL;
173		}
174		e = encoded;
175		*e++='0'; *e++ = 'x';
176		for (n = 0; n < *size; n++, value++) {
177			*e++ = digits[((unsigned char)*value >> 4)];
178			*e++ = digits[((unsigned char)*value & 0x0F)];
179		}
180		*e = '\0';
181		*size = (e - encoded);
182	} else if (strcmp(enc, "base64") == 0) {
183		static const char *digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
184					    "ghijklmnopqrstuvwxyz0123456789+/";
185		size_t n;
186
187		if (high_water_alloc((void **)&encoded, &encoded_size,
188				     (*size + 2) / 3 * 4 + 1)) {
189			perror(progname);
190			had_errors++;
191			return NULL;
192		}
193		e = encoded;
194		*e++='0'; *e++ = 's';
195		for (n=0; n + 2 < *size; n += 3) {
196			*e++ = digits[(unsigned char)value[0] >> 2];
197			*e++ = digits[(((unsigned char)value[0] & 0x03) << 4) |
198			              (((unsigned char)value[1] & 0xF0) >> 4)];
199			*e++ = digits[(((unsigned char)value[1] & 0x0F) << 2) |
200			              ((unsigned char)value[2] >> 6)];
201			*e++ = digits[(unsigned char)value[2] & 0x3F];
202			value += 3;
203		}
204		if (*size - n == 2) {
205			*e++ = digits[(unsigned char)value[0] >> 2];
206			*e++ = digits[(((unsigned char)value[0] & 0x03) << 4) |
207			              (((unsigned char)value[1] & 0xF0) >> 4)];
208			*e++ = digits[((unsigned char)value[1] & 0x0F) << 2];
209			*e++ = '=';
210		} else if (*size - n == 1) {
211			*e++ = digits[(unsigned char)value[0] >> 2];
212			*e++ = digits[((unsigned char)value[0] & 0x03) << 4];
213			*e++ = '=';
214			*e++ = '=';
215		}
216		*e = '\0';
217		*size = (e - encoded);
218	}
219	return encoded;
220}
221
222int print_attribute(const char *path, const char *name, int *header_printed)
223{
224	static char *value;
225	static size_t value_size;
226	ssize_t length = 0;
227
228	if (opt_dump || opt_value_only) {
229		length = do_getxattr(path, name, NULL, 0);
230		if (length < 0) {
231			fprintf(stderr, "%s: ", xquote(path));
232			fprintf(stderr, "%s: %s\n", xquote(name),
233				strerror_ea(errno));
234			return 1;
235		}
236		if (high_water_alloc((void **)&value, &value_size, length)) {
237			perror(progname);
238			had_errors++;
239			return 1;
240		}
241		length = do_getxattr(path, name, value, value_size);
242		if (length < 0) {
243			fprintf(stderr, "%s: ", xquote(path));
244			fprintf(stderr, "%s: %s\n", xquote(name),
245				strerror_ea(errno));
246			return 1;
247		}
248	}
249
250	if (opt_strip_leading_slash) {
251		if (*path == '/') {
252			if (!absolute_warning) {
253				fprintf(stderr, _("%s: Removing leading '/' "
254					"from absolute path names\n"),
255					progname);
256				absolute_warning = 1;
257			}
258			while (*path == '/')
259				path++;
260		} else if (*path == '.' && *(path+1) == '/')
261			while (*++path == '/')
262				/* nothing */ ;
263		if (*path == '\0')
264			path = ".";
265	}
266
267	if (!*header_printed && !opt_value_only) {
268		printf("# file: %s\n", xquote(path));
269		*header_printed = 1;
270	}
271
272	if (opt_value_only)
273		fwrite(value, length, 1, stdout);
274	else if (length) {
275		const char *enc = encode(value, &length);
276
277		if (enc)
278			printf("%s=%s\n", xquote(name), enc);
279	} else
280		puts(xquote(name));
281
282	return 0;
283}
284
285int list_attributes(const char *path, int *header_printed)
286{
287	static char *list;
288	static size_t list_size;
289	static char **names;
290	static size_t names_size;
291	int num_names = 0;
292	ssize_t length;
293	char *l;
294
295	length = do_listxattr(path, NULL, 0);
296	if (length < 0) {
297		fprintf(stderr, "%s: %s: %s\n", progname, xquote(path),
298			strerror_ea(errno));
299		had_errors++;
300		return 1;
301	} else if (length == 0)
302		return 0;
303
304	if (high_water_alloc((void **)&list, &list_size, length)) {
305		perror(progname);
306		had_errors++;
307		return 1;
308	}
309
310	length = do_listxattr(path, list, list_size);
311	if (length < 0) {
312		perror(xquote(path));
313		had_errors++;
314		return 1;
315	}
316
317	for (l = list; l != list + length; l = strchr(l, '\0')+1) {
318		if (*l == '\0')	/* not a name, kernel bug */
319			continue;
320
321		if (regexec(&name_regex, l, 0, NULL, 0) != 0)
322			continue;
323
324		if (names_size < (num_names+1) * sizeof(*names)) {
325			if (high_water_alloc((void **)&names, &names_size,
326				             (num_names+1) * sizeof(*names))) {
327				perror(progname);
328				had_errors++;
329				return 1;
330			}
331		}
332
333		names[num_names++] = l;
334	}
335
336	qsort(names, num_names, sizeof(*names), pstrcmp);
337
338	if (num_names) {
339		int n;
340
341		for (n = 0; n < num_names; n++)
342			print_attribute(path, names[n], header_printed);
343	}
344	return 0;
345}
346
347int do_print(const char *path, const struct stat *stat,
348             int flag, struct FTW *ftw)
349{
350	int header_printed = 0;
351
352	if (flag & FTW_DNR) {
353		/* Item is a directory which can't be read. */
354		fprintf(stderr, "%s: %s: %s\n", progname, xquote(path),
355			strerror(errno));
356		return 0;
357	}
358
359	/*
360	 * Process the target of a symbolic link, and traverse the
361	 * link, only if doing a logical walk, or if the symbolic link
362	 * was specified on the command line. Always skip symbolic
363	 * links if doing a physical walk.
364	 */
365
366	if (S_ISLNK(stat->st_mode) &&
367	    (opt_walk_physical || (ftw->level > 0 && !opt_walk_logical)))
368		return 0;
369
370	if (opt_name)
371		print_attribute(path, opt_name, &header_printed);
372	else
373		list_attributes(path, &header_printed);
374
375	if (header_printed)
376		puts("");
377
378	/*
379	 * We also get here in non-recursive mode. In that case,
380	 *  return something != 0 to abort nftw.
381	 */
382
383	if (!opt_recursive)
384		return 1;
385	return 0;
386}
387
388void help(void)
389{
390	printf(_("%s %s -- get extended attributes\n"),
391	       progname, VERSION);
392	printf(_("Usage: %s %s\n"),
393	         progname, _(CMD_LINE_SPEC));
394	printf(_(
395"  -n, --name=name         get the named extended attribute value\n"
396"  -d, --dump              get all extended attribute values\n"
397"  -e, --encoding=...      encode values (as 'text', 'hex' or 'base64')\n"
398"      --match=pattern     only get attributes with names matching pattern\n"
399"      --only-values       print the bare values only\n"
400"  -h, --no-dereference    do not dereference symbolic links\n"
401"      --absolute-names    don't strip leading '/' in pathnames\n"
402"  -R, --recursive         recurse into subdirectories\n"
403"  -L, --logical           logical walk, follow symbolic links\n"
404"  -P  --physical          physical walk, do not follow symbolic links\n"
405"      --version           print version and exit\n"
406"      --help              this help text\n"));
407}
408
409
410int main(int argc, char *argv[])
411{
412	int opt;
413
414	progname = basename(argv[0]);
415
416	setlocale(LC_CTYPE, "");
417	setlocale(LC_MESSAGES, "");
418	bindtextdomain(PACKAGE, LOCALEDIR);
419	textdomain(PACKAGE);
420
421	while ((opt = getopt_long(argc, argv, CMD_LINE_OPTIONS,
422		                  long_options, NULL)) != -1) {
423		switch(opt) {
424			case 'a': /* absolute names */
425				opt_strip_leading_slash = 0;
426				break;
427
428			case 'd': /* dump attribute values */
429				opt_dump = 1;
430				break;
431
432			case 'e':  /* encoding */
433				if (strcmp(optarg, "text") != 0 &&
434				    strcmp(optarg, "hex") != 0 &&
435				    strcmp(optarg, "base64") != 0)
436					goto synopsis;
437				opt_encoding = optarg;
438				break;
439
440			case 'H':
441				help();
442				return 0;
443
444			case 'h': /* do not dereference symlinks */
445				opt_deref = 0;
446				break;
447
448			case 'n':  /* get named attribute */
449				opt_dump = 1;
450				opt_name = optarg;
451				break;
452
453			case 'm':  /* regular expression for filtering names */
454				opt_name_pattern = optarg;
455				if (strcmp(opt_name_pattern, "-") == 0)
456					opt_name_pattern = "";
457				break;
458
459			case 'v':  /* get attribute values only */
460				opt_value_only = 1;
461				break;
462
463			case 'L':
464				opt_walk_logical = 1;
465				opt_walk_physical = 0;
466				break;
467
468			case 'P':
469				opt_walk_logical = 0;
470				opt_walk_physical = 1;
471				break;
472
473			case 'R':
474				opt_recursive = 1;
475				break;
476
477			case 'V':
478				printf("%s " VERSION "\n", progname);
479				return 0;
480
481			case ':':  /* option missing */
482			case '?':  /* unknown option */
483			default:
484				goto synopsis;
485		}
486	}
487	if (optind >= argc)
488		goto synopsis;
489
490	if (regcomp(&name_regex, opt_name_pattern,
491	            REG_EXTENDED | REG_NOSUB) != 0) {
492		fprintf(stderr, _("%s: invalid regular expression \"%s\"\n"),
493			progname, opt_name_pattern);
494		return 1;
495	}
496
497	while (optind < argc) {
498		if (nftw(argv[optind], do_print, 0,
499			 opt_walk_physical * FTW_PHYS) < 0) {
500			fprintf(stderr, "%s: %s: %s\n", progname, argv[optind],
501			        strerror_ea(errno));
502			had_errors++;
503		}
504		optind++;
505	}
506
507	return (had_errors ? 1 : 0);
508
509synopsis:
510	fprintf(stderr, _("Usage: %s %s\n"
511	                  "Try `%s --help' for more information.\n"),
512		progname, CMD_LINE_SPEC, progname);
513	return 2;
514}
515
516