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