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