enable login utils
[framework/base/util-linux-ng.git] / misc-utils / findmnt.c
1 /*
2  * findmnt(8)
3  *
4  * Copyright (C) 2010,2011 Red Hat, Inc. All rights reserved.
5  * Written by Karel Zak <kzak@redhat.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it would be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <string.h>
27 #include <termios.h>
28 #ifdef HAVE_SYS_IOCTL_H
29 #include <sys/ioctl.h>
30 #endif
31 #include <assert.h>
32 #include <poll.h>
33
34 #include <libmount.h>
35
36 #include "pathnames.h"
37 #include "nls.h"
38 #include "c.h"
39 #include "tt.h"
40 #include "strutils.h"
41
42 /* flags */
43 enum {
44         FL_EVALUATE     = (1 << 1),
45         FL_CANONICALIZE = (1 << 2),
46         FL_FIRSTONLY    = (1 << 3),
47         FL_INVERT       = (1 << 4),
48         FL_NOSWAPMATCH  = (1 << 6),
49         FL_NOFSROOT     = (1 << 7),
50         FL_SUBMOUNTS    = (1 << 8),
51         FL_POLL         = (1 << 9)
52 };
53
54 /* column IDs */
55 enum {
56         COL_SOURCE,
57         COL_TARGET,
58         COL_FSTYPE,
59         COL_OPTIONS,
60         COL_VFS_OPTIONS,
61         COL_FS_OPTIONS,
62         COL_LABEL,
63         COL_UUID,
64         COL_MAJMIN,
65         COL_ACTION,
66         COL_OLD_TARGET,
67         COL_OLD_OPTIONS,
68
69         FINDMNT_NCOLUMNS
70 };
71
72 /* column names */
73 struct colinfo {
74         const char      *name;          /* header */
75         double          whint;          /* width hint (N < 1 is in percent of termwidth) */
76         int             flags;          /* tt flags */
77         const char      *help;          /* column description */
78         const char      *match;         /* pattern for match_func() */
79 };
80
81 /* columns descriptions (don't use const, this is writable) */
82 static struct colinfo infos[FINDMNT_NCOLUMNS] = {
83         [COL_SOURCE]       = { "SOURCE",       0.25, 0, N_("source device") },
84         [COL_TARGET]       = { "TARGET",       0.30, TT_FL_TREE, N_("mountpoint") },
85         [COL_FSTYPE]       = { "FSTYPE",       0.10, TT_FL_TRUNC, N_("filesystem type") },
86         [COL_OPTIONS]      = { "OPTIONS",      0.10, TT_FL_TRUNC, N_("all mount options") },
87         [COL_VFS_OPTIONS]  = { "VFS-OPTIONS",  0.20, TT_FL_TRUNC, N_("VFS specific mount options") },
88         [COL_FS_OPTIONS]   = { "FS-OPTIONS",   0.10, TT_FL_TRUNC, N_("FS specific mount options") },
89         [COL_LABEL]        = { "LABEL",        0.10, 0, N_("filesystem label") },
90         [COL_UUID]         = { "UUID",           36, 0, N_("filesystem UUID") },
91         [COL_MAJMIN]       = { "MAJ:MIN",         6, 0, N_("major:minor device number") },
92         [COL_ACTION]       = { "ACTION",         10, TT_FL_STRICTWIDTH, N_("action detected by --poll") },
93         [COL_OLD_OPTIONS]  = { "OLD-OPTIONS",  0.10, TT_FL_TRUNC, N_("old mount options saved by --poll") },
94         [COL_OLD_TARGET]   = { "OLD-TARGET",   0.30, 0, N_("old mountpoint saved by --poll") },
95 };
96
97 /* global flags */
98 static int flags;
99 static int tt_flags;
100
101 /* array with IDs of enabled columns */
102 static int columns[FINDMNT_NCOLUMNS];
103 static int ncolumns;
104
105 /* poll actions (parsed --poll=<list> */
106 #define FINDMNT_NACTIONS        4               /* mount, umount, move, remount */
107 static int actions[FINDMNT_NACTIONS];
108 static int nactions;
109
110 /* libmount cache */
111 static struct libmnt_cache *cache;
112
113 static int get_column_id(int num)
114 {
115         assert(num < ncolumns);
116         assert(columns[num] < FINDMNT_NCOLUMNS);
117         return columns[num];
118 }
119
120 static struct colinfo *get_column_info(int num)
121 {
122         return &infos[ get_column_id(num) ];
123 }
124
125 static const char *column_id_to_name(int id)
126 {
127         assert(id < FINDMNT_NCOLUMNS);
128         return infos[id].name;
129 }
130
131 static const char *get_column_name(int num)
132 {
133         return get_column_info(num)->name;
134 }
135
136 static float get_column_whint(int num)
137 {
138         return get_column_info(num)->whint;
139 }
140
141 static int get_column_flags(int num)
142 {
143         return get_column_info(num)->flags;
144 }
145
146 static const char *get_match(int id)
147 {
148         assert(id < FINDMNT_NCOLUMNS);
149         return infos[id].match;
150 }
151
152 static void set_match(int id, const char *match)
153 {
154         assert(id < FINDMNT_NCOLUMNS);
155         infos[id].match = match;
156 }
157
158 static int is_tabdiff_column(int id)
159 {
160         assert(id < FINDMNT_NCOLUMNS);
161
162         switch(id) {
163         case COL_ACTION:
164         case COL_OLD_TARGET:
165         case COL_OLD_OPTIONS:
166                 return 1;
167         default:
168                 break;
169         }
170         return 0;
171 }
172
173 /*
174  * "findmnt" without any filter
175  */
176 static int is_listall_mode(void)
177 {
178         return (!get_match(COL_SOURCE) &&
179                 !get_match(COL_TARGET) &&
180                 !get_match(COL_FSTYPE) &&
181                 !get_match(COL_OPTIONS));
182 }
183
184 /*
185  * Returns 1 if the @act is in the --poll=<list>
186  */
187 static int has_poll_action(int act)
188 {
189         int i;
190
191         if (!nactions)
192                 return 1;       /* all actions enabled */
193         for (i = 0; i < nactions; i++)
194                 if (actions[i] == act)
195                         return 1;
196         return 0;
197 }
198
199 static int poll_action_name_to_id(const char *name, size_t namesz)
200 {
201         int id = -1;
202
203         if (strncasecmp(name, "move", namesz) == 0 && namesz == 4)
204                 id = MNT_TABDIFF_MOVE;
205         else if (strncasecmp(name, "mount", namesz) == 0 && namesz == 5)
206                 id = MNT_TABDIFF_MOUNT;
207         else if (strncasecmp(name, "umount", namesz) == 0 && namesz == 6)
208                 id = MNT_TABDIFF_UMOUNT;
209         else if (strncasecmp(name, "remount", namesz) == 0 && namesz == 7)
210                 id = MNT_TABDIFF_REMOUNT;
211         else
212                 warnx(_("unknown action: %s"), name);
213
214         return id;
215 }
216
217 /*
218  * findmnt --first-only <devname|TAG=|mountpoint>
219  *
220  * ... it works like "mount <devname|TAG=|mountpoint>"
221  */
222 static int is_mount_compatible_mode(void)
223 {
224         if (!get_match(COL_SOURCE))
225                return 0;                /* <devname|TAG=|mountpoint> is required */
226         if (get_match(COL_FSTYPE) || get_match(COL_OPTIONS))
227                 return 0;               /* cannot be restricted by -t or -O */
228         if (!(flags & FL_FIRSTONLY))
229                 return 0;               /* we have to return the first entry only */
230
231         return 1;                       /* ok */
232 }
233
234 static void disable_columns_truncate(void)
235 {
236         int i;
237
238         for (i = 0; i < FINDMNT_NCOLUMNS; i++)
239                 infos[i].flags &= ~TT_FL_TRUNC;
240 }
241
242 /*
243  * converts @name to column ID
244  */
245 static int column_name_to_id(const char *name, size_t namesz)
246 {
247         int i;
248
249         for (i = 0; i < FINDMNT_NCOLUMNS; i++) {
250                 const char *cn = column_id_to_name(i);
251
252                 if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
253                         return i;
254         }
255         warnx(_("unknown column: %s"), name);
256         return -1;
257 }
258
259 /* Returns LABEL or UUID */
260 static const char *get_tag(struct libmnt_fs *fs, const char *tagname)
261 {
262         const char *t, *v, *res;
263
264         if (!mnt_fs_get_tag(fs, &t, &v) && !strcmp(t, tagname))
265                 res = v;
266         else {
267                 res = mnt_fs_get_source(fs);
268                 if (res)
269                         res = mnt_resolve_spec(res, cache);
270                 if (res)
271                         res = mnt_cache_find_tag_value(cache, res, tagname);
272         }
273
274         return res;
275 }
276
277 /* reads FS data from libmount
278  * TODO: add function that will deallocate data allocated by get_data()
279  */
280 static const char *get_data(struct libmnt_fs *fs, int num)
281 {
282         const char *str = NULL;
283
284         switch(get_column_id(num)) {
285         case COL_SOURCE:
286         {
287                 const char *root = mnt_fs_get_root(fs);
288
289                 str = mnt_fs_get_srcpath(fs);
290
291                 if (str && (flags & FL_CANONICALIZE))
292                         str = mnt_resolve_path(str, cache);
293                 if (!str) {
294                         str = mnt_fs_get_source(fs);
295
296                         if (str && (flags & FL_EVALUATE))
297                                 str = mnt_resolve_spec(str, cache);
298                 }
299                 if (root && str && !(flags & FL_NOFSROOT) && strcmp(root, "/")) {
300                         char *tmp;
301
302                         if (asprintf(&tmp, "%s[%s]", str, root) > 0)
303                                 str = tmp;
304                 }
305                 break;
306         }
307         case COL_TARGET:
308                 str = mnt_fs_get_target(fs);
309                 break;
310         case COL_FSTYPE:
311                 str = mnt_fs_get_fstype(fs);
312                 break;
313         case COL_OPTIONS:
314                 str = mnt_fs_get_options(fs);
315                 break;
316         case COL_VFS_OPTIONS:
317                 str = mnt_fs_get_vfs_options(fs);
318                 break;
319         case COL_FS_OPTIONS:
320                 str = mnt_fs_get_fs_options(fs);
321                 break;
322         case COL_UUID:
323                 str = get_tag(fs, "UUID");
324                 break;
325         case COL_LABEL:
326                 str = get_tag(fs, "LABEL");
327                 break;
328         case COL_MAJMIN:
329         {
330                 dev_t devno = mnt_fs_get_devno(fs);
331                 if (devno) {
332                         char *tmp;
333                         int rc = 0;
334                         if ((tt_flags & TT_FL_RAW) || (tt_flags & TT_FL_EXPORT))
335                                 rc = asprintf(&tmp, "%u:%u",
336                                               major(devno), minor(devno));
337                         else
338                                 rc = asprintf(&tmp, "%3u:%-3u",
339                                               major(devno), minor(devno));
340                         if (rc)
341                                 str = tmp;
342                 }
343         }
344         default:
345                 break;
346         }
347         return str;
348 }
349
350 static const char *get_tabdiff_data(struct libmnt_fs *old_fs,
351                                     struct libmnt_fs *new_fs,
352                                     int change,
353                                     int num)
354 {
355         const char *str = NULL;
356
357         switch (get_column_id(num)) {
358         case COL_ACTION:
359                 switch (change) {
360                 case MNT_TABDIFF_MOUNT:
361                         str = _("mount");
362                         break;
363                 case MNT_TABDIFF_UMOUNT:
364                         str = _("umount");
365                         break;
366                 case MNT_TABDIFF_REMOUNT:
367                         str = _("remount");
368                         break;
369                 case MNT_TABDIFF_MOVE:
370                         str = _("move");
371                         break;
372                 default:
373                         str = _("unknown");
374                         break;
375                 }
376                 break;
377         case COL_OLD_OPTIONS:
378                 if (old_fs && (change == MNT_TABDIFF_REMOUNT ||
379                                change == MNT_TABDIFF_UMOUNT))
380                         str = mnt_fs_get_options(old_fs);
381                 break;
382         case COL_OLD_TARGET:
383                 if (old_fs && (change == MNT_TABDIFF_MOVE ||
384                                change == MNT_TABDIFF_UMOUNT))
385                         str = mnt_fs_get_target(old_fs);
386                 break;
387         default:
388                 if (new_fs)
389                         str = get_data(new_fs, num);
390                 else
391                         str = get_data(old_fs, num);
392                 break;
393         }
394         return str;
395 }
396
397 /* adds one line to the output @tab */
398 static struct tt_line *add_line(struct tt *tt, struct libmnt_fs *fs,
399                                         struct tt_line *parent)
400 {
401         int i;
402         struct tt_line *line = tt_add_line(tt, parent);
403
404         if (!line) {
405                 warn(_("failed to add line to output"));
406                 return NULL;
407         }
408         for (i = 0; i < ncolumns; i++)
409                 tt_line_set_data(line, i, get_data(fs, i));
410
411         tt_line_set_userdata(line, fs);
412         return line;
413 }
414
415 static struct tt_line *add_tabdiff_line(struct tt *tt, struct libmnt_fs *new_fs,
416                         struct libmnt_fs *old_fs, int change)
417 {
418         int i;
419         struct tt_line *line = tt_add_line(tt, NULL);
420
421         if (!line) {
422                 warn(_("failed to add line to output"));
423                 return NULL;
424         }
425         for (i = 0; i < ncolumns; i++)
426                 tt_line_set_data(line, i,
427                                 get_tabdiff_data(old_fs, new_fs, change, i));
428
429         return line;
430 }
431
432 static int has_line(struct tt *tt, struct libmnt_fs *fs)
433 {
434         struct list_head *p;
435
436         list_for_each(p, &tt->tb_lines) {
437                 struct tt_line *ln = list_entry(p, struct tt_line, ln_lines);
438                 if ((struct libmnt_fs *) ln->userdata == fs)
439                         return 1;
440         }
441         return 0;
442 }
443
444 /* reads filesystems from @tb (libmount) and fillin @tt (output table) */
445 static int create_treenode(struct tt *tt, struct libmnt_table *tb,
446                            struct libmnt_fs *fs, struct tt_line *parent_line)
447 {
448         struct libmnt_fs *chld = NULL;
449         struct libmnt_iter *itr = NULL;
450         struct tt_line *line;
451         int rc = -1;
452
453         if (!fs) {
454                 /* first call, get root FS */
455                 if (mnt_table_get_root_fs(tb, &fs))
456                         goto leave;
457                 parent_line = NULL;
458
459         } else if ((flags & FL_SUBMOUNTS) && has_line(tt, fs))
460                 return 0;
461
462         itr = mnt_new_iter(MNT_ITER_FORWARD);
463         if (!itr)
464                 goto leave;
465
466         line = add_line(tt, fs, parent_line);
467         if (!line)
468                 goto leave;
469
470         /*
471          * add all children to the output table
472          */
473         while(mnt_table_next_child_fs(tb, itr, fs, &chld) == 0) {
474                 if (create_treenode(tt, tb, chld, line))
475                         goto leave;
476         }
477         rc = 0;
478 leave:
479         mnt_free_iter(itr);
480         return rc;
481 }
482
483 /* error callback */
484 static int parser_errcb(struct libmnt_table *tb __attribute__ ((__unused__)),
485                         const char *filename, int line)
486 {
487         warn(_("%s: parse error at line %d"), filename, line);
488         return 0;
489 }
490
491 /* calls libmount fstab/mtab/mountinfo parser */
492 static struct libmnt_table *parse_tabfile(const char *path)
493 {
494         int rc;
495         struct libmnt_table *tb = mnt_new_table();
496
497         if (!tb) {
498                 warn(_("failed to initialize libmount table"));
499                 return NULL;
500         }
501
502         mnt_table_set_parser_errcb(tb, parser_errcb);
503
504         if (!strcmp(path, _PATH_MNTTAB))
505                 rc = mnt_table_parse_fstab(tb, NULL);
506         else if (!strcmp(path, _PATH_MOUNTED))
507                 rc = mnt_table_parse_mtab(tb, NULL);
508         else
509                 rc = mnt_table_parse_file(tb, path);
510
511         if (rc) {
512                 mnt_free_table(tb);
513                 warn(_("can't read %s"), path);
514                 return NULL;
515         }
516         return tb;
517 }
518
519 /* filter function for libmount (mnt_table_find_next_fs()) */
520 static int match_func(struct libmnt_fs *fs,
521                       void *data __attribute__ ((__unused__)))
522 {
523         int rc = flags & FL_INVERT ? 1 : 0;
524         const char *m;
525
526         m = get_match(COL_TARGET);
527         if (m && !mnt_fs_match_target(fs, m, cache))
528                 return rc;
529
530         m = get_match(COL_SOURCE);
531         if (m && !mnt_fs_match_source(fs, m, cache))
532                 return rc;
533
534         m = get_match(COL_FSTYPE);
535         if (m && !mnt_fs_match_fstype(fs, m))
536                 return rc;
537
538         m = get_match(COL_OPTIONS);
539         if (m && !mnt_fs_match_options(fs, m))
540                 return rc;
541
542         return !rc;
543 }
544
545 /* iterate over filesystems in @tb */
546 static struct libmnt_fs *get_next_fs(struct libmnt_table *tb,
547                                      struct libmnt_iter *itr)
548 {
549         struct libmnt_fs *fs = NULL;
550
551         if (is_listall_mode()) {
552                 /*
553                  * Print whole file
554                  */
555                 if (mnt_table_next_fs(tb, itr, &fs) != 0)
556                         return NULL;
557
558         } else if (is_mount_compatible_mode()) {
559                 /*
560                  * Look up for FS in the same way how mount(8) searchs in fstab
561                  *
562                  *   findmnt -f <spec>
563                  */
564                 fs = mnt_table_find_source(tb, get_match(COL_SOURCE),
565                                         mnt_iter_get_direction(itr));
566
567                 if (!fs && !(flags & FL_NOSWAPMATCH))
568                         fs = mnt_table_find_target(tb, get_match(COL_SOURCE),
569                                         mnt_iter_get_direction(itr));
570         } else {
571                 /*
572                  * Look up for all matching entries
573                  *
574                  *    findmnt [-l] <source> <target> [-O <options>] [-t <types>]
575                  *    findmnt [-l] <spec> [-O <options>] [-t <types>]
576                  */
577 again:
578                 mnt_table_find_next_fs(tb, itr, match_func,  NULL, &fs);
579
580                 if (!fs &&
581                     !(flags & FL_NOSWAPMATCH) &&
582                     !get_match(COL_TARGET) && get_match(COL_SOURCE)) {
583
584                         /* swap 'spec' and target. */
585                         set_match(COL_TARGET, get_match(COL_SOURCE));
586                         set_match(COL_SOURCE, NULL);
587                         mnt_reset_iter(itr, -1);
588
589                         goto again;
590                 }
591         }
592
593         return fs;
594 }
595
596 static int add_matching_lines(struct libmnt_table *tb,
597                               struct tt *tt, int direction)
598 {
599         struct libmnt_iter *itr = NULL;
600         struct libmnt_fs *fs;
601         int nlines = 0, rc = -1;
602
603         itr = mnt_new_iter(direction);
604         if (!itr) {
605                 warn(_("failed to initialize libmount iterator"));
606                 goto done;
607         }
608
609         while((fs = get_next_fs(tb, itr))) {
610                 if ((tt_flags & TT_FL_TREE) || (flags & FL_SUBMOUNTS))
611                         rc = create_treenode(tt, tb, fs, NULL);
612                 else
613                         rc = !add_line(tt, fs, NULL);
614                 if (rc)
615                         goto done;
616                 nlines++;
617                 if (flags & FL_FIRSTONLY)
618                         break;
619                 flags |= FL_NOSWAPMATCH;
620         }
621
622         if (nlines)
623                 rc = 0;
624 done:
625         mnt_free_iter(itr);
626         return rc;
627 }
628
629 static int poll_match(struct libmnt_fs *fs)
630 {
631         int rc = match_func(fs, NULL);
632
633         if (rc == 0 && !(flags & FL_NOSWAPMATCH) &&
634             get_match(COL_SOURCE) && !get_match(COL_TARGET)) {
635                 /*
636                  * findmnt --poll /foo
637                  * The '/foo' source as well as target.
638                  */
639                 const char *str = get_match(COL_SOURCE);
640
641                 set_match(COL_TARGET, str);     /* swap */
642                 set_match(COL_SOURCE, NULL);
643
644                 rc = match_func(fs, NULL);
645
646                 set_match(COL_TARGET, NULL);    /* restore */
647                 set_match(COL_SOURCE, str);
648
649         }
650         return rc;
651 }
652
653 static int poll_table(struct libmnt_table *tb, const char *tabfile,
654                   int timeout, struct tt *tt, int direction)
655 {
656         FILE *f;
657         int rc = -1;
658         struct libmnt_iter *itr = NULL;
659         struct libmnt_table *tb_new = NULL;
660         struct libmnt_tabdiff *diff = NULL;
661         struct pollfd fds[1];
662
663         tb_new = mnt_new_table();
664         if (!tb_new) {
665                 warn(_("failed to initialize libmount table"));
666                 goto done;
667         }
668
669         itr = mnt_new_iter(direction);
670         if (!itr) {
671                 warn(_("failed to initialize libmount iterator"));
672                 goto done;
673         }
674
675         diff = mnt_new_tabdiff();
676         if (!diff) {
677                 warn(_("failed to initialize libmount tabdiff"));
678                 goto done;
679         }
680
681         /* cache is unnecessary to detect changes */
682         mnt_table_set_cache(tb, NULL);
683         mnt_table_set_cache(tb_new, NULL);
684
685         f = fopen(tabfile, "r");
686         if (!f) {
687                 warn(_("%s: open failed"), tabfile);
688                 goto done;
689         }
690
691         mnt_table_set_parser_errcb(tb_new, parser_errcb);
692
693         fds[0].fd = fileno(f);
694         fds[0].events = POLLPRI;
695
696         while (1) {
697                 struct libmnt_table *tmp;
698                 struct libmnt_fs *old, *new;
699                 int change, count;
700
701                 count = poll(fds, 1, timeout);
702                 if (count == 0)
703                         break;  /* timeout */
704                 if (count < 0) {
705                         warn(_("poll() failed"));
706                         goto done;
707                 }
708
709                 rewind(f);
710                 rc = mnt_table_parse_stream(tb_new, f, tabfile);
711                 if (!rc)
712                         rc = mnt_diff_tables(diff, tb, tb_new);
713                 if (rc < 0)
714                         goto done;
715
716                 count = 0;
717                 mnt_reset_iter(itr, direction);
718                 while(mnt_tabdiff_next_change(
719                                 diff, itr, &old, &new, &change) == 0) {
720
721                         if (!has_poll_action(change))
722                                 continue;
723                         if (!poll_match(new ? new : old))
724                                 continue;
725                         count++;
726                         rc = !add_tabdiff_line(tt, new, old, change);
727                         if (rc)
728                                 goto done;
729                         if (flags & FL_FIRSTONLY)
730                                 break;
731                 }
732
733                 if (count) {
734                         rc = tt_print_table(tt);
735                         if (rc)
736                                 goto done;
737                 }
738
739                 /* swap tables */
740                 tmp = tb;
741                 tb = tb_new;
742                 tb_new = tmp;
743
744                 tt_remove_lines(tt);
745                 mnt_reset_table(tb_new);
746
747                 if (count && (flags & FL_FIRSTONLY))
748                         break;
749         }
750
751         rc = 0;
752 done:
753         mnt_free_table(tb_new);
754         mnt_free_tabdiff(diff);
755         mnt_free_iter(itr);
756         return rc;
757 }
758
759 static void __attribute__((__noreturn__)) usage(FILE *out)
760 {
761         int i;
762
763         fprintf(out, _(
764         "\nUsage:\n"
765         " %1$s [options]\n"
766         " %1$s [options] <device> | <mountpoint>\n"
767         " %1$s [options] <device> <mountpoint>\n"
768         " %1$s [options] [--source <device>] [--target <mountpoint>]\n"),
769                 program_invocation_short_name);
770
771         fprintf(out, _(
772         "\nOptions:\n"
773         " -s, --fstab            search in static table of filesystems\n"
774         " -m, --mtab             search in table of mounted filesystems\n"
775         " -k, --kernel           search in kernel table of mounted\n"
776         "                          filesystems (default)\n\n"));
777
778         fprintf(out, _(
779         " -p, --poll[=<list>]    monitor changes in table of mounted filesystems\n"
780         " -w, --timeout <num>    upper limit in milliseconds that --poll will block\n\n"));
781
782         fprintf(out, _(
783         " -a, --ascii            use ASCII chars for tree formatting\n"
784         " -c, --canonicalize     canonicalize printed paths\n"
785         " -d, --direction <word> direction of search, 'forward' or 'backward'\n"
786         " -e, --evaluate         convert tags (LABEL/UUID) to device names\n"
787         " -f, --first-only       print the first found filesystem only\n"));
788
789         fprintf(out, _(
790         " -h, --help             display this help text and exit\n"
791         " -i, --invert           invert the sense of matching\n"
792         " -l, --list             use list format output\n"
793         " -n, --noheadings       don't print column headings\n"
794         " -u, --notruncate       don't truncate text in columns\n"));
795         fprintf(out, _(
796         " -O, --options <list>   limit the set of filesystems by mount options\n"
797         " -o, --output <list>    the output columns to be shown\n"
798         " -P, --pairs            use key=\"value\" output format\n"
799         " -r, --raw              use raw output format\n"
800         " -t, --types <list>     limit the set of filesystems by FS types\n"));
801         fprintf(out, _(
802         " -v, --nofsroot         don't print [/dir] for bind or btrfs mounts\n"
803         " -R, --submounts        print all submounts for the matching filesystems\n"
804         " -S, --source <string>  the device to mount (by name, LABEL= or UUID=)\n"
805         " -T, --target <string>  the mountpoint to use\n\n"));
806
807
808         fprintf(out, _("\nAvailable columns:\n"));
809
810         for (i = 0; i < FINDMNT_NCOLUMNS; i++)
811                 fprintf(out, " %11s  %s\n", infos[i].name, _(infos[i].help));
812
813
814         fprintf(out, _("\nFor more information see findmnt(1).\n"));
815
816         exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
817 }
818
819 static void __attribute__((__noreturn__))
820 errx_mutually_exclusive(const char *opts)
821 {
822         errx(EXIT_FAILURE, "%s %s", opts, _("options are mutually exclusive"));
823 }
824
825 int main(int argc, char *argv[])
826 {
827         /* libmount */
828         struct libmnt_table *tb = NULL;
829         char *tabfile = NULL;
830         int direction = MNT_ITER_FORWARD;
831         int i, c, rc = -1, timeout = -1;
832
833         /* table.h */
834         struct tt *tt = NULL;
835
836         static const struct option longopts[] = {
837             { "ascii",        0, 0, 'a' },
838             { "canonicalize", 0, 0, 'c' },
839             { "direction",    1, 0, 'd' },
840             { "evaluate",     0, 0, 'e' },
841             { "first-only",   0, 0, 'f' },
842             { "fstab",        0, 0, 's' },
843             { "help",         0, 0, 'h' },
844             { "invert",       0, 0, 'i' },
845             { "kernel",       0, 0, 'k' },
846             { "list",         0, 0, 'l' },
847             { "mtab",         0, 0, 'm' },
848             { "noheadings",   0, 0, 'n' },
849             { "notruncate",   0, 0, 'u' },
850             { "options",      1, 0, 'O' },
851             { "output",       1, 0, 'o' },
852             { "poll",         2, 0, 'p' },
853             { "pairs",        0, 0, 'P' },
854             { "raw",          0, 0, 'r' },
855             { "types",        1, 0, 't' },
856             { "fsroot",       0, 0, 'v' },
857             { "submounts",    0, 0, 'R' },
858             { "source",       1, 0, 'S' },
859             { "target",       1, 0, 'T' },
860             { "timeout",      1, 0, 'w' },
861
862             { NULL,           0, 0, 0 }
863         };
864
865         assert(ARRAY_SIZE(columns) == FINDMNT_NCOLUMNS);
866
867         setlocale(LC_ALL, "");
868         bindtextdomain(PACKAGE, LOCALEDIR);
869         textdomain(PACKAGE);
870
871         /* default output format */
872         tt_flags |= TT_FL_TREE;
873
874         while ((c = getopt_long(argc, argv,
875                                 "acd:ehifo:O:p::Pklmnrst:uvRS:T:w:", longopts, NULL)) != -1) {
876                 switch(c) {
877                 case 'a':
878                         tt_flags |= TT_FL_ASCII;
879                         break;
880                 case 'c':
881                         flags |= FL_CANONICALIZE;
882                         break;
883                 case 'd':
884                         if (!strcmp(optarg, "forward"))
885                                 direction = MNT_ITER_FORWARD;
886                         else if (!strcmp(optarg, "backward"))
887                                 direction = MNT_ITER_BACKWARD;
888                         else
889                                 errx(EXIT_FAILURE,
890                                         _("unknown direction '%s'"), optarg);
891                         break;
892                 case 'e':
893                         flags |= FL_EVALUATE;
894                         break;
895                 case 'h':
896                         usage(stdout);
897                         break;
898                 case 'i':
899                         flags |= FL_INVERT;
900                         break;
901                 case 'f':
902                         flags |= FL_FIRSTONLY;
903                         break;
904                 case 'u':
905                         disable_columns_truncate();
906                         break;
907                 case 'o':
908                         ncolumns = string_to_idarray(optarg,
909                                                 columns, ARRAY_SIZE(columns),
910                                                 column_name_to_id);
911                         if (ncolumns < 0)
912                                 exit(EXIT_FAILURE);
913                         break;
914                 case 'O':
915                         set_match(COL_OPTIONS, optarg);
916                         break;
917                 case 'p':
918                         if (optarg) {
919                                 nactions = string_to_idarray(optarg,
920                                                 actions, ARRAY_SIZE(actions),
921                                                 poll_action_name_to_id);
922                                 if (nactions < 0)
923                                         exit(EXIT_FAILURE);
924                         }
925                         flags |= FL_POLL;
926                         tt_flags &= ~TT_FL_TREE;
927                         break;
928                 case 'P':
929                         tt_flags |= TT_FL_EXPORT;
930                         tt_flags &= ~TT_FL_TREE;
931                         break;
932                 case 'm':               /* mtab */
933                         if (tabfile)
934                                 errx_mutually_exclusive("--{fstab,mtab,kernel}");
935                         tabfile = _PATH_MOUNTED;
936                         tt_flags &= ~TT_FL_TREE;
937                         break;
938                 case 's':               /* fstab */
939                         if (tabfile)
940                                 errx_mutually_exclusive("--{fstab,mtab,kernel}");
941                         tabfile = _PATH_MNTTAB;
942                         tt_flags &= ~TT_FL_TREE;
943                         break;
944                 case 'k':               /* kernel (mountinfo) */
945                         if (tabfile)
946                                  errx_mutually_exclusive("--{fstab,mtab,kernel}");
947                         tabfile = _PATH_PROC_MOUNTINFO;
948                         break;
949                 case 't':
950                         set_match(COL_FSTYPE, optarg);
951                         break;
952                 case 'r':
953                         tt_flags &= ~TT_FL_TREE;        /* disable the default */
954                         tt_flags |= TT_FL_RAW;          /* enable raw */
955                         break;
956                 case 'l':
957                         if ((tt_flags & TT_FL_RAW) && (tt_flags & TT_FL_EXPORT))
958                                 errx_mutually_exclusive("--{raw,list,pairs}");
959
960                         tt_flags &= ~TT_FL_TREE; /* disable the default */
961                         break;
962                 case 'n':
963                         tt_flags |= TT_FL_NOHEADINGS;
964                         break;
965                 case 'v':
966                         flags |= FL_NOFSROOT;
967                         break;
968                 case 'R':
969                         flags |= FL_SUBMOUNTS;
970                         break;
971                 case 'S':
972                         set_match(COL_SOURCE, optarg);
973                         flags |= FL_NOSWAPMATCH;
974                         break;
975                 case 'T':
976                         set_match(COL_TARGET, optarg);
977                         flags |= FL_NOSWAPMATCH;
978                         break;
979                 case 'w':
980                         timeout = strtol_or_err(optarg,
981                                         _("failed to parse timeout"));
982                         break;
983                 default:
984                         usage(stderr);
985                         break;
986                 }
987         }
988
989         /* default columns */
990         if (!ncolumns) {
991                 if (flags & FL_POLL)
992                         columns[ncolumns++] = COL_ACTION;
993
994                 columns[ncolumns++] = COL_TARGET;
995                 columns[ncolumns++] = COL_SOURCE;
996                 columns[ncolumns++] = COL_FSTYPE;
997                 columns[ncolumns++] = COL_OPTIONS;
998         }
999
1000         if (!tabfile) {
1001                 tabfile = _PATH_PROC_MOUNTINFO;
1002
1003                 if (access(tabfile, R_OK)) {            /* old kernel? */
1004                         tabfile = _PATH_PROC_MOUNTS;
1005                         tt_flags &= ~TT_FL_TREE;
1006                 }
1007         }
1008
1009         if (optind < argc && (get_match(COL_SOURCE) || get_match(COL_TARGET)))
1010                 errx(EXIT_FAILURE, _(
1011                         "options --target and --source can't be used together "
1012                         "with command line element that is not an option"));
1013
1014         if (optind < argc)
1015                 set_match(COL_SOURCE, argv[optind++]);  /* dev/tag/mountpoint */
1016         if (optind < argc)
1017                 set_match(COL_TARGET, argv[optind++]);  /* mountpoint */
1018
1019         if ((flags & FL_SUBMOUNTS) && is_listall_mode())
1020                 /* don't care about submounts if list all mounts */
1021                 flags &= ~FL_SUBMOUNTS;
1022
1023         if (!(flags & FL_SUBMOUNTS) &&
1024             (!is_listall_mode() || (flags & FL_FIRSTONLY)))
1025                 tt_flags &= ~TT_FL_TREE;
1026
1027         if (!(flags & FL_NOSWAPMATCH) &&
1028             !get_match(COL_TARGET) && get_match(COL_SOURCE)) {
1029                 /*
1030                  * Check if we can swap source and target, it's
1031                  * not possible if the source is LABEL=/UUID=
1032                  */
1033                 const char *x = get_match(COL_SOURCE);
1034
1035                 if (!strncmp(x, "LABEL=", 6) || !strncmp(x, "UUID=", 5))
1036                         flags |= FL_NOSWAPMATCH;
1037         }
1038
1039         /*
1040          * initialize libmount
1041          */
1042         mnt_init_debug(0);
1043
1044         tb = parse_tabfile(tabfile);
1045         if (!tb)
1046                 goto leave;
1047
1048         cache = mnt_new_cache();
1049         if (!cache) {
1050                 warn(_("failed to initialize libmount cache"));
1051                 goto leave;
1052         }
1053         mnt_table_set_cache(tb, cache);
1054
1055         /*
1056          * initialize output formatting (tt.h)
1057          */
1058         tt = tt_new_table(tt_flags);
1059         if (!tt) {
1060                 warn(_("failed to initialize output table"));
1061                 goto leave;
1062         }
1063
1064         for (i = 0; i < ncolumns; i++) {
1065                 int fl = get_column_flags(i);
1066                 int id = get_column_id(i);
1067
1068                 if (!(tt_flags & TT_FL_TREE))
1069                         fl &= ~TT_FL_TREE;
1070
1071                 if (!(flags & FL_POLL) && is_tabdiff_column(id)) {
1072                         warnx(_("%s column is requested, but --poll "
1073                                "is not enabled"), get_column_name(i));
1074                         goto leave;
1075                 }
1076                 if (!tt_define_column(tt, get_column_name(i),
1077                                         get_column_whint(i), fl)) {
1078                         warn(_("failed to initialize output column"));
1079                         goto leave;
1080                 }
1081         }
1082
1083         /*
1084          * Fill in data to the output table
1085          */
1086         if (flags & FL_POLL)
1087                 /* poll mode */
1088                 rc = poll_table(tb, tabfile, timeout, tt, direction);
1089
1090         else if ((tt_flags & TT_FL_TREE) && is_listall_mode())
1091                 /* whole tree */
1092                 rc = create_treenode(tt, tb, NULL, NULL);
1093         else
1094                 /* whole lits of sub-tree */
1095                 rc = add_matching_lines(tb, tt, direction);
1096
1097         /*
1098          * Print the output table for non-poll modes
1099          */
1100         if (!rc && !(flags & FL_POLL))
1101                 tt_print_table(tt);
1102 leave:
1103         tt_free_table(tt);
1104
1105         mnt_free_table(tb);
1106         mnt_free_cache(cache);
1107
1108         return rc ? EXIT_FAILURE : EXIT_SUCCESS;
1109 }