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