Merge from vendor branch OPENSSH:
[dragonfly.git] / sbin / mountctl / mountctl.c
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.5 2005/07/06 06:04:32 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 <fcntl.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <errno.h>
52
53 static volatile void usage(void);
54 static void parse_option_keyword(const char *opt, 
55                 const char **wopt, const char **xopt);
56 static int64_t getsize(const char *str);
57 static const char *numtostr(int64_t num);
58
59 static int mountctl_scan(void (*func)(const char *, const char *, int, void *),
60                 const char *keyword, const char *mountpt, int fd);
61 static void mountctl_list(const char *keyword, const char *mountpt,
62                 int __unused fd, void *info);
63 static void mountctl_add(const char *keyword, const char *mountpt, int fd);
64 static void mountctl_delete(const char *keyword, const char *mountpt,
65                 int __unused fd, void __unused *);
66 static void mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *);
67
68 /*
69  * For all options 0 means unspecified, -1 means noOPT or nonOPT, and a
70  * positive number indicates enabling or execution of the option.
71  */
72 static int exitCode;
73 static int freeze_opt;
74 static int start_opt;
75 static int close_opt;
76 static int abort_opt;
77 static int flush_opt;
78 static int reversable_opt;
79 static int twoway_opt;
80 static int64_t memfifo_opt;
81 static int64_t swapfifo_opt;
82
83 int
84 main(int ac, char **av)
85 {
86     int fd;
87     int ch;
88     int aopt = 0;
89     int dopt = 0;
90     int fopt = 0;
91     int lopt = 0;
92     int mopt = 0;
93     int mimplied = 0;
94     const char *wopt = NULL;
95     const char *xopt = NULL;
96     const char *keyword = NULL;
97     const char *mountpt = NULL;
98     char *tmp;
99
100     while ((ch = getopt(ac, av, "2adflo:mw:x:ACFSZ")) != -1) {
101         switch(ch) {
102         case '2':
103             twoway_opt = 1;
104             break;
105         case 'a':
106             aopt = 1;
107             if (aopt + dopt + lopt + mopt != 1) {
108                 fprintf(stderr, "too many action options specified\n");
109                 usage();
110             }
111             break;
112         case 'd':
113             dopt = 1;
114             if (aopt + dopt + lopt + mopt != 1) {
115                 fprintf(stderr, "too many action options specified\n");
116                 usage();
117             }
118             break;
119         case 'f':
120             fopt = 1;
121             break;
122         case 'l':
123             lopt = 1;
124             if (aopt + dopt + lopt + mopt != 1) {
125                 fprintf(stderr, "too many action options specified\n");
126                 usage();
127             }
128             break;
129         case 'o':
130             parse_option_keyword(optarg, &wopt, &xopt);
131             break;
132         case 'm':
133             mopt = 1;
134             if (aopt + dopt + lopt + mopt != 1) {
135                 fprintf(stderr, "too many action options specified\n");
136                 usage();
137             }
138             break;
139         case 'w':
140             wopt = optarg;
141             mimplied = 1;
142             break;
143         case 'x':
144             xopt = optarg;
145             mimplied = 1;
146             break;
147         case 'A':
148             mimplied = 1;
149             abort_opt = 1;
150             break;
151         case 'C':
152             mimplied = 1;
153             close_opt = 1;
154             break;
155         case 'F':
156             mimplied = 1;
157             flush_opt = 1;
158             break;
159         case 'S':
160             mimplied = 1;
161             start_opt = 1;
162             break;
163         case 'Z':
164             mimplied = 1;
165             freeze_opt = 1;
166             break;
167         default:
168             fprintf(stderr, "unknown option: -%c\n", optopt);
169             usage();
170         }
171     }
172     ac -= optind;
173     av += optind;
174
175     /*
176      * Parse the keyword and/or mount point.
177      */
178     switch(ac) {
179     case 0:
180         if (aopt) {
181             fprintf(stderr, "action requires a tag and/or mount "
182                             "point to be specified\n");
183             usage();
184         }
185         break;
186     case 1:
187         if (av[0][0] == '/') {
188             mountpt = av[0];
189             if ((keyword = strchr(mountpt, ':')) != NULL) {
190                 ++keyword;
191                 tmp = strdup(mountpt);
192                 *strchr(tmp, ':') = 0;
193                 mountpt = tmp;
194             }
195         } else {
196             keyword = av[0];
197         }
198         break;
199     default:
200         fprintf(stderr, "unexpected extra arguments to command\n");
201         usage();
202     }
203
204     /*
205      * Additional sanity checks
206      */
207     if (aopt + dopt + lopt + mopt + mimplied == 0) {
208         fprintf(stderr, "no action or implied action options were specified\n");
209         usage();
210     }
211     if (mimplied && aopt + dopt + lopt == 0)
212         mopt = 1;
213     if ((wopt || xopt) && !(aopt || mopt)) {
214         fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a\n");
215         usage();
216     }
217     if (aopt && (keyword == NULL || mountpt == NULL)) {
218         fprintf(stderr, "a keyword AND a mountpt must be specified "
219                         "when adding a journal\n");
220         usage();
221     }
222     if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
223         fprintf(stderr, "a keyword, a mountpt, or both must be specified "
224                         "when modifying or deleting a journal, unless "
225                         "-f is also specified for safety\n");
226         usage();
227     }
228
229     /*
230      * Open the journaling file descriptor if required.
231      */
232     if (wopt && xopt) {
233         fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
234         exit(1);
235     } else if (wopt) {
236         if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
237             fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
238             exit(1);
239         }
240     } else if (xopt) {
241         fd = strtol(xopt, NULL, 0);
242     } else if (aopt) {
243         fd = 1;         /* stdout default for -a */
244     } else {
245         fd = -1;
246     }
247
248     /*
249      * And finally execute the core command.
250      */
251     if (lopt)
252         mountctl_scan(mountctl_list, keyword, mountpt, fd);
253     if (aopt)
254         mountctl_add(keyword, mountpt, fd);
255     if (dopt) {
256         ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1);
257         if (ch)
258             printf("%d journals deleted\n", ch);
259         else
260             printf("Unable to locate any matching journals\n");
261     }
262     if (mopt) {
263         ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd);
264         if (ch)
265             printf("%d journals modified\n", ch);
266         else
267             printf("Unable to locate any matching journals\n");
268     }
269
270     return(exitCode);
271 }
272
273 static void
274 parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
275 {
276     char *str = strdup(opt);
277     char *name;
278     char *val;
279     int negate;
280     int hasval;
281     int cannotnegate;
282
283     /*
284      * multiple comma delimited options may be specified.
285      */
286     while ((name = strsep(&str, ",")) != NULL) {
287         /*
288          * some options have associated data.
289          */
290         if ((val = strchr(name, '=')) != NULL)
291             *val++ = 0;
292
293         /*
294          * options beginning with 'no' or 'non' are negated.  A positive
295          * number means not negated, a negative number means negated.
296          */
297         negate = 1;
298         cannotnegate = 0;
299         hasval = 0;
300         if (strncmp(name, "non", 3) == 0) {
301             name += 3;
302             negate = -1;
303         } else if (strncmp(name, "no", 2) == 0) {
304             name += 2;
305             negate = -1;
306         }
307
308         /*
309          * Parse supported options
310          */
311         if (strcmp(name, "reversable") == 0) {
312             reversable_opt = negate;
313         } else if (strcmp(name, "twoway") == 0) {
314             twoway_opt = negate;
315         } else if (strcmp(name, "memfifo") == 0) {
316             cannotnegate = 1;
317             hasval = 1;
318             if (val) {
319                 if ((memfifo_opt = getsize(val)) == 0)
320                     memfifo_opt = -1;
321             }
322         } else if (strcmp(name, "swapfifo") == 0) {
323             if (val) {
324                 hasval = 1;
325                 if ((swapfifo_opt = getsize(val)) == 0)
326                     swapfifo_opt = -1;
327             } else if (negate < 0) {
328                 swapfifo_opt = -1;
329             } else {
330                 hasval = 1;     /* force error */
331             }
332         } else if (strcmp(name, "fd") == 0) {
333             cannotnegate = 1;
334             hasval = 1;
335             if (val)
336                 *xopt = val;
337         } else if (strcmp(name, "path") == 0) {
338             cannotnegate = 1;
339             hasval = 1;
340             if (val)
341                 *wopt = val;
342         } else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
343             if (negate < 0)
344                 start_opt = -negate;
345             else
346                 freeze_opt = negate;
347         } else if (strcmp(name, "start") == 0) {
348             if (negate < 0)
349                 freeze_opt = -negate;
350             else
351                 start_opt = negate;
352         } else if (strcmp(name, "close") == 0) {
353             close_opt = negate;
354         } else if (strcmp(name, "abort") == 0) {
355             abort_opt = negate;
356         } else if (strcmp(name, "flush") == 0) {
357             flush_opt = negate;
358         } else {
359             fprintf(stderr, "unknown option keyword: %s\n", name);
360             exit(1);
361         }
362
363         /*
364          * Sanity checks
365          */
366         if (cannotnegate && negate < 0) {
367             fprintf(stderr, "option %s may not be negated\n", name);
368             exit(1);
369         }
370         if (hasval && val == NULL) {
371             fprintf(stderr, "option %s requires assigned data\n", name);
372             exit(1);
373         }
374         if (hasval == 0 && val) {
375             fprintf(stderr, "option %s does not take an assignment\n", name);
376             exit(1);
377         }
378
379     }
380 }
381
382 static int
383 mountctl_scan(void (*func)(const char *, const char *, int, void *),
384             const char *keyword, const char *mountpt, int fd)
385 {
386     struct statfs *sfs;
387     int count;
388     int calls;
389     int i;
390     struct mountctl_status_journal statreq;
391     struct mountctl_journal_ret_status rstat[4];        /* BIG */
392
393     calls = 0;
394     if (mountpt) {
395         bzero(&statreq, sizeof(statreq));
396         if (keyword) {
397             statreq.index = MC_JOURNAL_INDEX_ID;
398             count = strlen(keyword);
399             if (count > JIDMAX)
400                 count = JIDMAX;
401             bcopy(keyword, statreq.id, count);
402         } else {
403             statreq.index = MC_JOURNAL_INDEX_ALL;
404         }
405         count = mountctl(mountpt, MOUNTCTL_STATUS_VFS_JOURNAL, -1,
406                         &statreq, sizeof(statreq), &rstat, sizeof(rstat));
407         if (count > 0 && rstat[0].recsize != sizeof(rstat[0])) {
408             fprintf(stderr, "Unable to access status, "
409                             "structure size mismatch\n");
410             exit(1);
411         }
412         if (count > 0) {
413             count /= sizeof(rstat[0]);
414             for (i = 0; i < count; ++i) {
415                 func(rstat[i].id, mountpt, fd, &rstat[i]);
416                 ++calls;
417             }
418         }
419     } else {
420         if ((count = getmntinfo(&sfs, MNT_WAIT)) > 0) {
421             for (i = 0; i < count; ++i) {
422                 calls += mountctl_scan(func, keyword, sfs[i].f_mntonname, fd);
423             }
424         } else if (count < 0) {
425             /* XXX */
426         }
427     }
428     return(calls);
429 }
430
431 static void
432 mountctl_list(const char *keyword, const char *mountpt, int __unused fd, void *info)
433 {
434     struct mountctl_journal_ret_status *rstat = info;
435
436     printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
437     printf("    membufsize=%s\n", numtostr(rstat->membufsize));
438     printf("    membufused=%s\n", numtostr(rstat->membufused));
439     printf("    membufunacked=%s\n", numtostr(rstat->membufunacked));
440     printf("    total_bytes=%s\n", numtostr(rstat->bytessent));
441     printf("    fifo_stalls=%lld\n", rstat->fifostalls);
442 }
443
444 static void
445 mountctl_add(const char *keyword, const char *mountpt, int fd)
446 {
447     struct mountctl_install_journal joinfo;
448     struct stat st1;
449     struct stat st2;
450     int error;
451
452     /*
453      * Make sure the file descriptor is not on the same filesystem as the
454      * mount point.  This isn't a perfect test, but it should catch most
455      * foot shooting.
456      */
457     if (fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) &&
458         stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev
459     ) {
460         fprintf(stderr, "%s:%s failed to add, the journal cannot be on the "
461                         "same filesystem being journaled!\n",
462                         mountpt, keyword);
463         exitCode = 1;
464         return;
465     }
466
467     /*
468      * Setup joinfo and issue the add
469      */
470     bzero(&joinfo, sizeof(joinfo));
471     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
472     if (memfifo_opt > 0)
473         joinfo.membufsize = memfifo_opt;
474     if (twoway_opt > 0)
475         joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
476     if (reversable_opt > 0)
477         joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
478
479     error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
480                         &joinfo, sizeof(joinfo), NULL, 0);
481     if (error == 0) {
482         fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
483     } else {
484         fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
485         exitCode = 1;
486     }
487 }
488
489 static void
490 mountctl_delete(const char *keyword, const char *mountpt, int __unused fd, void __unused *info)
491 {
492     struct mountctl_remove_journal joinfo;
493     int error;
494
495     bzero(&joinfo, sizeof(joinfo));
496     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
497     error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
498                         &joinfo, sizeof(joinfo), NULL, 0);
499     if (error == 0) {
500         fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
501     } else {
502         fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
503     }
504 }
505
506 static void
507 mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *info)
508 {
509     fprintf(stderr, "modify not yet implemented\n");
510 }
511
512
513 static volatile
514 void
515 usage(void)
516 {
517     printf(
518         " mountctl -l [tag/mountpt | mountpt:tag]\n"
519         " mountctl -a [-w output_path] [-x filedesc]\n"
520         "             [-o option] [-o option ...] mountpt:tag\n"
521         " mountctl -d [tag/mountpt | mountpt:tag]\n"
522         " mountctl -m [-o option] [-o option ...] [tag/mountpt | mountpt:tag]\n"
523         " mountctl -FZSCA [tag/mountpt | mountpt:tag]\n"
524     );
525     exit(1);
526 }
527
528 static
529 int64_t
530 getsize(const char *str)
531 {
532     const char *suffix;
533     int64_t val;
534
535     val = strtoll(str, &suffix, 0);
536     if (suffix) {
537         switch(*suffix) {
538         case 'b':
539             break;
540         case 't':
541             val *= 1024;
542             /* fall through */
543         case 'g':
544             val *= 1024;
545             /* fall through */
546         case 'm':
547             val *= 1024;
548             /* fall through */
549         case 'k':
550             val *= 1024;
551             /* fall through */
552             break;
553         default:
554             fprintf(stderr, "data value '%s' has unknown suffix\n", str);
555             exit(1);
556         }
557     }
558     return(val);
559 }
560
561 static
562 const char *
563 numtostr(int64_t num)
564 {
565     static char buf[64];
566     int n;
567     double v = num;
568
569     if (num < 1024)
570         snprintf(buf, sizeof(buf), "%lld", num);
571     else if (num < 10 * 1024)
572         snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
573     else if (num < 1024 * 1024)
574         snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
575     else if (num < 10 * 1024 * 1024)
576         snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
577     else if (num < 1024 * 1024 * 1024)
578         snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
579     else if (num < 10LL * 1024 * 1024 * 1024)
580         snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
581     else
582         snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
583     return(buf);
584 }
585