xref: /dragonfly/sbin/mountctl/mountctl.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1 /*
2  * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sbin/mountctl/mountctl.c,v 1.10 2008/02/05 20:49:50 dillon Exp $
35  */
36 /*
37  * This utility implements the userland mountctl command which is used to
38  * manage high level journaling on mount points.
39  */
40 
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/ucred.h>
44 #include <sys/mount.h>
45 #include <sys/time.h>
46 #include <sys/mountctl.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <fcntl.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <errno.h>
53 
54 static void usage(void);
55 static void parse_option_keyword(const char *opt,
56                     const char **wopt, const char **xopt);
57 static int64_t getsize(const char *str);
58 static const char *numtostr(int64_t num);
59 
60 static int mountctl_scan(void (*func)(const char *, const char *, int, void *),
61                     const char *keyword, const char *mountpt, int fd);
62 static void mountctl_list(const char *keyword, const char *mountpt,
63                     int __unused fd, void *info);
64 static void mountctl_add(const char *keyword, const char *mountpt, int fd);
65 static void mountctl_restart(const char *keyword, const char *mountpt,
66                     int fd, void __unused *);
67 static void mountctl_delete(const char *keyword, const char *mountpt,
68                     int __unused fd, void __unused *);
69 static void mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *);
70 
71 /*
72  * For all options 0 means unspecified, -1 means noOPT or nonOPT, and a
73  * positive number indicates enabling or execution of the option.
74  */
75 static int exitCode;
76 static int freeze_opt;
77 static int start_opt;
78 static int close_opt;
79 static int abort_opt;
80 static int flush_opt;
81 static int reversable_opt;
82 static int twoway_opt;
83 static int output_safety_override_opt;
84 static int64_t memfifo_opt;
85 static int64_t swapfifo_opt;
86 
87 int
main(int ac,char ** av)88 main(int ac, char **av)
89 {
90     int fd;
91     int ch;
92     int aopt = 0;
93     int dopt = 0;
94     int fopt = 0;
95     int lopt = 0;
96     int mopt = 0;
97     int ropt = 0;
98     int mimplied = 0;
99     const char *wopt = NULL;
100     const char *xopt = NULL;
101     const char *keyword = NULL;
102     const char *mountpt = NULL;
103     char *tmp;
104 
105     while ((ch = getopt(ac, av, "2adflmo:rw:x:ACFSW:X:Z")) != -1) {
106           switch(ch) {
107           case '2':
108               twoway_opt = 1;
109               break;
110           case 'r':
111               ropt = 1;
112               if (aopt + dopt + lopt + mopt + ropt != 1) {
113                     fprintf(stderr, "too many action options specified\n");
114                     usage();
115               }
116               break;
117           case 'a':
118               aopt = 1;
119               if (aopt + dopt + lopt + mopt + ropt != 1) {
120                     fprintf(stderr, "too many action options specified\n");
121                     usage();
122               }
123               break;
124           case 'd':
125               dopt = 1;
126               if (aopt + dopt + lopt + mopt + ropt != 1) {
127                     fprintf(stderr, "too many action options specified\n");
128                     usage();
129               }
130               break;
131           case 'f':
132               fopt = 1;
133               break;
134           case 'l':
135               lopt = 1;
136               if (aopt + dopt + lopt + mopt + ropt != 1) {
137                     fprintf(stderr, "too many action options specified\n");
138                     usage();
139               }
140               break;
141           case 'o':
142               parse_option_keyword(optarg, &wopt, &xopt);
143               break;
144           case 'm':
145               mopt = 1;
146               if (aopt + dopt + lopt + mopt + ropt != 1) {
147                     fprintf(stderr, "too many action options specified\n");
148                     usage();
149               }
150               break;
151           case 'W':
152               output_safety_override_opt = 1;
153               /* fall through */
154           case 'w':
155               wopt = optarg;
156               mimplied = 1;
157               break;
158           case 'X':
159               output_safety_override_opt = 1;
160               /* fall through */
161           case 'x':
162               xopt = optarg;
163               mimplied = 1;
164               break;
165           case 'A':
166               mimplied = 1;
167               abort_opt = 1;
168               break;
169           case 'C':
170               mimplied = 1;
171               close_opt = 1;
172               break;
173           case 'F':
174               mimplied = 1;
175               flush_opt = 1;
176               break;
177           case 'S':
178               mimplied = 1;
179               start_opt = 1;
180               break;
181           case 'Z':
182               mimplied = 1;
183               freeze_opt = 1;
184               break;
185           default:
186               fprintf(stderr, "unknown option: -%c\n", optopt);
187               usage();
188           }
189     }
190     ac -= optind;
191     av += optind;
192 
193     /*
194      * Parse the keyword and/or mount point.
195      */
196     switch(ac) {
197     case 0:
198           if (aopt || ropt) {
199               fprintf(stderr, "action requires a tag and/or mount "
200                                   "point to be specified\n");
201               usage();
202           }
203           break;
204     case 1:
205           if (av[0][0] == '/') {
206               mountpt = av[0];
207               if ((keyword = strchr(mountpt, ':')) != NULL) {
208                     ++keyword;
209                     tmp = strdup(mountpt);
210                     *strchr(tmp, ':') = 0;
211                     mountpt = tmp;
212               }
213           } else {
214               keyword = av[0];
215           }
216           break;
217     default:
218           fprintf(stderr, "unexpected extra arguments to command\n");
219           usage();
220     }
221 
222     /*
223      * Additional sanity checks
224      */
225     if (aopt + dopt + lopt + mopt + ropt + mimplied == 0) {
226           fprintf(stderr, "no action or implied action options were specified\n");
227           usage();
228     }
229     if (mimplied && aopt + dopt + lopt + ropt == 0)
230           mopt = 1;
231     if ((wopt || xopt) && !(aopt || ropt || mopt)) {
232           fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a/-r\n");
233           usage();
234     }
235     if (aopt && (keyword == NULL || mountpt == NULL)) {
236           fprintf(stderr, "a keyword AND a mountpt must be specified "
237                               "when adding a journal\n");
238           usage();
239     }
240     if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
241           fprintf(stderr, "a keyword, a mountpt, or both must be specified "
242                               "when modifying or deleting a journal, unless "
243                               "-f is also specified for safety\n");
244           usage();
245     }
246 
247     /*
248      * Open the journaling file descriptor if required.
249      */
250     if (wopt && xopt) {
251           fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
252           exit(1);
253     } else if (wopt) {
254           if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
255               fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
256               exit(1);
257           }
258     } else if (xopt) {
259           fd = strtol(xopt, NULL, 0);
260     } else if (aopt || ropt) {
261           fd = 1;             /* stdout default for -a */
262     } else {
263           fd = -1;
264     }
265 
266     /*
267      * And finally execute the core command.
268      */
269     if (lopt)
270           mountctl_scan(mountctl_list, keyword, mountpt, fd);
271     if (aopt)
272           mountctl_add(keyword, mountpt, fd);
273     if (ropt) {
274           ch = mountctl_scan(mountctl_restart, keyword, mountpt, fd);
275           if (ch)
276               fprintf(stderr, "%d journals restarted\n", ch);
277           else
278               fprintf(stderr, "Unable to locate any matching journals\n");
279     }
280     if (dopt) {
281           ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1);
282           if (ch)
283               fprintf(stderr, "%d journals deleted\n", ch);
284           else
285               fprintf(stderr, "Unable to locate any matching journals\n");
286     }
287     if (mopt) {
288           ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd);
289           if (ch)
290               fprintf(stderr, "%d journals modified\n", ch);
291           else
292               fprintf(stderr, "Unable to locate any matching journals\n");
293     }
294 
295     return(exitCode);
296 }
297 
298 static void
parse_option_keyword(const char * opt,const char ** wopt,const char ** xopt)299 parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
300 {
301     char *str = strdup(opt);
302     char *name;
303     char *val;
304     int negate;
305     int hasval;
306     int cannotnegate;
307 
308     /*
309      * multiple comma delimited options may be specified.
310      */
311     while ((name = strsep(&str, ",")) != NULL) {
312           /*
313            * some options have associated data.
314            */
315           if ((val = strchr(name, '=')) != NULL)
316               *val++ = 0;
317 
318           /*
319            * options beginning with 'no' or 'non' are negated.  A positive
320            * number means not negated, a negative number means negated.
321            */
322           negate = 1;
323           cannotnegate = 0;
324           hasval = 0;
325           if (strncmp(name, "non", 3) == 0) {
326               name += 3;
327               negate = -1;
328           } else if (strncmp(name, "no", 2) == 0) {
329               name += 2;
330               negate = -1;
331           }
332 
333           /*
334            * Parse supported options
335            */
336           if (strcmp(name, "undo") == 0) {
337               reversable_opt = negate;
338           } else if (strcmp(name, "reversable") == 0) {
339               reversable_opt = negate;
340           } else if (strcmp(name, "twoway") == 0) {
341               twoway_opt = negate;
342           } else if (strcmp(name, "memfifo") == 0) {
343               cannotnegate = 1;
344               hasval = 1;
345               if (val) {
346                     if ((memfifo_opt = getsize(val)) == 0)
347                         memfifo_opt = -1;
348               }
349           } else if (strcmp(name, "swapfifo") == 0) {
350               if (val) {
351                     hasval = 1;
352                     if ((swapfifo_opt = getsize(val)) == 0)
353                         swapfifo_opt = -1;
354               } else if (negate < 0) {
355                     swapfifo_opt = -1;
356               } else {
357                     hasval = 1;         /* force error */
358               }
359           } else if (strcmp(name, "fd") == 0) {
360               cannotnegate = 1;
361               hasval = 1;
362               if (val)
363                     *xopt = val;
364           } else if (strcmp(name, "path") == 0) {
365               cannotnegate = 1;
366               hasval = 1;
367               if (val)
368                     *wopt = val;
369           } else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
370               if (negate < 0)
371                     start_opt = -negate;
372               else
373                     freeze_opt = negate;
374           } else if (strcmp(name, "start") == 0) {
375               if (negate < 0)
376                     freeze_opt = -negate;
377               else
378                     start_opt = negate;
379           } else if (strcmp(name, "close") == 0) {
380               close_opt = negate;
381           } else if (strcmp(name, "abort") == 0) {
382               abort_opt = negate;
383           } else if (strcmp(name, "flush") == 0) {
384               flush_opt = negate;
385           } else {
386               fprintf(stderr, "unknown option keyword: %s\n", name);
387               exit(1);
388           }
389 
390           /*
391            * Sanity checks
392            */
393           if (cannotnegate && negate < 0) {
394               fprintf(stderr, "option %s may not be negated\n", name);
395               exit(1);
396           }
397           if (hasval && val == NULL) {
398               fprintf(stderr, "option %s requires assigned data\n", name);
399               exit(1);
400           }
401           if (hasval == 0 && val) {
402               fprintf(stderr, "option %s does not take an assignment\n", name);
403               exit(1);
404           }
405 
406     }
407 }
408 
409 static int
mountctl_scan(void (* func)(const char *,const char *,int,void *),const char * keyword,const char * mountpt,int fd)410 mountctl_scan(void (*func)(const char *, const char *, int, void *),
411               const char *keyword, const char *mountpt, int fd)
412 {
413     struct statfs *sfs;
414     int count;
415     int calls;
416     int i;
417     struct mountctl_status_journal statreq;
418     struct mountctl_journal_ret_status rstat[4];  /* BIG */
419 
420     calls = 0;
421     if (mountpt) {
422           bzero(&statreq, sizeof(statreq));
423           if (keyword) {
424               statreq.index = MC_JOURNAL_INDEX_ID;
425               count = strlen(keyword);
426               if (count > JIDMAX)
427                     count = JIDMAX;
428               bcopy(keyword, statreq.id, count);
429           } else {
430               statreq.index = MC_JOURNAL_INDEX_ALL;
431           }
432           count = mountctl(mountpt, MOUNTCTL_STATUS_VFS_JOURNAL, -1,
433                               &statreq, sizeof(statreq), &rstat, sizeof(rstat));
434           if (count > 0 && rstat[0].recsize != sizeof(rstat[0])) {
435               fprintf(stderr, "Unable to access status, "
436                                   "structure size mismatch\n");
437               exit(1);
438           }
439           if (count > 0) {
440               count /= sizeof(rstat[0]);
441               for (i = 0; i < count; ++i) {
442                     func(rstat[i].id, mountpt, fd, &rstat[i]);
443                     ++calls;
444               }
445           }
446     } else {
447           if ((count = getmntinfo(&sfs, MNT_WAIT)) > 0) {
448               for (i = 0; i < count; ++i) {
449                     calls += mountctl_scan(func, keyword, sfs[i].f_mntonname, fd);
450               }
451           } else if (count < 0) {
452               /* XXX */
453           }
454     }
455     return(calls);
456 }
457 
458 static void
mountctl_list(const char * keyword __unused,const char * mountpt,int fd __unused,void * info)459 mountctl_list(const char *keyword __unused, const char *mountpt,
460                 int fd __unused, void *info)
461 {
462     struct mountctl_journal_ret_status *rstat = info;
463 
464     printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
465     printf("    membufsize=%s\n", numtostr(rstat->membufsize));
466     printf("    membufused=%s\n", numtostr(rstat->membufused));
467     printf("    membufunacked=%s\n", numtostr(rstat->membufunacked));
468     printf("    total_bytes=%s\n", numtostr(rstat->bytessent));
469     printf("    fifo_stalls=%jd\n", (intmax_t)rstat->fifostalls);
470 }
471 
472 static void
mountctl_add(const char * keyword,const char * mountpt,int fd)473 mountctl_add(const char *keyword, const char *mountpt, int fd)
474 {
475     struct mountctl_install_journal joinfo;
476     struct stat st1;
477     struct stat st2;
478     int error;
479 
480     /*
481      * Make sure the file descriptor is not on the same filesystem as the
482      * mount point.  This isn't a perfect test, but it should catch most
483      * foot shooting.
484      */
485     if (output_safety_override_opt == 0 &&
486           fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) &&
487           stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev
488     ) {
489           fprintf(stderr, "%s:%s failed to add, the journal cannot be on the "
490                               "same filesystem being journaled!\n",
491                               mountpt, keyword);
492           exitCode = 1;
493           return;
494     }
495 
496     /*
497      * Setup joinfo and issue the add
498      */
499     bzero(&joinfo, sizeof(joinfo));
500     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
501     if (memfifo_opt > 0)
502           joinfo.membufsize = memfifo_opt;
503     if (twoway_opt > 0)
504           joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
505     if (reversable_opt > 0)
506           joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
507 
508     error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
509                               &joinfo, sizeof(joinfo), NULL, 0);
510     if (error == 0) {
511           fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
512     } else {
513           fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
514           exitCode = 1;
515     }
516 }
517 
518 static void
mountctl_restart(const char * keyword,const char * mountpt,int fd,void __unused * info)519 mountctl_restart(const char *keyword, const char *mountpt,
520                      int fd, void __unused *info)
521 {
522     struct mountctl_restart_journal joinfo;
523     int error;
524 
525     /* XXX make sure descriptor is not on same filesystem as journal */
526 
527     bzero(&joinfo, sizeof(joinfo));
528 
529     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
530     if (twoway_opt > 0)
531           joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
532     if (reversable_opt > 0)
533           joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
534 
535     error = mountctl(mountpt, MOUNTCTL_RESTART_VFS_JOURNAL, fd,
536                               &joinfo, sizeof(joinfo), NULL, 0);
537     if (error == 0) {
538           fprintf(stderr, "%s:%s restarted\n", mountpt, joinfo.id);
539     } else {
540           fprintf(stderr, "%s:%s restart failed, error %s\n", mountpt, joinfo.id, strerror(errno));
541     }
542 }
543 
544 static void
mountctl_delete(const char * keyword,const char * mountpt,int __unused fd,void __unused * info)545 mountctl_delete(const char *keyword, const char *mountpt,
546                     int __unused fd, void __unused *info)
547 {
548     struct mountctl_remove_journal joinfo;
549     int error;
550 
551     bzero(&joinfo, sizeof(joinfo));
552     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
553     error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
554                               &joinfo, sizeof(joinfo), NULL, 0);
555     if (error == 0) {
556           fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
557     } else {
558           fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
559     }
560 }
561 
562 static void
mountctl_modify(const char * keyword __unused,const char * mountpt __unused,int fd __unused,void * info __unused)563 mountctl_modify(const char *keyword __unused, const char *mountpt __unused,
564                     int fd __unused, void *info __unused)
565 {
566     fprintf(stderr, "modify not yet implemented\n");
567 }
568 
569 
570 static void
usage(void)571 usage(void)
572 {
573     printf(
574           "usage: mountctl -l {mountpt | tag | mountpt:tag}\n"
575           "       mountctl -a [-2] [-w/W output_path] [-x/X filedesc]\n"
576           "                   [-o options] mountpt:tag\n"
577           "       mountctl -r [-2] [-w/W output_path] [-x/X filedesc] mountpt:tag\n"
578           "       mountctl -d {mountpt | tag | mountpt:tag}\n"
579           "       mountctl -m [-o options] {mountpt | tag | mountpt:tag}\n"
580           "       mountctl -FZSCA {mountpt | tag | mountpt:tag}\n"
581     );
582     exit(1);
583 }
584 
585 static int64_t
getsize(const char * str)586 getsize(const char *str)
587 {
588     char *suffix;
589     int64_t val;
590 
591     val = strtoll(str, &suffix, 0);
592     if (suffix) {
593           switch(*suffix) {
594           case 'b':
595               break;
596           case 't':
597               val *= 1024;
598               /* fall through */
599           case 'g':
600               val *= 1024;
601               /* fall through */
602           case 'm':
603               val *= 1024;
604               /* fall through */
605           case 'k':
606               val *= 1024;
607               /* fall through */
608               break;
609           default:
610               fprintf(stderr, "data value '%s' has unknown suffix\n", str);
611               exit(1);
612           }
613     }
614     return(val);
615 }
616 
617 static const char *
numtostr(int64_t num)618 numtostr(int64_t num)
619 {
620     static char buf[64];
621 
622     if (num < 1024)
623           snprintf(buf, sizeof(buf), "%jd", (intmax_t)num);
624     else if (num < 10 * 1024)
625           snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
626     else if (num < 1024 * 1024)
627           snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
628     else if (num < 10 * 1024 * 1024)
629           snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
630     else if (num < 1024 * 1024 * 1024)
631           snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
632     else if (num < 10LL * 1024 * 1024 * 1024)
633           snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
634     else
635           snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
636     return(buf);
637 }
638 
639