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