xref: /dragonfly/sbin/mountctl/mountctl.c (revision 7bc7e232)
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.9 2007/11/25 01:28:23 swildner 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 volatile 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
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
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
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
459 mountctl_list(const char *keyword, const char *mountpt, int __unused fd, void *info)
460 {
461     struct mountctl_journal_ret_status *rstat = info;
462 
463     printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
464     printf("    membufsize=%s\n", numtostr(rstat->membufsize));
465     printf("    membufused=%s\n", numtostr(rstat->membufused));
466     printf("    membufunacked=%s\n", numtostr(rstat->membufunacked));
467     printf("    total_bytes=%s\n", numtostr(rstat->bytessent));
468     printf("    fifo_stalls=%lld\n", rstat->fifostalls);
469 }
470 
471 static void
472 mountctl_add(const char *keyword, const char *mountpt, int fd)
473 {
474     struct mountctl_install_journal joinfo;
475     struct stat st1;
476     struct stat st2;
477     int error;
478 
479     /*
480      * Make sure the file descriptor is not on the same filesystem as the
481      * mount point.  This isn't a perfect test, but it should catch most
482      * foot shooting.
483      */
484     if (output_safety_override_opt == 0 &&
485 	fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) &&
486 	stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev
487     ) {
488 	fprintf(stderr, "%s:%s failed to add, the journal cannot be on the "
489 			"same filesystem being journaled!\n",
490 			mountpt, keyword);
491 	exitCode = 1;
492 	return;
493     }
494 
495     /*
496      * Setup joinfo and issue the add
497      */
498     bzero(&joinfo, sizeof(joinfo));
499     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
500     if (memfifo_opt > 0)
501 	joinfo.membufsize = memfifo_opt;
502     if (twoway_opt > 0)
503 	joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
504     if (reversable_opt > 0)
505 	joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
506 
507     error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
508 			&joinfo, sizeof(joinfo), NULL, 0);
509     if (error == 0) {
510 	fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
511     } else {
512 	fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
513 	exitCode = 1;
514     }
515 }
516 
517 static void
518 mountctl_restart(const char *keyword, const char *mountpt,
519 		 int fd, void __unused *info)
520 {
521     struct mountctl_restart_journal joinfo;
522     int error;
523 
524     /* XXX make sure descriptor is not on same filesystem as journal */
525 
526     bzero(&joinfo, sizeof(joinfo));
527 
528     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
529     if (twoway_opt > 0)
530 	joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
531     if (reversable_opt > 0)
532 	joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
533 
534     error = mountctl(mountpt, MOUNTCTL_RESTART_VFS_JOURNAL, fd,
535 			&joinfo, sizeof(joinfo), NULL, 0);
536     if (error == 0) {
537 	fprintf(stderr, "%s:%s restarted\n", mountpt, joinfo.id);
538     } else {
539 	fprintf(stderr, "%s:%s restart failed, error %s\n", mountpt, joinfo.id, strerror(errno));
540     }
541 }
542 
543 static void
544 mountctl_delete(const char *keyword, const char *mountpt,
545 		int __unused fd, void __unused *info)
546 {
547     struct mountctl_remove_journal joinfo;
548     int error;
549 
550     bzero(&joinfo, sizeof(joinfo));
551     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
552     error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
553 			&joinfo, sizeof(joinfo), NULL, 0);
554     if (error == 0) {
555 	fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
556     } else {
557 	fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
558     }
559 }
560 
561 static void
562 mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *info)
563 {
564     fprintf(stderr, "modify not yet implemented\n");
565 }
566 
567 
568 static volatile void
569 usage(void)
570 {
571     printf(
572 	" mountctl -l [tag/mountpt | mountpt:tag]\n"
573 	" mountctl -a [-w output_path] [-x filedesc]\n"
574 	"             [-o option] [-o option ...] mountpt:tag\n"
575 	" mountctl -d [tag/mountpt | mountpt:tag]\n"
576 	" mountctl -m [-o option] [-o option ...] [tag/mountpt | mountpt:tag]\n"
577 	" mountctl -FZSCA [tag/mountpt | mountpt:tag]\n"
578     );
579     exit(1);
580 }
581 
582 static int64_t
583 getsize(const char *str)
584 {
585     const char *suffix;
586     int64_t val;
587 
588     val = strtoll(str, &suffix, 0);
589     if (suffix) {
590 	switch(*suffix) {
591 	case 'b':
592 	    break;
593 	case 't':
594 	    val *= 1024;
595 	    /* fall through */
596 	case 'g':
597 	    val *= 1024;
598 	    /* fall through */
599 	case 'm':
600 	    val *= 1024;
601 	    /* fall through */
602 	case 'k':
603 	    val *= 1024;
604 	    /* fall through */
605 	    break;
606 	default:
607 	    fprintf(stderr, "data value '%s' has unknown suffix\n", str);
608 	    exit(1);
609 	}
610     }
611     return(val);
612 }
613 
614 static const char *
615 numtostr(int64_t num)
616 {
617     static char buf[64];
618     int n;
619     double v = num;
620 
621     if (num < 1024)
622 	snprintf(buf, sizeof(buf), "%lld", num);
623     else if (num < 10 * 1024)
624 	snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
625     else if (num < 1024 * 1024)
626 	snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
627     else if (num < 10 * 1024 * 1024)
628 	snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
629     else if (num < 1024 * 1024 * 1024)
630 	snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
631     else if (num < 10LL * 1024 * 1024 * 1024)
632 	snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
633     else
634 	snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
635     return(buf);
636 }
637 
638