This is the initial skeleton for the new mountctl utility. Manual page,
authorMatthew Dillon <dillon@dragonflybsd.org>
Sat, 8 Jan 2005 23:22:35 +0000 (23:22 +0000)
committerMatthew Dillon <dillon@dragonflybsd.org>
Sat, 8 Jan 2005 23:22:35 +0000 (23:22 +0000)
options parsing, sanity checks, and the scan-callback framework.

sbin/mountctl/Makefile [new file with mode: 0644]
sbin/mountctl/mountctl.8 [new file with mode: 0644]
sbin/mountctl/mountctl.c [new file with mode: 0644]

diff --git a/sbin/mountctl/Makefile b/sbin/mountctl/Makefile
new file mode 100644 (file)
index 0000000..844fe17
--- /dev/null
@@ -0,0 +1,8 @@
+#
+# $DragonFly: src/sbin/mountctl/Makefile,v 1.1 2005/01/08 23:22:35 dillon Exp $
+
+PROG=  mountctl
+SRCS=  mountctl.c
+MAN=   mountctl.8
+
+.include <bsd.prog.mk>
diff --git a/sbin/mountctl/mountctl.8 b/sbin/mountctl/mountctl.8
new file mode 100644 (file)
index 0000000..3534a5a
--- /dev/null
@@ -0,0 +1,227 @@
+.\" Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
+.\" 
+.\" This code is derived from software contributed to The DragonFly Project
+.\" by Matthew Dillon <dillon@backplane.com>
+.\" 
+.\" 
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\" 
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $DragonFly: src/sbin/mountctl/mountctl.8,v 1.1 2005/01/08 23:22:35 dillon Exp $
+.\"
+.Dd January 8, 2005
+.Dt MOUNTCTL 8
+.Os
+.Sh NAME
+.Nm mountctl
+.Nd control journaling and other features on mounted file systems
+.Sh SYNOPSIS
+.Nm
+.Fl l
+.Op Ar tag/mountpt | tag mountpt
+.Nm
+.Fl a
+.Op Fl w Ar output_path
+.Op Fl x Ar filedesc
+.Op Fl o Ar option
+.Op Fl o Ar option ...
+.Ar tag mountpt
+.Nm
+.Fl d
+.Op Ar tag/mountpt | tag mountpt
+.Nm
+.Fl m
+.Op Fl o Ar option
+.Op Fl o Ar option ...
+.Op Ar tag/mountpt | tag mountpt
+.Nm
+.Fl FZSCA
+.Op Ar tag/mountpt | tag mountpt
+
+.Sh DESCRIPTION
+The
+.Nm
+utility manages journaling and (eventually) other features on a mounted
+filesystem. 
+.Pp
+.Nm
+.Fl l
+will list all installed journals in the system or on a particular mount point
+or tag, including their current state of operation.
+.Pp
+.Nm
+.Fl a
+will add a new journal to a mount point.  A mount may have any number of
+journals associated with it.  If no output path is specified the journal
+will be written to the standard output.  Options may be specified as
+described in the OPTION KEYWORDS section. 
+The tag is required and must be unique
+relative to any given mount, but you can use the same tag on multiple
+mount points if you wish (and control them all together by referencing that
+tag).
+The output path may represent any streamable entity.  You can, for example,
+output to a pipe into a program which does further buffering or processing
+of the journal.
+.Em WARNING
+A stalled journaling descriptor will stall the filesystem.  Eventually a
+kernel-implemented swap backing will be available for journals but that is
+not the case at the moment.
+.Pp
+.Nm
+.Fl d
+will remove the specified journal(s).  A mount point, a tag, or both may be
+specified.  This function will operate on all matching journals.
+.Pp
+.Nm
+.Fl m
+will modify the options associated with an existing journal.  Options are
+specified in the OPTION KEYWORDS section.
+.Sh OTHER OPTIONS
+.Bl -tag -width indent
+.It Fl F
+Flush a journal, equivalent to the 'flush' keyword.
+This option implies
+.Fl m .
+.It Fl Z
+Freeze a journal, equivalent to the 'freeze' keyword.
+This option implies
+.Fl m
+if
+.Fl a
+or
+.Fl d
+are not specified.
+.It Fl S
+Start or restart a journal, equivalent to the 'start' keyword.
+This option implies
+.Fl m .
+.It Fl C
+Close a journal, equivalent to the 'close' keyword.
+This option implies
+.Fl m .
+.It Fl A
+Abort a journal, equivalent to the 'abort' keyword.
+This option implies
+.Fl m .
+.It Fl w Ar output_path
+Change a journal's stream descriptor to the specified path.
+This option implies
+.Fl m
+if
+.Fl a
+or
+.Fl d
+are not specified.
+.It Fl x Ar filedesc
+Change a journal's stream descriptor to the specified file descriptor number.
+This option implies
+.Fl m
+if
+.Fl a
+or
+.Fl d
+are not specified.
+.El
+.Sh OPTION KEYWORDS
+Options keywords may be comma delimited without whitespace within a single
+.Fl o
+or via multiple
+.Fl o
+options.  Some keywords require a value which is specified as
+.Ar keyword=value .
+Any option may be prefixed with 'no' or 'non' to turn off the option.
+Some options are one-shot and have no 'no' or 'non' equivalent.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Ar reversable
+Generate a reversable journaling stream.  This allows the target to run
+the journal backwards as well as forwards to 'undo' operations.  This is the
+default.
+.It Ar twoway
+Indicate that the journaling stream is a two-way stream and that transaction
+id acknowledgements will be returned.
+.It Ar memfifo=size[k,m]
+Specify the size of the in-kernel memory FIFO used to buffer the journaling
+stream between processes doing filesystem operations and the worker thread
+writing out the journal.  Since the kernel has limited virtual memory 
+buffers larger then 4MB are not recommended.
+.It Ar swapfifo=size[k,m,g]
+Specify the size of the kernel-managed swap-backed FIFO used to buffer
+overflows.
+.It Ar path=filepath
+Switch the journal's output stream to a new file.  This feature is typically
+used to restart a dead stream. 
+Note that the
+.Fl w
+option is equivalent to specifying the path option.  Both should not be 
+specified.
+.It Ar fd=filedesc
+Switch the journal's output stream to a file descriptor specified by number.
+Use file descriptor 1 if you wish to reopen the journal to the current
+stdout.  This feature is typically used to restart a dead stream (for example
+if a TCP stream fails).
+Note that the
+.Fl w
+option is equivalent to specifying the path option.  Both should not be 
+specified.
+.It Ar freeze
+Freeze the worker thread.  This may cause the filesystem to stall once
+the memory fifo has filled up.  A freeze point record will be written to
+the journal.  If used as part of the creation of a new journal via
+.Fl a ,
+this option will prevent any initial output to the journal and a freeze
+point record will NOT be written.  Again, the filesystem will stall if
+the memory fifo fills up.
+.It Ar start
+Start or restart the worker thread after a freeze.
+.It Ar close
+Close the journal.  Any transactions still under way will be allowed to
+complete, a closing record will be generated, and the journaling descriptor
+will be closed.  If the connection is two-way the journal will away a final
+acknowledgement of the closing record before closing the descriptor.
+.It Ar abort
+Close the journal.  Any currently buffered data will be aborted.  No close
+record is written.  The journaling stream is immediately closed.
+.It Ar flush
+Flush the journal.  All currently buffered data is flushed.  The command
+does not return until the write suceeds and, if the connection is two-way,
+and acknowledgement has been returned for journaled data buffered at the
+time the flush was issued.
+.El
+.Pp
+.Sh FILES
+.Sh SEE ALSO
+.Xr mount 2 ,
+.Sh BUGS
+.Sh CAVEATS
+This utility is currently under construction and not all features have been
+implemented yet.  In fact, most have not.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in DragonFly .
diff --git a/sbin/mountctl/mountctl.c b/sbin/mountctl/mountctl.c
new file mode 100644 (file)
index 0000000..b125b7f
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
+ * 
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 3. Neither the name of The DragonFly Project nor the names of its
+ *    contributors may be used to endorse or promote products derived
+ *    from this software without specific, prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * 
+ * $DragonFly: src/sbin/mountctl/mountctl.c,v 1.1 2005/01/08 23:22:35 dillon Exp $
+ */
+/*
+ * This utility implements the userland mountctl command which is used to
+ * manage high level journaling on mount points.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/mountctl.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+static volatile void usage(void);
+static void parse_option_keyword(const char *opt, 
+               const char **wopt, const char **xopt);
+static int64_t getsize(const char *str);
+
+static void mountctl_scan(void (*func)(const char *, const char *, int),
+               const char *keyword, const char *mountpt, int fd);
+static void mountctl_list(const char *keyword, const char *mountpt,
+               int __unused fd);
+static void mountctl_add(const char *keyword, const char *mountpt, int fd);
+static void mountctl_delete(const char *keyword, const char *mountpt,
+               int __unused fd);
+static void mountctl_modify(const char *keyword, const char *mountpt, int fd);
+
+/*
+ * For all options 0 means unspecified, -1 means noOPT or nonOPT, and a
+ * positive number indicates enabling or execution of the option.
+ */
+static int freeze_opt;
+static int start_opt;
+static int close_opt;
+static int abort_opt;
+static int flush_opt;
+static int reversable_opt;
+static int twoway_opt;
+static int64_t memfifo_opt;
+static int64_t swapfifo_opt;
+
+int
+main(int ac, char **av)
+{
+    int fd;
+    int ch;
+    int aopt = 0;
+    int dopt = 0;
+    int fopt = 0;
+    int lopt = 0;
+    int mopt = 0;
+    int mimplied = 0;
+    const char *wopt = NULL;
+    const char *xopt = NULL;
+    const char *keyword = NULL;
+    const char *mountpt = NULL;
+
+    while ((ch = getopt(ac, av, "adflo:mw:x:ACFSZ")) != -1) {
+       switch(ch) {
+       case 'a':
+           aopt = 1;
+           if (aopt + dopt + lopt + mopt != 1) {
+               fprintf(stderr, "too many action options specified\n");
+               usage();
+           }
+           break;
+       case 'd':
+           dopt = 1;
+           if (aopt + dopt + lopt + mopt != 1) {
+               fprintf(stderr, "too many action options specified\n");
+               usage();
+           }
+           break;
+       case 'f':
+           fopt = 1;
+           break;
+       case 'l':
+           lopt = 1;
+           if (aopt + dopt + lopt + mopt != 1) {
+               fprintf(stderr, "too many action options specified\n");
+               usage();
+           }
+           break;
+       case 'o':
+           parse_option_keyword(optarg, &wopt, &xopt);
+           break;
+       case 'm':
+           mopt = 1;
+           if (aopt + dopt + lopt + mopt != 1) {
+               fprintf(stderr, "too many action options specified\n");
+               usage();
+           }
+           break;
+       case 'w':
+           wopt = optarg;
+           mimplied = 1;
+           break;
+       case 'x':
+           xopt = optarg;
+           mimplied = 1;
+           break;
+       case 'A':
+           mimplied = 1;
+           abort_opt = 1;
+           break;
+       case 'C':
+           mimplied = 1;
+           close_opt = 1;
+           break;
+       case 'F':
+           mimplied = 1;
+           flush_opt = 1;
+           break;
+       case 'S':
+           mimplied = 1;
+           start_opt = 1;
+           break;
+       case 'Z':
+           mimplied = 1;
+           freeze_opt = 1;
+           break;
+       default:
+           fprintf(stderr, "unknown option: -%c\n", optopt);
+           usage();
+       }
+    }
+    ac -= optind;
+    av += optind;
+
+    /*
+     * Parse the keyword and/or mount point.
+     */
+    switch(ac) {
+    case 0:
+       if (aopt == 0) {
+           fprintf(stderr, "action requires a tag and/or mount "
+                           "point to be specified\n");
+           usage();
+       }
+       break;
+    case 1:
+       if (av[0][0] == '/')
+           mountpt = av[0];
+       else
+           keyword = av[0];
+       break;
+    case 2:
+       keyword = av[0];
+       mountpt = av[1];
+       if (*mountpt != '/') {
+           fprintf(stderr, "mount points must begin with '/'\n");
+           usage();
+       }
+       break;
+    default:
+       fprintf(stderr, "unexpected extra arguments to command\n");
+       usage();
+    }
+
+    /*
+     * Additional sanity checks
+     */
+    if (aopt + dopt + lopt + mopt + mimplied == 0) {
+       fprintf(stderr, "no action or implied action options were specified\n");
+       usage();
+    }
+    if (mimplied && aopt + dopt + lopt == 0)
+       mopt = 1;
+    if ((wopt || xopt) && !(aopt || mopt)) {
+       fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a\n");
+       usage();
+    }
+    if (aopt && (keyword == NULL || mountpt == NULL)) {
+       fprintf(stderr, "a keyword AND a mountpt must be specified "
+                       "when adding a journal\n");
+       usage();
+    }
+    if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
+       fprintf(stderr, "a keyword, a mountpt, or both must be specified "
+                       "when modifying or deleting a journal, unless "
+                       "-f is also specified for safety\n");
+       usage();
+    }
+
+    /*
+     * Open the journaling file descriptor if required.
+     */
+    if (wopt && xopt) {
+       fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
+       exit(1);
+    } else if (wopt) {
+       if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
+           fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
+           exit(1);
+       }
+    } else if (xopt) {
+       fd = strtol(xopt, NULL, 0);
+    } else if (aopt) {
+       fd = 1;         /* stdout default for -a */
+    } else {
+       fd = -1;
+    }
+
+    /*
+     * And finally execute the core command.
+     */
+    if (lopt)
+       mountctl_scan(mountctl_list, keyword, mountpt, fd);
+    if (aopt)
+       mountctl_add(keyword, mountpt, fd);
+    if (dopt)
+       mountctl_scan(mountctl_delete, keyword, mountpt, -1);
+    if (mopt)
+       mountctl_scan(mountctl_modify, keyword, mountpt, fd);
+
+    return(0);
+}
+
+static void
+parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
+{
+    char *str = strdup(opt);
+    char *name;
+    char *val;
+    int negate;
+    int hasval;
+    int cannotnegate;
+
+    /*
+     * multiple comma delimited options may be specified.
+     */
+    while ((name = strsep(&str, ",")) != NULL) {
+       /*
+        * some options have associated data.
+        */
+       if ((val = strchr(name, '=')) != NULL)
+           *val++ = 0;
+
+       /*
+        * options beginning with 'no' or 'non' are negated.  A positive
+        * number means not negated, a negative number means negated.
+        */
+       negate = 1;
+       cannotnegate = 0;
+       hasval = 0;
+       if (strncmp(name, "non", 3) == 0) {
+           name += 3;
+           negate = -1;
+       } else if (strncmp(name, "no", 2) == 0) {
+           name += 2;
+           negate = -1;
+       }
+
+       /*
+        * Parse supported options
+        */
+       if (strcmp(name, "reversable") == 0) {
+           reversable_opt = negate;
+       } else if (strcmp(name, "twoway") == 0) {
+           twoway_opt = negate;
+       } else if (strcmp(name, "memfifo") == 0) {
+           cannotnegate = 1;
+           hasval = 1;
+           if (val) {
+               if ((memfifo_opt = getsize(val)) == 0)
+                   memfifo_opt = -1;
+           }
+       } else if (strcmp(name, "swapfifo") == 0) {
+           if (val) {
+               hasval = 1;
+               if ((swapfifo_opt = getsize(val)) == 0)
+                   swapfifo_opt = -1;
+           } else if (negate < 0) {
+               swapfifo_opt = -1;
+           } else {
+               hasval = 1;     /* force error */
+           }
+       } else if (strcmp(name, "fd") == 0) {
+           cannotnegate = 1;
+           hasval = 1;
+           if (val)
+               *xopt = val;
+       } else if (strcmp(name, "path") == 0) {
+           cannotnegate = 1;
+           hasval = 1;
+           if (val)
+               *wopt = val;
+       } else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
+           if (negate < 0)
+               start_opt = -negate;
+           else
+               freeze_opt = negate;
+       } else if (strcmp(name, "start") == 0) {
+           if (negate < 0)
+               freeze_opt = -negate;
+           else
+               start_opt = negate;
+       } else if (strcmp(name, "close") == 0) {
+           close_opt = negate;
+       } else if (strcmp(name, "abort") == 0) {
+           abort_opt = negate;
+       } else if (strcmp(name, "flush") == 0) {
+           flush_opt = negate;
+       } else {
+           fprintf(stderr, "unknown option keyword: %s\n", name);
+           exit(1);
+       }
+
+       /*
+        * Sanity checks
+        */
+       if (cannotnegate && negate < 0) {
+           fprintf(stderr, "option %s may not be negated\n", name);
+           exit(1);
+       }
+       if (hasval && val == NULL) {
+           fprintf(stderr, "option %s requires assigned data\n", name);
+           exit(1);
+       }
+       if (hasval == 0 && val) {
+           fprintf(stderr, "option %s does not take an assignment\n", name);
+           exit(1);
+       }
+
+    }
+}
+
+static void
+mountctl_scan(void (*func)(const char *, const char *, int),
+           const char *keyword, const char *mountpt, int fd)
+{
+    fprintf(stderr, "scan not yet implemented\n");
+}
+
+static void
+mountctl_list(const char *keyword, const char *mountpt, int __unused fd)
+{
+    fprintf(stderr, "list not yet implemented\n");
+}
+
+static void
+mountctl_add(const char *keyword, const char *mountpt, int fd)
+{
+    fprintf(stderr, "add not yet implemented\n");
+}
+
+static void
+mountctl_delete(const char *keyword, const char *mountpt, int __unused fd)
+{
+    fprintf(stderr, "delete not yet implemented\n");
+}
+
+static void
+mountctl_modify(const char *keyword, const char *mountpt, int fd)
+{
+    fprintf(stderr, "modify not yet implemented\n");
+}
+
+
+static volatile
+void
+usage(void)
+{
+    printf(
+       " mountctl -l [tag/mountpt | tag mountpt]\n"
+       " mountctl -a [-w output_path] [-x filedesc]\n"
+       "             [-o option] [-o option ...] tag mountpt\n"
+       " mountctl -d [tag/mountpt | tag mountpt]\n"
+       " mountctl -m [-o option] [-o option ...] [tag/mountpt | tag mountpt]\n"
+       " mountctl -FZSCA [tag/mountpt | tag mountpt]\n"
+    );
+    exit(1);
+}
+
+static
+int64_t
+getsize(const char *str)
+{
+    const char *suffix;
+    int64_t val;
+
+    val = strtoll(str, &suffix, 0);
+    if (suffix) {
+       switch(*suffix) {
+       case 'b':
+           break;
+       case 't':
+           val *= 1024;
+           /* fall through */
+       case 'g':
+           val *= 1024;
+           /* fall through */
+       case 'm':
+           val *= 1024;
+           /* fall through */
+       case 'k':
+           val *= 1024;
+           /* fall through */
+           break;
+       default:
+           fprintf(stderr, "data value '%s' has unknown suffix\n", str);
+           exit(1);
+       }
+    }
+    return(val);
+}
+