update changelog
[platform/upstream/acl.git] / getfacl / getfacl.c
1 /*
2   File: getfacl.c
3   (Linux Access Control List Management)
4
5   Copyright (C) 1999-2002
6   Andreas Gruenbacher, <a.gruenbacher@bestbits.at>
7         
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or (at
11   your option) any later version.
12
13   This program is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this library; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21   USA.
22 */
23
24 #include <stdio.h>
25 #include <errno.h>
26 #include <sys/acl.h>
27 #include <acl/libacl.h>
28
29 #include <limits.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <string.h>
33 #include <sys/stat.h>
34 #include <dirent.h>
35 #include <libgen.h>
36 #include <getopt.h>
37 #include <locale.h>
38 #include "config.h"
39 #include "user_group.h"
40 #include "walk_tree.h"
41 #include "misc.h"
42
43 #define POSIXLY_CORRECT_STR "POSIXLY_CORRECT"
44
45 #if !POSIXLY_CORRECT
46 #  define CMD_LINE_OPTIONS "aceEsRLPtpndvh"
47 #endif
48 #define POSIXLY_CMD_LINE_OPTIONS "d"
49
50 struct option long_options[] = {
51 #if !POSIXLY_CORRECT
52         { "access",     0, 0, 'a' },
53         { "omit-header",        0, 0, 'c' },
54         { "all-effective",      0, 0, 'e' },
55         { "no-effective",       0, 0, 'E' },
56         { "skip-base",  0, 0, 's' },
57         { "recursive",  0, 0, 'R' },
58         { "logical",    0, 0, 'L' },
59         { "physical",   0, 0, 'P' },
60         { "tabular",    0, 0, 't' },
61         { "absolute-names",     0, 0, 'p' },
62         { "numeric",    0, 0, 'n' },
63 #endif
64         { "default",    0, 0, 'd' },
65         { "version",    0, 0, 'v' },
66         { "help",       0, 0, 'h' },
67         { NULL,         0, 0, 0   }
68 };
69
70 const char *progname;
71 const char *cmd_line_options;
72
73 int walk_flags = WALK_TREE_DEREFERENCE_TOPLEVEL;
74 int opt_print_acl;
75 int opt_print_default_acl;
76 int opt_strip_leading_slash = 1;
77 int opt_comments = 1;  /* include comments */
78 int opt_skip_base;  /* skip files that only have the base entries */
79 int opt_tabular;  /* tabular output format (alias `showacl') */
80 #if POSIXLY_CORRECT
81 const int posixly_correct = 1;  /* Posix compatible behavior! */
82 #else
83 int posixly_correct;  /* Posix compatible behavior? */
84 #endif
85 int had_errors;
86 int absolute_warning;  /* Absolute path warning was issued */
87 int print_options = TEXT_SOME_EFFECTIVE;
88 int opt_numeric;  /* don't convert id's to symbolic names */
89
90
91 static const char *xquote(const char *str, const char *quote_chars)
92 {
93         const char *q = quote(str, quote_chars);
94         if (q == NULL) {
95                 fprintf(stderr, "%s: %s\n", progname, strerror(errno));
96                 exit(1);
97         }
98         return q;
99 }
100
101 struct name_list {
102         struct name_list *next;
103         char name[0];
104 };
105
106 void free_list(struct name_list *names)
107 {
108         struct name_list *next;
109
110         while (names) {
111                 next = names->next;
112                 free(names);
113                 names = next;
114         }
115 }
116
117 struct name_list *get_list(const struct stat *st, acl_t acl)
118 {
119         struct name_list *first = NULL, *last = NULL;
120         acl_entry_t ent;
121         int ret = 0;
122
123         if (acl != NULL)
124                 ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &ent);
125         if (ret != 1)
126                 return NULL;
127         while (ret > 0) {
128                 acl_tag_t e_type;
129                 const id_t *id_p;
130                 const char *name = "";
131                 int len;
132
133                 acl_get_tag_type(ent, &e_type);
134                 switch(e_type) {
135                         case ACL_USER_OBJ:
136                                 name = user_name(st->st_uid, opt_numeric);
137                                 break;
138
139                         case ACL_USER:
140                                 id_p = acl_get_qualifier(ent);
141                                 if (id_p != NULL)
142                                         name = user_name(*id_p, opt_numeric);
143                                 break;
144
145                         case ACL_GROUP_OBJ:
146                                 name = group_name(st->st_gid, opt_numeric);
147                                 break;
148
149                         case ACL_GROUP:
150                                 id_p = acl_get_qualifier(ent);
151                                 if (id_p != NULL)
152                                         name = group_name(*id_p, opt_numeric);
153                                 break;
154                 }
155                 name = xquote(name, "\t\n\r");
156                 len = strlen(name);
157                 if (last == NULL) {
158                         first = last = (struct name_list *)
159                                 malloc(sizeof(struct name_list) + len + 1);
160                 } else {
161                         last->next = (struct name_list *)
162                                 malloc(sizeof(struct name_list) + len + 1);
163                         last = last->next;
164                 }
165                 if (last == NULL) {
166                         free_list(first);
167                         return NULL;
168                 }
169                 last->next = NULL;
170                 strcpy(last->name, name);
171
172                 ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &ent);
173         }
174         return first;
175 }
176
177 int max_name_length(struct name_list *names)
178 {
179         int max_len = 0;
180         while (names != NULL) {
181                 struct name_list *next = names->next;
182                 int len = strlen(names->name);
183
184                 if (len > max_len)
185                         max_len = len;
186                 names = next;
187         }
188         return max_len;
189 }
190
191 int names_width;
192
193 struct acl_perm_def {
194         acl_tag_t       tag;
195         char            c;
196 };
197
198 struct acl_perm_def acl_perm_defs[] = {
199         { ACL_READ,     'r' },
200         { ACL_WRITE,    'w' },
201         { ACL_EXECUTE,  'x' },
202         { 0, 0 }
203 };
204
205 #define ACL_PERMS (sizeof(acl_perm_defs) / sizeof(struct acl_perm_def) - 1)
206
207 void acl_perm_str(acl_entry_t entry, char *str)
208 {
209         acl_permset_t permset;
210         int n;
211
212         acl_get_permset(entry, &permset);
213         for (n = 0; n < (int) ACL_PERMS; n++) {
214                 str[n] = (acl_get_perm(permset, acl_perm_defs[n].tag) ?
215                           acl_perm_defs[n].c : '-');
216         }
217         str[n] = '\0';
218 }
219
220 void acl_mask_perm_str(acl_t acl, char *str)
221 {
222         acl_entry_t entry;
223
224         str[0] = '\0';
225         if (acl_get_entry(acl, ACL_FIRST_ENTRY, &entry) != 1)
226                 return;
227         for(;;) {
228                 acl_tag_t tag;
229
230                 acl_get_tag_type(entry, &tag);
231                 if (tag == ACL_MASK) {
232                         acl_perm_str(entry, str);
233                         return;
234                 }
235                 if (acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) != 1)
236                         return;
237         }
238 }
239
240 void apply_mask(char *perm, const char *mask)
241 {
242         while (*perm) {
243                 if (*mask == '-' && *perm >= 'a' && *perm <= 'z')
244                         *perm = *perm - 'a' + 'A';
245                 perm++;
246                 if (*mask)
247                         mask++;
248         }
249 }
250
251 int show_line(FILE *stream, struct name_list **acl_names,  acl_t acl,
252               acl_entry_t *acl_ent, const char *acl_mask,
253               struct name_list **dacl_names, acl_t dacl,
254               acl_entry_t *dacl_ent, const char *dacl_mask)
255 {
256         acl_tag_t tag_type;
257         const char *tag, *name;
258         char acl_perm[ACL_PERMS+1], dacl_perm[ACL_PERMS+1];
259
260         if (acl) {
261                 acl_get_tag_type(*acl_ent, &tag_type);
262                 name = (*acl_names)->name;
263         } else {
264                 acl_get_tag_type(*dacl_ent, &tag_type);
265                 name = (*dacl_names)->name;
266         }
267
268         switch(tag_type) {
269                 case ACL_USER_OBJ:
270                         tag = "USER";
271                         break;
272                 case ACL_USER:
273                         tag = "user";
274                         break;
275                 case ACL_GROUP_OBJ:
276                         tag = "GROUP";
277                         break;
278                 case ACL_GROUP:
279                         tag = "group";
280                         break;
281                 case ACL_MASK:
282                         tag = "mask";
283                         break;
284                 case ACL_OTHER:
285                         tag = "other";
286                         break;
287                 default:
288                         return -1;
289         }
290
291         memset(acl_perm, ' ', ACL_PERMS);
292         acl_perm[ACL_PERMS] = '\0';
293         if (acl_ent) {
294                 acl_perm_str(*acl_ent, acl_perm);
295                 if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER &&
296                     tag_type != ACL_MASK)
297                         apply_mask(acl_perm, acl_mask);
298         }
299         memset(dacl_perm, ' ', ACL_PERMS);
300         dacl_perm[ACL_PERMS] = '\0';
301         if (dacl_ent) {
302                 acl_perm_str(*dacl_ent, dacl_perm);
303                 if (tag_type != ACL_USER_OBJ && tag_type != ACL_OTHER &&
304                     tag_type != ACL_MASK)
305                         apply_mask(dacl_perm, dacl_mask);
306         }
307
308         fprintf(stream, "%-5s  %*s  %*s  %*s\n",
309                 tag, -names_width, name,
310                 -(int)ACL_PERMS, acl_perm,
311                 -(int)ACL_PERMS, dacl_perm);
312
313         if (acl_names) {
314                 acl_get_entry(acl, ACL_NEXT_ENTRY, acl_ent);
315                 (*acl_names) = (*acl_names)->next;
316         }
317         if (dacl_names) {
318                 acl_get_entry(dacl, ACL_NEXT_ENTRY, dacl_ent);
319                 (*dacl_names) = (*dacl_names)->next;
320         }
321         return 0;
322 }
323
324 int do_show(FILE *stream, const char *path_p, const struct stat *st,
325             acl_t acl, acl_t dacl)
326 {
327         struct name_list *acl_names = get_list(st, acl),
328                          *first_acl_name = acl_names;
329         struct name_list *dacl_names = get_list(st, dacl),
330                          *first_dacl_name = dacl_names;
331         
332         int acl_names_width = max_name_length(acl_names);
333         int dacl_names_width = max_name_length(dacl_names);
334         acl_entry_t acl_ent;
335         acl_entry_t dacl_ent;
336         char acl_mask[ACL_PERMS+1], dacl_mask[ACL_PERMS+1];
337         int ret;
338
339         names_width = 8;
340         if (acl_names_width > names_width)
341                 names_width = acl_names_width;
342         if (dacl_names_width > names_width)
343                 names_width = dacl_names_width;
344
345         acl_mask[0] = '\0';
346         if (acl) {
347                 acl_mask_perm_str(acl, acl_mask);
348                 ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_ent);
349                 if (ret == 0)
350                         acl = NULL;
351                 if (ret < 0)
352                         return ret;
353         }
354         dacl_mask[0] = '\0';
355         if (dacl) {
356                 acl_mask_perm_str(dacl, dacl_mask);
357                 ret = acl_get_entry(dacl, ACL_FIRST_ENTRY, &dacl_ent);
358                 if (ret == 0)
359                         dacl = NULL;
360                 if (ret < 0)
361                         return ret;
362         }
363         fprintf(stream, "# file: %s\n", xquote(path_p, "\n\r"));
364         while (acl_names != NULL || dacl_names != NULL) {
365                 acl_tag_t acl_tag, dacl_tag;
366
367                 if (acl)
368                         acl_get_tag_type(acl_ent, &acl_tag);
369                 if (dacl)
370                         acl_get_tag_type(dacl_ent, &dacl_tag);
371
372                 if (acl && (!dacl || acl_tag < dacl_tag)) {
373                         show_line(stream, &acl_names, acl, &acl_ent, acl_mask,
374                                   NULL, NULL, NULL, NULL);
375                         continue;
376                 } else if (dacl && (!acl || dacl_tag < acl_tag)) {
377                         show_line(stream, NULL, NULL, NULL, NULL,
378                                   &dacl_names, dacl, &dacl_ent, dacl_mask);
379                         continue;
380                 } else {
381                         if (acl_tag == ACL_USER || acl_tag == ACL_GROUP) {
382                                 id_t  *acl_id_p = NULL, *dacl_id_p = NULL;
383                                 if (acl_ent)
384                                         acl_id_p = acl_get_qualifier(acl_ent);
385                                 if (dacl_ent)
386                                         dacl_id_p = acl_get_qualifier(dacl_ent);
387                                 
388                                 if (acl && (!dacl || *acl_id_p < *dacl_id_p)) {
389                                         show_line(stream, &acl_names, acl,
390                                                   &acl_ent, acl_mask,
391                                                   NULL, NULL, NULL, NULL);
392                                         continue;
393                                 } else if (dacl &&
394                                         (!acl || *dacl_id_p < *acl_id_p)) {
395                                         show_line(stream, NULL, NULL, NULL,
396                                                   NULL, &dacl_names, dacl,
397                                                   &dacl_ent, dacl_mask);
398                                         continue;
399                                 }
400                         }
401                         show_line(stream, &acl_names,  acl,  &acl_ent, acl_mask,
402                                   &dacl_names, dacl, &dacl_ent, dacl_mask);
403                 }
404         }
405
406         free_list(first_acl_name);
407         free_list(first_dacl_name);
408
409         return 0;
410 }
411
412 /*
413  * Create an ACL from the file permission bits
414  * of the file PATH_P.
415  */
416 static acl_t
417 acl_get_file_mode(const char *path_p)
418 {
419         struct stat st;
420
421         if (stat(path_p, &st) != 0)
422                 return NULL;
423         return acl_from_mode(st.st_mode);
424 }
425
426 static const char *
427 flagstr(mode_t mode)
428 {
429         static char str[4];
430
431         str[0] = (mode & S_ISUID) ? 's' : '-';
432         str[1] = (mode & S_ISGID) ? 's' : '-';
433         str[2] = (mode & S_ISVTX) ? 't' : '-';
434         str[3] = '\0';
435         return str;
436 }
437
438 int do_print(const char *path_p, const struct stat *st, int walk_flags, void *unused)
439 {
440         const char *default_prefix = NULL;
441         acl_t acl = NULL, default_acl = NULL;
442         int error = 0;
443
444         if (walk_flags & WALK_TREE_FAILED) {
445                 fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"),
446                         strerror(errno));
447                 return 1;
448         }
449
450         /*
451          * Symlinks can never have ACLs, so when doing a physical walk, we
452          * skip symlinks altogether, and when doing a half-logical walk, we
453          * skip all non-toplevel symlinks. 
454          */
455         if ((walk_flags & WALK_TREE_SYMLINK) &&
456             ((walk_flags & WALK_TREE_PHYSICAL) ||
457              !(walk_flags & (WALK_TREE_TOPLEVEL | WALK_TREE_LOGICAL))))
458                 return 0;
459
460         if (opt_print_acl) {
461                 acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
462                 if (acl == NULL && (errno == ENOSYS || errno == ENOTSUP))
463                         acl = acl_get_file_mode(path_p);
464                 if (acl == NULL)
465                         goto fail;
466         }
467
468         if (opt_print_default_acl && S_ISDIR(st->st_mode)) {
469                 default_acl = acl_get_file(path_p, ACL_TYPE_DEFAULT);
470                 if (default_acl == NULL) {
471                         if (errno != ENOSYS && errno != ENOTSUP)
472                                 goto fail;
473                 } else if (acl_entries(default_acl) == 0) {
474                         acl_free(default_acl);
475                         default_acl = NULL;
476                 }
477         }
478
479         if (opt_skip_base &&
480             (!acl || acl_equiv_mode(acl, NULL) == 0) && !default_acl)
481                 return 0;
482
483         if (opt_print_acl && opt_print_default_acl)
484                 default_prefix = "default:";
485
486         if (opt_strip_leading_slash) {
487                 if (*path_p == '/') {
488                         if (!absolute_warning) {
489                                 fprintf(stderr, _("%s: Removing leading "
490                                         "'/' from absolute path names\n"),
491                                         progname);
492                                 absolute_warning = 1;
493                         }
494                         while (*path_p == '/')
495                                 path_p++;
496                 } else if (*path_p == '.' && *(path_p+1) == '/')
497                         while (*++path_p == '/')
498                                 /* nothing */ ;
499                 if (*path_p == '\0')
500                         path_p = ".";
501         }
502
503         if (opt_tabular)  {
504                 if (do_show(stdout, path_p, st, acl, default_acl) != 0)
505                         goto fail;
506         } else {
507                 if (opt_comments) {
508                         printf("# file: %s\n", xquote(path_p, "\n\r"));
509                         printf("# owner: %s\n",
510                                xquote(user_name(st->st_uid, opt_numeric), " \t\n\r"));
511                         printf("# group: %s\n",
512                                xquote(group_name(st->st_gid, opt_numeric), " \t\n\r"));
513                         if ((st->st_mode & (S_ISVTX | S_ISUID | S_ISGID)) && !posixly_correct)
514                                 printf("# flags: %s\n", flagstr(st->st_mode));
515                 }
516                 if (acl != NULL) {
517                         char *acl_text = acl_to_any_text(acl, NULL, '\n',
518                                                          print_options);
519                         if (!acl_text)
520                                 goto fail;
521                         if (puts(acl_text) < 0) {
522                                 acl_free(acl_text);
523                                 goto fail;
524                         }
525                         acl_free(acl_text);
526                 }
527                 if (default_acl != NULL) {
528                         char *acl_text = acl_to_any_text(default_acl, 
529                                                          default_prefix, '\n',
530                                                          print_options);
531                         if (!acl_text)
532                                 goto fail;
533                         if (puts(acl_text) < 0) {
534                                 acl_free(acl_text);
535                                 goto fail;
536                         }
537                         acl_free(acl_text);
538                 }
539         }
540         if (acl || default_acl || opt_comments)
541                 printf("\n");
542
543 cleanup:
544         if (acl)
545                 acl_free(acl);
546         if (default_acl)
547                 acl_free(default_acl);
548         return error;
549
550 fail:
551         fprintf(stderr, "%s: %s: %s\n", progname, xquote(path_p, "\n\r"),
552                 strerror(errno));
553         error = -1;
554         goto cleanup;
555 }
556
557
558 void help(void)
559 {
560         printf(_("%s %s -- get file access control lists\n"),
561                progname, VERSION);
562         printf(_("Usage: %s [-%s] file ...\n"),
563                  progname, cmd_line_options);
564 #if !POSIXLY_CORRECT
565         if (posixly_correct) {
566 #endif
567                 printf(_(
568 "  -d, --default           display the default access control list\n"));
569 #if !POSIXLY_CORRECT
570         } else {
571                 printf(_(
572 "  -a,  --access           display the file access control list only\n"
573 "  -d, --default           display the default access control list only\n"
574 "  -c, --omit-header       do not display the comment header\n"
575 "  -e, --all-effective     print all effective rights\n"
576 "  -E, --no-effective      print no effective rights\n"
577 "  -s, --skip-base         skip files that only have the base entries\n"
578 "  -R, --recursive         recurse into subdirectories\n"
579 "  -L, --logical           logical walk, follow symbolic links\n"
580 "  -P, --physical          physical walk, do not follow symbolic links\n"
581 "  -t, --tabular           use tabular output format\n"
582 "  -n, --numeric           print numeric user/group identifiers\n"
583 "  -p, --absolute-names    don't strip leading '/' in pathnames\n"));
584         }
585 #endif
586         printf(_(
587 "  -v, --version           print version and exit\n"
588 "  -h, --help              this help text\n"));
589 }
590
591 int main(int argc, char *argv[])
592 {
593         int opt;
594         char *line;
595
596         progname = basename(argv[0]);
597
598 #if POSIXLY_CORRECT
599         cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
600 #else
601         if (getenv(POSIXLY_CORRECT_STR))
602                 posixly_correct = 1;
603         if (!posixly_correct)
604                 cmd_line_options = CMD_LINE_OPTIONS;
605         else
606                 cmd_line_options = POSIXLY_CMD_LINE_OPTIONS;
607 #endif
608
609         setlocale(LC_CTYPE, "");
610         setlocale(LC_MESSAGES, "");
611         bindtextdomain(PACKAGE, LOCALEDIR);
612         textdomain(PACKAGE);
613
614         /* Align `#effective:' comments to column 40 for tty's */
615         if (!posixly_correct && isatty(fileno(stdout)))
616                 print_options |= TEXT_SMART_INDENT;
617
618         while ((opt = getopt_long(argc, argv, cmd_line_options,
619                                  long_options, NULL)) != -1) {
620                 switch (opt) {
621                         case 'a':  /* acl only */
622                                 if (posixly_correct)
623                                         goto synopsis;
624                                 opt_print_acl = 1;
625                                 break;
626
627                         case 'd':  /* default acl only */
628                                 opt_print_default_acl = 1;
629                                 break;
630
631                         case 'c':  /* no comments */
632                                 if (posixly_correct)
633                                         goto synopsis;
634                                 opt_comments = 0;
635                                 break;
636
637                         case 'e':  /* all #effective comments */
638                                 if (posixly_correct)
639                                         goto synopsis;
640                                 print_options |= TEXT_ALL_EFFECTIVE;
641                                 break;
642
643                         case 'E':  /* no #effective comments */
644                                 if (posixly_correct)
645                                         goto synopsis;
646                                 print_options &= ~(TEXT_SOME_EFFECTIVE |
647                                                    TEXT_ALL_EFFECTIVE);
648                                 break;
649
650                         case 'R':  /* recursive */
651                                 if (posixly_correct)
652                                         goto synopsis;
653                                 walk_flags |= WALK_TREE_RECURSIVE;
654                                 break;
655
656                         case 'L':  /* follow all symlinks */
657                                 if (posixly_correct)
658                                         goto synopsis;
659                                 walk_flags |= WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE;
660                                 walk_flags &= ~WALK_TREE_PHYSICAL;
661                                 break;
662
663                         case 'P':  /* skip all symlinks */
664                                 if (posixly_correct)
665                                         goto synopsis;
666                                 walk_flags |= WALK_TREE_PHYSICAL;
667                                 walk_flags &= ~(WALK_TREE_LOGICAL | WALK_TREE_DEREFERENCE |
668                                                 WALK_TREE_DEREFERENCE_TOPLEVEL);
669                                 break;
670
671                         case 's':  /* skip files with only base entries */
672                                 if (posixly_correct)
673                                         goto synopsis;
674                                 opt_skip_base = 1;
675                                 break;
676
677                         case 'p':
678                                 if (posixly_correct)
679                                         goto synopsis;
680                                 opt_strip_leading_slash = 0;
681                                 break;
682
683                         case 't':
684                                 if (posixly_correct)
685                                         goto synopsis;
686                                 opt_tabular = 1;
687                                 break;
688
689                         case 'n':  /* numeric */
690                                 opt_numeric = 1;
691                                 print_options |= TEXT_NUMERIC_IDS;
692                                 break;
693
694                         case 'v':  /* print version */
695                                 printf("%s " VERSION "\n", progname);
696                                 return 0;
697
698                         case 'h':  /* help */
699                                 help();
700                                 return 0;
701
702                         case ':':  /* option missing */
703                         case '?':  /* unknown option */
704                         default:
705                                 goto synopsis;
706                 }
707         }
708
709         if (!(opt_print_acl || opt_print_default_acl)) {
710                 opt_print_acl = 1;
711                 if (!posixly_correct)
712                         opt_print_default_acl = 1;
713         }
714                 
715         if ((optind == argc) && !posixly_correct)
716                 goto synopsis;
717
718         do {
719                 if (optind == argc ||
720                     strcmp(argv[optind], "-") == 0) {
721                         while ((line = next_line(stdin)) != NULL) {
722                                 if (*line == '\0')
723                                         continue;
724
725                                 had_errors += walk_tree(line, walk_flags, 0,
726                                                         do_print, NULL);
727                         }
728                         if (!feof(stdin)) {
729                                 fprintf(stderr, _("%s: Standard input: %s\n"),
730                                         progname, strerror(errno));
731                                 had_errors++;
732                         }
733                 } else
734                         had_errors += walk_tree(argv[optind], walk_flags, 0,
735                                                 do_print, NULL);
736                 optind++;
737         } while (optind < argc);
738
739         return had_errors ? 1 : 0;
740
741 synopsis:
742         fprintf(stderr, _("Usage: %s [-%s] file ...\n"),
743                 progname, cmd_line_options);
744         fprintf(stderr, _("Try `%s --help' for more information.\n"),
745                 progname);
746         return 2;
747 }
748