Merge branch 'refs/heads/t/kill-w'
authorAndre Noll <maan@tuebingen.mpg.de>
Sat, 18 Nov 2017 14:54:03 +0000 (15:54 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Sat, 18 Nov 2017 14:56:55 +0000 (15:56 +0100)
Two patches which make life easier for shutdown scripts which need
to terminate the dss process, but would like to wait until the exit
hook completed.

The merge conflicted in dss.suite, but this was trivial to fix.

Cooking for a week.

* refs/heads/t/kill-w:
  kill: New option --wait.
  run: Wait for children to die.

1  2 
NEWS
dss.c
dss.suite
err.h

diff --combined NEWS
--- 1/NEWS
--- 2/NEWS
+++ b/NEWS
@@@ -1,43 -1,26 +1,49 @@@
  -----------------------
 -x.y.z (to be announced)
 +1.0.0 (to be announced)
  -----------------------
  
 - - Improved error diagnostics for the kill subcommand.
 + - The gengetopt option parser has been replaced by the
 + [lopsub](http://people.tuebingen.mpg.de/maan/lopsub) library. Hence
 + lopsub must be installed to compile this package. help2man is
 + no longer required since lopsub has built-in roff support.
 +
 + - New subcommand "configtest" to check the command line options and
 + the configuration file for syntactic correctness.
 +
 + - New option: --mountpoint. If this option is given, dss aborts if
 + no file system is mounted on the destination directory.
 +
 + - New option --checksum to let rsync compute checksums occasionally.
 +
++ - The kill subcommand gained the new --wait option which instructs dss
++ to wait until the signalled process has terminated.
   - The --no-resume option has been removed.
  
 - - The gengetopt option parser has been replaced by the
 - [lopsub](http://people.tuebingen.mpg.de/~maan/lopsub) library. Hence
 - lopsub must be installed to compile this package. Also help2man is
 - no longer required since lopsub has built-in roff support.
++ - On exit, the run subcommand now waits for any previously spawned
++ rsync or rm processes to terminate.
 - - "make install" will install the executable and the man page.
 + - The ls subcommand now shows the age of incomplete snapshots rather
 + than 0:00.
  
   - In run mode, dss no longer exits successfully if another instance
   is already running.
  
 - - New option --checksum to let rsync compute checksums occasionally.
 + - The command specified as the argument to --exit-hook is now subject
 + to word splitting. Previously, the string was executed as-is.
 +
 + - Improved error diagnostics for the kill subcommand.
 +
 + - For all subcommands other than "run", timestamps and function names
 + are omitted from the log output.
 +
 + - "make install" will install the executable and the man page.
  
   - CFLAGS, CPPFLAGS and LDFLAGS can now be used to override the flags
   of the build system.
  
 + - The dss logo is now created with ImageMagick rather than dia.
 +
  ------------------
  0.1.7 (2017-04-17)
  ------------------
diff --combined dss.c
--- 1/dss.c
--- 2/dss.c
+++ b/dss.c
@@@ -63,8 -63,6 +63,8 @@@ static struct lls_parse_result *cmdline
  
  /** Parsed subcommand options. */
  static struct lls_parse_result *cmdline_sublpr, *sublpr;
 +/* The executing subcommand (NULL at startup). */
 +static const struct lls_command *subcmd;
  /** Wether daemon_init() was called. */
  static bool daemonized;
  /** Non-NULL if we log to a file. */
@@@ -244,19 -242,16 +244,19 @@@ __printf_1_2 void dss_log(const char* f
        if (loglevel < lpr_ll)
                return;
        outfd = logfile? logfile : stderr;
 -      time(&t1);
 -      tm = localtime(&t1);
 -      strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
 -      fprintf(outfd, "%s ", str);
 -      if (lpr_ll <= INFO)
 -              fprintf(outfd, "%i: ", loglevel);
 +      if (subcmd == CMD_PTR(RUN)) {
 +              time(&t1);
 +              tm = localtime(&t1);
 +              strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
 +              fprintf(outfd, "%s ", str);
 +              if (lpr_ll <= INFO)
 +                      fprintf(outfd, "%i: ", loglevel);
 +      }
 +      if (subcmd == CMD_PTR(RUN))
  #ifdef DSS_NO_FUNC_NAMES
 -      fprintf(outfd, "%s:%d: ", location_file, location_line);
 +              fprintf(outfd, "%s:%d: ", location_file, location_line);
  #else
 -      fprintf(outfd, "%s: ", location_func);
 +              fprintf(outfd, "%s: ", location_func);
  #endif
        va_start(argp, fmt);
        vfprintf(outfd, fmt, argp);
@@@ -287,11 -282,13 +287,13 @@@ static char *get_config_file_name(void
        return config_file;
  }
  
- static int send_signal(int sig)
+ static int send_signal(int sig, bool wait)
  {
        pid_t pid;
        char *config_file = get_config_file_name();
        int ret = get_dss_pid(config_file, &pid);
+       unsigned ms = 32;
+       struct timespec ts;
  
        free(config_file);
        if (ret < 0)
                dss_msg("%d\n", (int)pid);
                return 0;
        }
 +      DSS_NOTICE_LOG(("sending signal %d to pid %d\n", sig, (int)pid));
        ret = kill(pid, sig);
        if (ret < 0)
                return -ERRNO_TO_DSS_ERROR(errno);
-       return 1;
+       if (!wait)
+               return 1;
+       while (ms < 5000) {
+               ts.tv_sec = ms / 1000;
+               ts.tv_nsec = (ms % 1000) * 1000 * 1000;
+               ret = nanosleep(&ts, NULL);
+               if (ret < 0)
+                       return -ERRNO_TO_DSS_ERROR(errno);
+               ret = kill(pid, 0);
+               if (ret < 0) {
+                       if (errno != ESRCH)
+                               return -ERRNO_TO_DSS_ERROR(errno);
+                       return 1;
+               }
+               ms *= 2;
+       }
+       return -E_KILL_TIMEOUT;
  }
  
  struct signal_info {
@@@ -363,6 -375,7 +381,7 @@@ static const struct signal_info signal_
  
  static int com_kill(void)
  {
+       bool w_given = OPT_GIVEN(KILL, WAIT);
        const char *arg = OPT_STRING_VAL(KILL, SIGNAL);
        int ret, i;
  
                        return ret;
                if (val < 0 || val > SIGRTMAX)
                        return -ERRNO_TO_DSS_ERROR(EINVAL);
-               return send_signal(val);
+               return send_signal(val, w_given);
        }
        if (strncasecmp(arg, "sig", 3) == 0)
                arg += 3;
        if (strcasecmp(arg, "CLD") == 0)
-               return send_signal(SIGCHLD);
+               return send_signal(SIGCHLD, w_given);
        if (strcasecmp(arg, "IOT") == 0)
-               return send_signal(SIGABRT);
+               return send_signal(SIGABRT, w_given);
        for (i = 0; i < SIGNAL_TABLE_SIZE; i++)
                if (strcasecmp(arg, signal_table[i].name) == 0)
-                       return send_signal(signal_table[i].num);
+                       return send_signal(signal_table[i].num, w_given);
        DSS_ERROR_LOG(("invalid sigspec: %s\n", arg));
        return -ERRNO_TO_DSS_ERROR(EINVAL);
  }
@@@ -521,7 -534,7 +540,7 @@@ static struct snapshot *find_orphaned_s
        struct snapshot *s;
        int i;
  
 -      DSS_DEBUG_LOG(("looking for orphaned snapshots\n"));
 +      DSS_DEBUG_LOG(("looking for old incomplete snapshots\n"));
        FOR_EACH_SNAPSHOT(s, i, sl) {
                if (snapshot_is_being_created(s))
                        continue;
@@@ -735,7 -748,6 +754,7 @@@ static int try_to_free_disk_space(void
        if (!low_disk_space)
                goto out;
        DSS_WARNING_LOG(("disk space low and nothing obvious to remove\n"));
 +      why = "oldest";
        victim = find_oldest_removable_snapshot(&sl);
        if (victim)
                goto remove;
@@@ -1060,39 -1072,20 +1079,39 @@@ static int handle_sigchld(void
        return -E_BUG;
  }
  
 +/* also checks if . is a mountpoint, if --mountpoint was given */
  static int change_to_dest_dir(void)
  {
        int ret;
        const char *dd = OPT_STRING_VAL(DSS, DEST_DIR);
 +      struct stat dot, dotdot;
  
        DSS_INFO_LOG(("changing cwd to %s\n", dd));
 -      if (chdir(dd) >= 0)
 -              return 1;
 -      ret = -ERRNO_TO_DSS_ERROR(errno);
 -      DSS_ERROR_LOG(("could not change cwd to %s\n", dd));
 -      return ret;
 +      if (chdir(dd) < 0) {
 +              ret = -ERRNO_TO_DSS_ERROR(errno);
 +              DSS_ERROR_LOG(("could not change cwd to %s\n", dd));
 +              return ret;
 +      }
 +      if (!OPT_GIVEN(DSS, MOUNTPOINT))
 +              return 0;
 +      if (stat(".", &dot) < 0) {
 +              ret = -ERRNO_TO_DSS_ERROR(errno);
 +              DSS_ERROR_LOG(("could not stat .\n"));
 +              return ret;
 +      }
 +      if (stat("..", &dotdot) < 0) {
 +              ret = -ERRNO_TO_DSS_ERROR(errno);
 +              DSS_ERROR_LOG(("could not stat ..\n"));
 +              return ret;
 +      }
 +      if (dot.st_dev == dotdot.st_dev && dot.st_ino != dotdot.st_ino) {
 +              DSS_ERROR_LOG(("mountpoint check failed for %s\n", dd));
 +              return -E_MOUNTPOINT;
 +      }
 +      return 1;
  }
  
 -static int check_config(const struct lls_command *cmd)
 +static int check_config(void)
  {
        int ret;
        uint32_t unit_interval = OPT_UINT32_VAL(DSS, UNIT_INTERVAL);
                DSS_ERROR_LOG(("bad number of intervals: %i\n", num_intervals));
                return -E_INVALID_NUMBER;
        }
 -      if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE))
 +      if (subcmd == CMD_PTR(RUN) || subcmd == CMD_PTR(CREATE))
                if (!OPT_GIVEN(DSS, SOURCE_DIR)) {
                        DSS_ERROR_LOG(("--source-dir required\n"));
                        return -E_SYNTAX;
                }
 -      if (cmd == CMD_PTR(RUN) || cmd == CMD_PTR(CREATE)
 -                      || cmd == CMD_PTR(LS) || cmd == CMD_PTR(PRUNE)) {
 +      if (subcmd == CMD_PTR(RUN) || subcmd == CMD_PTR(CREATE)
 +                      || subcmd == CMD_PTR(LS) || subcmd == CMD_PTR(PRUNE)) {
                if (!OPT_GIVEN(DSS, DEST_DIR)) {
                        DSS_ERROR_LOG(("--dest-dir required\n"));
                        return -E_SYNTAX;
@@@ -1252,7 -1245,7 +1271,7 @@@ static int handle_sighup(void
        ret = parse_config_file(true /* SIGHUP */, CMD_PTR(RUN));
        if (ret < 0)
                return ret;
 -      ret = check_config(CMD_PTR(RUN));
 +      ret = check_config();
        if (ret < 0)
                return ret;
        close_log(logfile);
  
  static void exit_hook(int exit_code)
  {
 -      const char *argv[3];
        pid_t pid;
 -
 -      argv[0] = OPT_STRING_VAL(DSS, EXIT_HOOK);
 -      argv[1] = dss_strerror(-exit_code);
 -      argv[2] = NULL;
 -
 -      DSS_NOTICE_LOG(("executing %s %s\n", argv[0], argv[1]));
 -      dss_exec(&pid, argv[0], (char **)argv);
 +      char **argv, *tmp = dss_strdup(OPT_STRING_VAL(DSS, EXIT_HOOK));
 +      unsigned n = split_args(tmp, &argv, " \t");
 +
 +      n++;
 +      argv = dss_realloc(argv, (n + 1) * sizeof(char *));
 +      argv[n - 1] = dss_strdup(dss_strerror(-exit_code));
 +      argv[n] = NULL;
 +      dss_exec(&pid, argv[0], argv);
 +      free(argv[n - 1]);
 +      free(argv);
 +      free(tmp);
  }
  
  static void lock_dss_or_die(void)
@@@ -1594,6 -1584,8 +1613,8 @@@ static int com_run(void
                ret = -E_BUG;
        kill_children();
        exit_hook(ret);
+       while (wait(NULL) >= 0 || errno != ECHILD)
+               ; /* still have children to wait for */
        return ret;
  }
  EXPORT_CMD_HANDLER(run);
@@@ -1712,30 -1704,19 +1733,30 @@@ static int com_ls(void
        int i;
        struct snapshot_list sl;
        struct snapshot *s;
 +      int64_t now = get_current_time();
  
        dss_get_snapshot_list(&sl);
        FOR_EACH_SNAPSHOT(s, i, &sl) {
 -              int64_t d = 0;
 +              int64_t d;
                if (s->flags & SS_COMPLETE)
                        d = (s->completion_time - s->creation_time) / 60;
 -              dss_msg("%u\t%s\t%3" PRId64 ":%02" PRId64 "\n", s->interval, s->name, d/60, d%60);
 +              else
 +                      d = (now - s->creation_time) / 60;
 +              dss_msg("%u\t%s\t%3" PRId64 ":%02" PRId64 "\n", s->interval,
 +                      s->name, d / 60, d % 60);
        }
        free_snapshot_list(&sl);
        return 1;
  }
  EXPORT_CMD_HANDLER(ls);
  
 +static int com_configtest(void)
 +{
 +      printf("Syntax Ok\n");
 +      return 0;
 +}
 +EXPORT_CMD_HANDLER(configtest);
 +
  static int setup_signal_handling(void)
  {
        int ret;
@@@ -1777,7 -1758,7 +1798,7 @@@ static void show_subcommand_summary(voi
        for (i = 1; (cmd = lls_cmd(i, dss_suite)); i++) {
                const char *name = lls_command_name(cmd);
                const char *purpose = lls_purpose(cmd);
 -              printf("%-10s%s\n", name, purpose);
 +              printf("%-11s%s\n", name, purpose);
        }
        exit(EXIT_SUCCESS);
  }
  int main(int argc, char **argv)
  {
        int ret;
 -      const struct lls_command *cmd = CMD_PTR(DSS);
        char *errctx = NULL;
        unsigned num_inputs;
 -      const struct dss_user_data *ud = NULL;
 +      const struct dss_user_data *ud;
  
 -      ret = lls_parse(argc, argv, cmd, &cmdline_lpr, &errctx);
 +      ret = lls_parse(argc, argv, CMD_PTR(DSS), &cmdline_lpr, &errctx);
        if (ret < 0) {
                ret = lopsub_error(ret, &errctx);
                goto out;
        }
        lpr = cmdline_lpr;
 -      ret = parse_config_file(false /* no SIGHUP */, cmd);
 +      ret = parse_config_file(false /* no SIGHUP */, CMD_PTR(DSS));
        if (ret < 0)
                goto out;
        handle_version_and_help();
                ret = lopsub_error(ret, &errctx);
                goto out;
        }
 -      cmd = lls_cmd(ret, dss_suite);
 -      ret = lls_parse(num_inputs, argv + argc - num_inputs, cmd,
 +      subcmd = lls_cmd(ret, dss_suite);
 +      ret = lls_parse(num_inputs, argv + argc - num_inputs, subcmd,
                &cmdline_sublpr, &errctx);
        if (ret < 0) {
                ret = lopsub_error(ret, &errctx);
                goto out;
        }
        sublpr = cmdline_sublpr;
 -      ret = parse_config_file(false /* no SIGHUP */, cmd);
 +      ret = parse_config_file(false /* no SIGHUP */, subcmd);
        if (ret < 0)
                goto out;
 -      ret = check_config(cmd);
 +      ret = check_config();
        if (ret < 0)
                goto out;
        ret = setup_signal_handling();
        if (ret < 0)
                goto out;
 -      ud = lls_user_data(cmd);
 +      ud = lls_user_data(subcmd);
        ret = ud->handler();
        signal_shutdown();
  out:
        lls_free_parse_result(lpr, CMD_PTR(DSS));
        if (lpr != cmdline_lpr)
                lls_free_parse_result(cmdline_lpr, CMD_PTR(DSS));
 -      lls_free_parse_result(sublpr, cmd);
 +      lls_free_parse_result(sublpr, subcmd);
        if (sublpr != cmdline_sublpr)
 -              lls_free_parse_result(cmdline_sublpr, cmd);
 +              lls_free_parse_result(cmdline_sublpr, subcmd);
        exit(ret >= 0? EXIT_SUCCESS : EXIT_FAILURE);
  }
diff --combined dss.suite
+++ b/dss.suite
@@@ -34,7 -34,7 +34,7 @@@ caption = Subcommand
                        However, there is one exception to this rule: The run subcommand
                        re-reads the configuration file when it receives the HUP signal. In
                        this case the options in the config file override any options that
 -                      were previously given at the command line. This allows to change the
 +                      were previously given at the command line. This allows changing the
                        configuration of a running dss process by sending SIGHUP.
                [/help]
        [option loglevel]
  
                        This option is mandatory for all subcommands except kill.
                [/help]
 +      [option mountpoint]
 +              summary = abort if destination directory is not a mountpoint
 +              [help]
 +                      This option checks whether a file system is mounted on the directory
 +                      specified as the argument to --dest-dir. Operation proceeds only
 +                      if this is the case. Otherwise dss exits unsuccessfully without
 +                      performing any action. Use this option to prevent snapshot creation
 +                      if the snapshot file system is not mounted.
 +
 +                      This option is silently ignored for subcommands which do not depend
 +                      on the destination directory.
 +              [/help]
        [option Rsync-options]
                summary = Controlling how rsync is run
                flag ignored
                typestr = percent
                default_val = 2
                [help]
 -                      This is like --min-free-mb but allows to specify the amount of
 -                      free disk space as a percentage. It is not recommended to set both
 +                      This is like --min-free-mb but the amount of free disk space
 +                      is specified as a percentage. It is not recommended to set both
                        --min-free-mb and --min-free-percent to zero as this will cause your
                        file system to fill up quickly.
                [/help]
  
                        Sending SIGHUP causes the running dss process to reload its config file.
                [/help]
+       [option wait]
+               short_opt = w
+               summary = wait until the signalled process has terminated
+               [help]
+                       This option is handy for system shutdown scripts which would like
+                       to terminate the dss daemon process.
+                       Without --wait the dss process which executes the kill subcommand
+                       exits right after the kill(2) system call returns. At this point the
+                       signalled process might still be alive (even if SIGKILL was sent).
+                       If --wait is given, the process waits until the signalled process
+                       has terminated or the timeout expires.
+                       If --wait is not given, the kill subcommand exits successfully if
+                       and only if the signal was sent (i.e., if there exists another dss
+                       process to receive the signal). With --wait it exits successfully
+                       if, additionally, the signalled process has terminated before the
+                       timeout expires.
+                       It makes only sense to use the option for signals which terminate dss.
+               [/help]
 +[subcommand configtest]
 +      purpose = run a configuration file syntax test
 +      [description]
 +              This command checks the command line options and the configuration
 +              file for syntactic correctness. It either reports "Syntax Ok" and
 +              exits successfully or prints information about the first syntax error
 +              detected and terminates with exit code 1.
 +      [/description]
  
  [section copyright]
        Written by Andre Noll
diff --combined err.h
--- 1/err.h
--- 2/err.h
+++ b/err.h
@@@ -48,13 -48,13 +48,14 @@@ static inline char *dss_strerror(int nu
        DSS_ERROR(INVALID_NUMBER, "invalid number"), \
        DSS_ERROR(STRFTIME, "strftime() failed"), \
        DSS_ERROR(LOCALTIME, "localtime() failed"), \
 +      DSS_ERROR(MOUNTPOINT, "destination directory is no mountpoint"), \
        DSS_ERROR(NULL_OPEN, "can not open /dev/null"), \
        DSS_ERROR(DUP_PIPE, "exec error: can not create pipe"), \
        DSS_ERROR(INVOLUNTARY_EXIT, "unexpected termination cause"), \
        DSS_ERROR(BAD_EXIT_CODE, "unexpected exit code"), \
        DSS_ERROR(SIGNAL_SIG_ERR, "signal() returned SIG_ERR"), \
        DSS_ERROR(SIGNAL, "caught terminating signal"), \
+       DSS_ERROR(KILL_TIMEOUT, "signal timeout expired"), \
        DSS_ERROR(BUG, "values of beta might cause dom!"), \
        DSS_ERROR(NOT_RUNNING, "dss not running"), \
        DSS_ERROR(ALREADY_RUNNING, "dss already running"), \