The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/contrib/openzfs/etc/systemd/system-generators/zfs-mount-generator.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 /*
    2  * Copyright (c) 2017 Antonio Russo <antonio.e.russo@gmail.com>
    3  * Copyright (c) 2020 InsanePrawn <insane.prawny@gmail.com>
    4  *
    5  * Permission is hereby granted, free of charge, to any person obtaining
    6  * a copy of this software and associated documentation files (the
    7  * "Software"), to deal in the Software without restriction, including
    8  * without limitation the rights to use, copy, modify, merge, publish,
    9  * distribute, sublicense, and/or sell copies of the Software, and to
   10  * permit persons to whom the Software is furnished to do so, subject to
   11  * the following conditions:
   12  *
   13  * The above copyright notice and this permission notice shall be
   14  * included in all copies or substantial portions of the Software.
   15  *
   16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
   20  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
   21  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   22  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   23  */
   24 
   25 
   26 #include <sys/resource.h>
   27 #include <sys/types.h>
   28 #include <sys/time.h>
   29 #include <sys/stat.h>
   30 #include <stdbool.h>
   31 #include <unistd.h>
   32 #include <fcntl.h>
   33 #include <stdio.h>
   34 #include <time.h>
   35 #include <regex.h>
   36 #include <search.h>
   37 #include <dirent.h>
   38 #include <string.h>
   39 #include <stdlib.h>
   40 #include <limits.h>
   41 #include <errno.h>
   42 #include <libzfs.h>
   43 
   44 /*
   45  * For debugging only.
   46  *
   47  * Free statics with trivial life-times,
   48  * but saved line filenames are replaced with a static string.
   49  */
   50 #define FREE_STATICS false
   51 
   52 #define nitems(arr) (sizeof (arr) / sizeof (*arr))
   53 #define STRCMP ((int(*)(const void *, const void *))&strcmp)
   54 
   55 
   56 #define PROGNAME "zfs-mount-generator"
   57 #define FSLIST SYSCONFDIR "/zfs/zfs-list.cache"
   58 #define ZFS SBINDIR "/zfs"
   59 
   60 #define OUTPUT_HEADER \
   61         "# Automatically generated by " PROGNAME "\n" \
   62         "\n"
   63 
   64 /*
   65  * Starts like the one in libzfs_util.c but also matches "//"
   66  * and captures until the end, since we actually use it for path extraxion
   67  */
   68 #define URI_REGEX_S "^\\([A-Za-z][A-Za-z0-9+.\\-]*\\):\\/\\/\\(.*\\)$"
   69 static regex_t uri_regex;
   70 
   71 static const char *destdir = "/tmp";
   72 static int destdir_fd = -1;
   73 
   74 static void *known_pools = NULL; /* tsearch() of C strings */
   75 static void *noauto_files = NULL; /* tsearch() of C strings */
   76 
   77 
   78 static char *
   79 systemd_escape(const char *input, const char *prepend, const char *append)
   80 {
   81         size_t len = strlen(input);
   82         size_t applen = strlen(append);
   83         size_t prelen = strlen(prepend);
   84         char *ret = malloc(4 * len + prelen + applen + 1);
   85         if (!ret) {
   86                 fprintf(stderr, PROGNAME "[%d]: "
   87                     "out of memory to escape \"%s%s%s\"!\n",
   88                     getpid(), prepend, input, append);
   89                 return (NULL);
   90         }
   91 
   92         memcpy(ret, prepend, prelen);
   93         char *out = ret + prelen;
   94 
   95         const char *cur = input;
   96         if (*cur == '.') {
   97                 memcpy(out, "\\x2e", 4);
   98                 out += 4;
   99                 ++cur;
  100         }
  101         for (; *cur; ++cur) {
  102                 if (*cur == '/')
  103                         *(out++) = '-';
  104                 else if (strchr(
  105                     "0123456789"
  106                     "abcdefghijklmnopqrstuvwxyz"
  107                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  108                     ":_.", *cur))
  109                         *(out++) = *cur;
  110                 else {
  111                         sprintf(out, "\\x%02x", (int)*cur);
  112                         out += 4;
  113                 }
  114         }
  115 
  116         memcpy(out, append, applen + 1);
  117         return (ret);
  118 }
  119 
  120 static void
  121 simplify_path(char *path)
  122 {
  123         char *out = path;
  124         for (char *cur = path; *cur; ++cur) {
  125                 if (*cur == '/') {
  126                         while (*(cur + 1) == '/')
  127                                 ++cur;
  128                         *(out++) = '/';
  129                 } else
  130                         *(out++) = *cur;
  131         }
  132 
  133         *(out++) = '\0';
  134 }
  135 
  136 static bool
  137 strendswith(const char *what, const char *suff)
  138 {
  139         size_t what_l = strlen(what);
  140         size_t suff_l = strlen(suff);
  141 
  142         return ((what_l >= suff_l) &&
  143             (strcmp(what + what_l - suff_l, suff) == 0));
  144 }
  145 
  146 /* Assumes already-simplified path, doesn't modify input */
  147 static char *
  148 systemd_escape_path(char *input, const char *prepend, const char *append)
  149 {
  150         if (strcmp(input, "/") == 0) {
  151                 char *ret;
  152                 if (asprintf(&ret, "%s-%s", prepend, append) == -1) {
  153                         fprintf(stderr, PROGNAME "[%d]: "
  154                             "out of memory to escape \"%s%s%s\"!\n",
  155                             getpid(), prepend, input, append);
  156                         ret = NULL;
  157                 }
  158                 return (ret);
  159         } else {
  160                 /*
  161                  * path_is_normalized() (flattened for absolute paths here),
  162                  * required for proper escaping
  163                  */
  164                 if (strstr(input, "/./") || strstr(input, "/../") ||
  165                     strendswith(input, "/.") || strendswith(input, "/.."))
  166                         return (NULL);
  167 
  168 
  169                 if (input[0] == '/')
  170                         ++input;
  171 
  172                 char *back = &input[strlen(input) - 1];
  173                 bool deslash = *back == '/';
  174                 if (deslash)
  175                         *back = '\0';
  176 
  177                 char *ret = systemd_escape(input, prepend, append);
  178 
  179                 if (deslash)
  180                         *back = '/';
  181                 return (ret);
  182         }
  183 }
  184 
  185 static FILE *
  186 fopenat(int dirfd, const char *pathname, int flags,
  187     const char *stream_mode, mode_t mode)
  188 {
  189         int fd = openat(dirfd, pathname, flags, mode);
  190         if (fd < 0)
  191                 return (NULL);
  192 
  193         return (fdopen(fd, stream_mode));
  194 }
  195 
  196 static int
  197 line_worker(char *line, const char *cachefile)
  198 {
  199         int ret = 0;
  200         void *tofree_all[8];
  201         void **tofree = tofree_all;
  202 
  203         char *toktmp;
  204         /* BEGIN CSTYLED */
  205         const char *dataset                     = strtok_r(line, "\t", &toktmp);
  206               char *p_mountpoint                = strtok_r(NULL, "\t", &toktmp);
  207         const char *p_canmount                  = strtok_r(NULL, "\t", &toktmp);
  208         const char *p_atime                     = strtok_r(NULL, "\t", &toktmp);
  209         const char *p_relatime                  = strtok_r(NULL, "\t", &toktmp);
  210         const char *p_devices                   = strtok_r(NULL, "\t", &toktmp);
  211         const char *p_exec                      = strtok_r(NULL, "\t", &toktmp);
  212         const char *p_readonly                  = strtok_r(NULL, "\t", &toktmp);
  213         const char *p_setuid                    = strtok_r(NULL, "\t", &toktmp);
  214         const char *p_nbmand                    = strtok_r(NULL, "\t", &toktmp);
  215         const char *p_encroot                   = strtok_r(NULL, "\t", &toktmp) ?: "-";
  216               char *p_keyloc                    = strtok_r(NULL, "\t", &toktmp) ?: strdupa("none");
  217         const char *p_systemd_requires          = strtok_r(NULL, "\t", &toktmp) ?: "-";
  218         const char *p_systemd_requiresmountsfor = strtok_r(NULL, "\t", &toktmp) ?: "-";
  219         const char *p_systemd_before            = strtok_r(NULL, "\t", &toktmp) ?: "-";
  220         const char *p_systemd_after             = strtok_r(NULL, "\t", &toktmp) ?: "-";
  221               char *p_systemd_wantedby          = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
  222               char *p_systemd_requiredby        = strtok_r(NULL, "\t", &toktmp) ?: strdupa("-");
  223         const char *p_systemd_nofail            = strtok_r(NULL, "\t", &toktmp) ?: "-";
  224         const char *p_systemd_ignore            = strtok_r(NULL, "\t", &toktmp) ?: "-";
  225         /* END CSTYLED */
  226 
  227         size_t pool_len = strlen(dataset);
  228         if ((toktmp = strchr(dataset, '/')) != NULL)
  229                 pool_len = toktmp - dataset;
  230         const char *pool = *(tofree++) = strndup(dataset, pool_len);
  231 
  232         if (p_nbmand == NULL) {
  233                 fprintf(stderr, PROGNAME "[%d]: %s: not enough tokens!\n",
  234                     getpid(), dataset);
  235                 goto err;
  236         }
  237 
  238         /* Minimal pre-requisites to mount a ZFS dataset */
  239         const char *after = "zfs-import.target";
  240         const char *wants = "zfs-import.target";
  241         const char *bindsto = NULL;
  242         char *wantedby = NULL;
  243         char *requiredby = NULL;
  244         bool noauto = false;
  245         bool wantedby_append = true;
  246 
  247         /*
  248          * zfs-import.target is not needed if the pool is already imported.
  249          * This avoids a dependency loop on root-on-ZFS systems:
  250          *   systemd-random-seed.service After (via RequiresMountsFor)
  251          *   var-lib.mount After
  252          *   zfs-import.target After
  253          *   zfs-import-{cache,scan}.service After
  254          *   cryptsetup.service After
  255          *   systemd-random-seed.service
  256          */
  257         if (tfind(pool, &known_pools, STRCMP)) {
  258                 after = "";
  259                 wants = "";
  260         }
  261 
  262         if (strcmp(p_systemd_after, "-") == 0)
  263                 p_systemd_after = NULL;
  264         if (strcmp(p_systemd_before, "-") == 0)
  265                 p_systemd_before = NULL;
  266         if (strcmp(p_systemd_requires, "-") == 0)
  267                 p_systemd_requires = NULL;
  268         if (strcmp(p_systemd_requiresmountsfor, "-") == 0)
  269                 p_systemd_requiresmountsfor = NULL;
  270 
  271 
  272         if (strcmp(p_encroot, "-") != 0) {
  273                 char *keyloadunit = *(tofree++) =
  274                     systemd_escape(p_encroot, "zfs-load-key@", ".service");
  275                 if (keyloadunit == NULL)
  276                         goto err;
  277 
  278                 if (strcmp(dataset, p_encroot) == 0) {
  279                         const char *keymountdep = NULL;
  280                         bool is_prompt = false;
  281                         bool need_network = false;
  282 
  283                         regmatch_t uri_matches[3];
  284                         if (regexec(&uri_regex, p_keyloc,
  285                             nitems(uri_matches), uri_matches, 0) == 0) {
  286                                 p_keyloc[uri_matches[1].rm_eo] = '\0';
  287                                 p_keyloc[uri_matches[2].rm_eo] = '\0';
  288                                 const char *scheme =
  289                                     &p_keyloc[uri_matches[1].rm_so];
  290                                 const char *path =
  291                                     &p_keyloc[uri_matches[2].rm_so];
  292 
  293                                 if (strcmp(scheme, "https") == 0 ||
  294                                     strcmp(scheme, "http") == 0)
  295                                         need_network = true;
  296                                 else
  297                                         keymountdep = path;
  298                         } else {
  299                                 if (strcmp(p_keyloc, "prompt") != 0)
  300                                         fprintf(stderr, PROGNAME "[%d]: %s: "
  301                                             "unknown non-URI keylocation=%s\n",
  302                                             getpid(), dataset, p_keyloc);
  303 
  304                                 is_prompt = true;
  305                         }
  306 
  307 
  308                         /* Generate the key-load .service unit */
  309                         FILE *keyloadunit_f = fopenat(destdir_fd, keyloadunit,
  310                             O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w",
  311                             0644);
  312                         if (!keyloadunit_f) {
  313                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  314                                     "couldn't open %s under %s: %s\n",
  315                                     getpid(), dataset, keyloadunit, destdir,
  316                                     strerror(errno));
  317                                 goto err;
  318                         }
  319 
  320                         fprintf(keyloadunit_f,
  321                             OUTPUT_HEADER
  322                             "[Unit]\n"
  323                             "Description=Load ZFS key for %s\n"
  324                             "SourcePath=" FSLIST "/%s\n"
  325                             "Documentation=man:zfs-mount-generator(8)\n"
  326                             "DefaultDependencies=no\n"
  327                             "Wants=%s\n"
  328                             "After=%s\n",
  329                             dataset, cachefile, wants, after);
  330 
  331                         if (need_network)
  332                                 fprintf(keyloadunit_f,
  333                                     "Wants=network-online.target\n"
  334                                     "After=network-online.target\n");
  335 
  336                         if (p_systemd_requires)
  337                                 fprintf(keyloadunit_f,
  338                                     "Requires=%s\n", p_systemd_requires);
  339 
  340                         if (p_systemd_requiresmountsfor)
  341                                 fprintf(keyloadunit_f,
  342                                     "RequiresMountsFor=%s\n",
  343                                     p_systemd_requiresmountsfor);
  344                         if (keymountdep)
  345                                 fprintf(keyloadunit_f,
  346                                     "RequiresMountsFor='%s'\n", keymountdep);
  347 
  348                         /* BEGIN CSTYLED */
  349                         fprintf(keyloadunit_f,
  350                             "\n"
  351                             "[Service]\n"
  352                             "Type=oneshot\n"
  353                             "RemainAfterExit=yes\n"
  354                             "# This avoids a dependency loop involving systemd-journald.socket if this\n"
  355                             "# dataset is a parent of the root filesystem.\n"
  356                             "StandardOutput=null\n"
  357                             "StandardError=null\n"
  358                             "ExecStart=/bin/sh -euc '"
  359                                 "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"unavailable\" ] || exit 0;",
  360                             dataset);
  361                         if (is_prompt)
  362                                 fprintf(keyloadunit_f,
  363                                     "for i in 1 2 3; do "
  364                                         "systemd-ask-password --id=\"zfs:%s\" \"Enter passphrase for %s:\" |"
  365                                         "" ZFS " load-key \"%s\" && exit 0;"
  366                                     "done;"
  367                                     "exit 1",
  368                                     dataset, dataset, dataset);
  369                         else
  370                                 fprintf(keyloadunit_f,
  371                                     "exec " ZFS " load-key \"%s\"",
  372                                     dataset);
  373 
  374                         fprintf(keyloadunit_f,
  375                                 "'\n"
  376                                 "ExecStop=/bin/sh -euc '"
  377                                     "[ \"$$(" ZFS " get -H -o value keystatus \"%s\")\" = \"available\" ] || exit 0;"
  378                                     "exec " ZFS " unload-key \"%s\""
  379                                 "'\n",
  380                                 dataset, dataset);
  381                         /* END CSTYLED */
  382 
  383                         (void) fclose(keyloadunit_f);
  384                 }
  385 
  386                 /* Update dependencies for the mount file to want this */
  387                 bindsto = keyloadunit;
  388                 if (after[0] == '\0')
  389                         after = keyloadunit;
  390                 else if (asprintf(&toktmp, "%s %s", after, keyloadunit) != -1)
  391                         after = *(tofree++) = toktmp;
  392                 else {
  393                         fprintf(stderr, PROGNAME "[%d]: %s: "
  394                             "out of memory to generate after=\"%s %s\"!\n",
  395                             getpid(), dataset, after, keyloadunit);
  396                         goto err;
  397                 }
  398         }
  399 
  400 
  401         /* Skip generation of the mount unit if org.openzfs.systemd:ignore=on */
  402         if (strcmp(p_systemd_ignore, "-") == 0 ||
  403             strcmp(p_systemd_ignore, "off") == 0) {
  404                 /* ok */
  405         } else if (strcmp(p_systemd_ignore, "on") == 0)
  406                 goto end;
  407         else {
  408                 fprintf(stderr, PROGNAME "[%d]: %s: "
  409                     "invalid org.openzfs.systemd:ignore=%s\n",
  410                     getpid(), dataset, p_systemd_ignore);
  411                 goto err;
  412         }
  413 
  414         /* Check for canmount */
  415         if (strcmp(p_canmount, "on") == 0) {
  416                 /* ok */
  417         } else if (strcmp(p_canmount, "noauto") == 0)
  418                 noauto = true;
  419         else if (strcmp(p_canmount, "off") == 0)
  420                 goto end;
  421         else {
  422                 fprintf(stderr, PROGNAME "[%d]: %s: invalid canmount=%s\n",
  423                     getpid(), dataset, p_canmount);
  424                 goto err;
  425         }
  426 
  427         /* Check for legacy and blank mountpoints */
  428         if (strcmp(p_mountpoint, "legacy") == 0 ||
  429             strcmp(p_mountpoint, "none") == 0)
  430                 goto end;
  431         else if (p_mountpoint[0] != '/') {
  432                 fprintf(stderr, PROGNAME "[%d]: %s: invalid mountpoint=%s\n",
  433                     getpid(), dataset, p_mountpoint);
  434                 goto err;
  435         }
  436 
  437         /* Escape the mountpoint per systemd policy */
  438         simplify_path(p_mountpoint);
  439         const char *mountfile = systemd_escape_path(p_mountpoint, "", ".mount");
  440         if (mountfile == NULL) {
  441                 fprintf(stderr,
  442                     PROGNAME "[%d]: %s: abnormal simplified mountpoint: %s\n",
  443                     getpid(), dataset, p_mountpoint);
  444                 goto err;
  445         }
  446 
  447 
  448         /*
  449          * Parse options, cf. lib/libzfs/libzfs_mount.c:zfs_add_options
  450          *
  451          * The longest string achievable here is
  452          * ",atime,strictatime,nodev,noexec,rw,nosuid,nomand".
  453          */
  454         char opts[64] = "";
  455 
  456         /* atime */
  457         if (strcmp(p_atime, "on") == 0) {
  458                 /* relatime */
  459                 if (strcmp(p_relatime, "on") == 0)
  460                         strcat(opts, ",atime,relatime");
  461                 else if (strcmp(p_relatime, "off") == 0)
  462                         strcat(opts, ",atime,strictatime");
  463                 else
  464                         fprintf(stderr,
  465                             PROGNAME "[%d]: %s: invalid relatime=%s\n",
  466                             getpid(), dataset, p_relatime);
  467         } else if (strcmp(p_atime, "off") == 0) {
  468                 strcat(opts, ",noatime");
  469         } else
  470                 fprintf(stderr, PROGNAME "[%d]: %s: invalid atime=%s\n",
  471                     getpid(), dataset, p_atime);
  472 
  473         /* devices */
  474         if (strcmp(p_devices, "on") == 0)
  475                 strcat(opts, ",dev");
  476         else if (strcmp(p_devices, "off") == 0)
  477                 strcat(opts, ",nodev");
  478         else
  479                 fprintf(stderr, PROGNAME "[%d]: %s: invalid devices=%s\n",
  480                     getpid(), dataset, p_devices);
  481 
  482         /* exec */
  483         if (strcmp(p_exec, "on") == 0)
  484                 strcat(opts, ",exec");
  485         else if (strcmp(p_exec, "off") == 0)
  486                 strcat(opts, ",noexec");
  487         else
  488                 fprintf(stderr, PROGNAME "[%d]: %s: invalid exec=%s\n",
  489                     getpid(), dataset, p_exec);
  490 
  491         /* readonly */
  492         if (strcmp(p_readonly, "on") == 0)
  493                 strcat(opts, ",ro");
  494         else if (strcmp(p_readonly, "off") == 0)
  495                 strcat(opts, ",rw");
  496         else
  497                 fprintf(stderr, PROGNAME "[%d]: %s: invalid readonly=%s\n",
  498                     getpid(), dataset, p_readonly);
  499 
  500         /* setuid */
  501         if (strcmp(p_setuid, "on") == 0)
  502                 strcat(opts, ",suid");
  503         else if (strcmp(p_setuid, "off") == 0)
  504                 strcat(opts, ",nosuid");
  505         else
  506                 fprintf(stderr, PROGNAME "[%d]: %s: invalid setuid=%s\n",
  507                     getpid(), dataset, p_setuid);
  508 
  509         /* nbmand */
  510         if (strcmp(p_nbmand, "on") == 0)
  511                 strcat(opts, ",mand");
  512         else if (strcmp(p_nbmand, "off") == 0)
  513                 strcat(opts, ",nomand");
  514         else
  515                 fprintf(stderr, PROGNAME "[%d]: %s: invalid nbmand=%s\n",
  516                     getpid(), dataset, p_setuid);
  517 
  518         if (strcmp(p_systemd_wantedby, "-") != 0) {
  519                 noauto = true;
  520 
  521                 if (strcmp(p_systemd_wantedby, "none") != 0)
  522                         wantedby = p_systemd_wantedby;
  523         }
  524 
  525         if (strcmp(p_systemd_requiredby, "-") != 0) {
  526                 noauto = true;
  527 
  528                 if (strcmp(p_systemd_requiredby, "none") != 0)
  529                         requiredby = p_systemd_requiredby;
  530         }
  531 
  532         /*
  533          * For datasets with canmount=on, a dependency is created for
  534          * local-fs.target by default. To avoid regressions, this dependency
  535          * is reduced to "wants" rather than "requires" when nofail!=off.
  536          * **THIS MAY CHANGE**
  537          * noauto=on disables this behavior completely.
  538          */
  539         if (!noauto) {
  540                 if (strcmp(p_systemd_nofail, "off") == 0)
  541                         requiredby = strdupa("local-fs.target");
  542                 else {
  543                         wantedby = strdupa("local-fs.target");
  544                         wantedby_append = strcmp(p_systemd_nofail, "on") != 0;
  545                 }
  546         }
  547 
  548         /*
  549          * Handle existing files:
  550          * 1.   We never overwrite existing files, although we may delete
  551          *      files if we're sure they were created by us. (see 5.)
  552          * 2.   We handle files differently based on canmount.
  553          *      Units with canmount=on always have precedence over noauto.
  554          *      This is enforced by processing these units before all others.
  555          *      It is important to use p_canmount and not noauto here,
  556          *      since we categorise by canmount while other properties,
  557          *      e.g. org.openzfs.systemd:wanted-by, also modify noauto.
  558          * 3.   If no unit file exists for a noauto dataset, we create one.
  559          *      Additionally, we use noauto_files to track the unit file names
  560          *      (which are the systemd-escaped mountpoints) of all (exclusively)
  561          *      noauto datasets that had a file created.
  562          * 4.   If the file to be created is found in the tracking tree,
  563          *      we do NOT create it.
  564          * 5.   If a file exists for a noauto dataset,
  565          *      we check whether the file name is in the array.
  566          *      If it is, we have multiple noauto datasets for the same
  567          *      mountpoint. In such cases, we remove the file for safety.
  568          *      We leave the file name in the tracking array to avoid
  569          *      further noauto datasets creating a file for this path again.
  570          */
  571 
  572         struct stat stbuf;
  573         bool already_exists = fstatat(destdir_fd, mountfile, &stbuf, 0) == 0;
  574         bool is_known = tfind(mountfile, &noauto_files, STRCMP) != NULL;
  575 
  576         *(tofree++) = (void *)mountfile;
  577         if (already_exists) {
  578                 if (is_known) {
  579                         /* If it's in noauto_files, we must be noauto too */
  580 
  581                         /* See 5 */
  582                         errno = 0;
  583                         (void) unlinkat(destdir_fd, mountfile, 0);
  584 
  585                         /* See 2 */
  586                         fprintf(stderr, PROGNAME "[%d]: %s: "
  587                             "removing duplicate noauto unit %s%s%s\n",
  588                             getpid(), dataset, mountfile,
  589                             errno ? "" : " failed: ",
  590                             errno ? "" : strerror(errno));
  591                 } else {
  592                         /* Don't log for canmount=noauto */
  593                         if (strcmp(p_canmount, "on") == 0)
  594                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  595                                     "%s already exists. Skipping.\n",
  596                                     getpid(), dataset, mountfile);
  597                 }
  598 
  599                 /* File exists: skip current dataset */
  600                 goto end;
  601         } else {
  602                 if (is_known) {
  603                         /* See 4 */
  604                         goto end;
  605                 } else if (strcmp(p_canmount, "noauto") == 0) {
  606                         if (tsearch(mountfile, &noauto_files, STRCMP) == NULL)
  607                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  608                                     "out of memory for noauto datasets! "
  609                                     "Not tracking %s.\n",
  610                                     getpid(), dataset, mountfile);
  611                         else
  612                                 /* mountfile escaped to noauto_files */
  613                                 *(--tofree) = NULL;
  614                 }
  615         }
  616 
  617 
  618         FILE *mountfile_f = fopenat(destdir_fd, mountfile,
  619             O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, "w", 0644);
  620         if (!mountfile_f) {
  621                 fprintf(stderr,
  622                     PROGNAME "[%d]: %s: couldn't open %s under %s: %s\n",
  623                     getpid(), dataset, mountfile, destdir, strerror(errno));
  624                 goto err;
  625         }
  626 
  627         fprintf(mountfile_f,
  628             OUTPUT_HEADER
  629             "[Unit]\n"
  630             "SourcePath=" FSLIST "/%s\n"
  631             "Documentation=man:zfs-mount-generator(8)\n"
  632             "\n"
  633             "Before=",
  634             cachefile);
  635 
  636         if (p_systemd_before)
  637                 fprintf(mountfile_f, "%s ", p_systemd_before);
  638         fprintf(mountfile_f, "zfs-mount.service"); /* Ensures we don't race */
  639         if (requiredby)
  640                 fprintf(mountfile_f, " %s", requiredby);
  641         if (wantedby && wantedby_append)
  642                 fprintf(mountfile_f, " %s", wantedby);
  643 
  644         fprintf(mountfile_f,
  645             "\n"
  646             "After=");
  647         if (p_systemd_after)
  648                 fprintf(mountfile_f, "%s ", p_systemd_after);
  649         fprintf(mountfile_f, "%s\n", after);
  650 
  651         fprintf(mountfile_f, "Wants=%s\n", wants);
  652 
  653         if (bindsto)
  654                 fprintf(mountfile_f, "BindsTo=%s\n", bindsto);
  655         if (p_systemd_requires)
  656                 fprintf(mountfile_f, "Requires=%s\n", p_systemd_requires);
  657         if (p_systemd_requiresmountsfor)
  658                 fprintf(mountfile_f,
  659                     "RequiresMountsFor=%s\n", p_systemd_requiresmountsfor);
  660 
  661         fprintf(mountfile_f,
  662             "\n"
  663             "[Mount]\n"
  664             "Where=%s\n"
  665             "What=%s\n"
  666             "Type=zfs\n"
  667             "Options=defaults%s,zfsutil\n",
  668             p_mountpoint, dataset, opts);
  669 
  670         (void) fclose(mountfile_f);
  671 
  672         if (!requiredby && !wantedby)
  673                 goto end;
  674 
  675         /* Finally, create the appropriate dependencies */
  676         char *linktgt;
  677         if (asprintf(&linktgt, "../%s", mountfile) == -1) {
  678                 fprintf(stderr, PROGNAME "[%d]: %s: "
  679                     "out of memory for dependents of %s!\n",
  680                     getpid(), dataset, mountfile);
  681                 goto err;
  682         }
  683         *(tofree++) = linktgt;
  684 
  685         struct dep {
  686                 const char *type;
  687                 char *list;
  688         } deps[] = {
  689                 {"wants", wantedby},
  690                 {"requires", requiredby},
  691                 {}
  692         };
  693         for (struct dep *dep = deps; dep->type; ++dep) {
  694                 if (!dep->list)
  695                         continue;
  696 
  697                 for (char *reqby = strtok_r(dep->list, " ", &toktmp);
  698                     reqby;
  699                     reqby = strtok_r(NULL, " ", &toktmp)) {
  700                         char *depdir;
  701                         if (asprintf(
  702                             &depdir, "%s.%s", reqby, dep->type) == -1) {
  703                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  704                                     "out of memory for dependent dir name "
  705                                     "\"%s.%s\"!\n",
  706                                     getpid(), dataset, reqby, dep->type);
  707                                 continue;
  708                         }
  709 
  710                         (void) mkdirat(destdir_fd, depdir, 0755);
  711                         int depdir_fd = openat(destdir_fd, depdir,
  712                             O_PATH | O_DIRECTORY | O_CLOEXEC);
  713                         if (depdir_fd < 0) {
  714                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  715                                     "couldn't open %s under %s: %s\n",
  716                                     getpid(), dataset, depdir, destdir,
  717                                     strerror(errno));
  718                                 free(depdir);
  719                                 continue;
  720                         }
  721 
  722                         if (symlinkat(linktgt, depdir_fd, mountfile) == -1)
  723                                 fprintf(stderr, PROGNAME "[%d]: %s: "
  724                                     "couldn't symlink at "
  725                                     "%s under %s under %s: %s\n",
  726                                     getpid(), dataset, mountfile,
  727                                     depdir, destdir, strerror(errno));
  728 
  729                         (void) close(depdir_fd);
  730                         free(depdir);
  731                 }
  732         }
  733 
  734 end:
  735         if (tofree >= tofree_all + nitems(tofree_all)) {
  736                 /*
  737                  * This won't happen as-is:
  738                  * we've got 8 slots and allocate 5 things at most.
  739                  */
  740                 fprintf(stderr,
  741                     PROGNAME "[%d]: %s: need to free %zu > %zu!\n",
  742                     getpid(), dataset, tofree - tofree_all, nitems(tofree_all));
  743                 ret = tofree - tofree_all;
  744         }
  745 
  746         while (tofree-- != tofree_all)
  747                 free(*tofree);
  748         return (ret);
  749 err:
  750         ret = 1;
  751         goto end;
  752 }
  753 
  754 
  755 static int
  756 pool_enumerator(zpool_handle_t *pool, void *data __attribute__((unused)))
  757 {
  758         int ret = 0;
  759 
  760         /*
  761          * Pools are guaranteed-unique by the kernel,
  762          * no risk of leaking dupes here
  763          */
  764         char *name = strdup(zpool_get_name(pool));
  765         if (!name || !tsearch(name, &known_pools, STRCMP)) {
  766                 free(name);
  767                 ret = ENOMEM;
  768         }
  769 
  770         zpool_close(pool);
  771         return (ret);
  772 }
  773 
  774 int
  775 main(int argc, char **argv)
  776 {
  777         struct timespec time_init = {};
  778         clock_gettime(CLOCK_MONOTONIC_RAW, &time_init);
  779 
  780         {
  781                 int kmfd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);
  782                 if (kmfd >= 0) {
  783                         (void) dup2(kmfd, STDERR_FILENO);
  784                         (void) close(kmfd);
  785 
  786                         setlinebuf(stderr);
  787                 }
  788         }
  789 
  790         switch (argc) {
  791         case 1:
  792                 /* Use default */
  793                 break;
  794         case 2:
  795         case 4:
  796                 destdir = argv[1];
  797                 break;
  798         default:
  799                 fprintf(stderr,
  800                     PROGNAME "[%d]: wrong argument count: %d\n",
  801                     getpid(), argc - 1);
  802                 _exit(1);
  803         }
  804 
  805         {
  806                 destdir_fd = open(destdir, O_PATH | O_DIRECTORY | O_CLOEXEC);
  807                 if (destdir_fd < 0) {
  808                         fprintf(stderr, PROGNAME "[%d]: "
  809                             "can't open destination directory %s: %s\n",
  810                             getpid(), destdir, strerror(errno));
  811                         _exit(1);
  812                 }
  813         }
  814 
  815         DIR *fslist_dir = opendir(FSLIST);
  816         if (!fslist_dir) {
  817                 if (errno != ENOENT)
  818                         fprintf(stderr,
  819                             PROGNAME "[%d]: couldn't open " FSLIST ": %s\n",
  820                             getpid(), strerror(errno));
  821                 _exit(0);
  822         }
  823 
  824         {
  825                 libzfs_handle_t *libzfs = libzfs_init();
  826                 if (libzfs) {
  827                         if (zpool_iter(libzfs, pool_enumerator, NULL) != 0)
  828                                 fprintf(stderr, PROGNAME "[%d]: "
  829                                     "error listing pools, ignoring\n",
  830                                     getpid());
  831                         libzfs_fini(libzfs);
  832                 } else
  833                         fprintf(stderr, PROGNAME "[%d]: "
  834                             "couldn't start libzfs, ignoring\n",
  835                             getpid());
  836         }
  837 
  838         {
  839                 int regerr = regcomp(&uri_regex, URI_REGEX_S, 0);
  840                 if (regerr != 0) {
  841                         fprintf(stderr,
  842                             PROGNAME "[%d]: invalid regex: %d\n",
  843                             getpid(), regerr);
  844                         _exit(1);
  845                 }
  846         }
  847 
  848         bool debug = false;
  849         char *line = NULL;
  850         size_t linelen = 0;
  851         {
  852                 const char *dbgenv = getenv("ZFS_DEBUG");
  853                 if (dbgenv)
  854                         debug = atoi(dbgenv);
  855                 else {
  856                         FILE *cmdline = fopen("/proc/cmdline", "re");
  857                         if (cmdline != NULL) {
  858                                 if (getline(&line, &linelen, cmdline) >= 0)
  859                                         debug = strstr(line, "debug");
  860                                 (void) fclose(cmdline);
  861                         }
  862                 }
  863 
  864                 if (debug && !isatty(STDOUT_FILENO))
  865                         dup2(STDERR_FILENO, STDOUT_FILENO);
  866         }
  867 
  868         struct timespec time_start = {};
  869         if (debug)
  870                 clock_gettime(CLOCK_MONOTONIC_RAW, &time_start);
  871 
  872         struct line {
  873                 char *line;
  874                 const char *fname;
  875                 struct line *next;
  876         } *lines_canmount_not_on = NULL;
  877 
  878         int ret = 0;
  879         struct dirent *cachent;
  880         while ((cachent = readdir(fslist_dir)) != NULL) {
  881                 if (strcmp(cachent->d_name, ".") == 0 ||
  882                     strcmp(cachent->d_name, "..") == 0)
  883                         continue;
  884 
  885                 FILE *cachefile = fopenat(dirfd(fslist_dir), cachent->d_name,
  886                     O_RDONLY | O_CLOEXEC, "r", 0);
  887                 if (!cachefile) {
  888                         fprintf(stderr, PROGNAME "[%d]: "
  889                             "couldn't open %s under " FSLIST ": %s\n",
  890                             getpid(), cachent->d_name, strerror(errno));
  891                         continue;
  892                 }
  893 
  894                 const char *filename = FREE_STATICS ? "(elided)" : NULL;
  895 
  896                 ssize_t read;
  897                 while ((read = getline(&line, &linelen, cachefile)) >= 0) {
  898                         line[read - 1] = '\0'; /* newline */
  899 
  900                         char *canmount = line;
  901                         canmount += strcspn(canmount, "\t");
  902                         canmount += strspn(canmount, "\t");
  903                         canmount += strcspn(canmount, "\t");
  904                         canmount += strspn(canmount, "\t");
  905                         bool canmount_on = strncmp(canmount, "on", 2) == 0;
  906 
  907                         if (canmount_on)
  908                                 ret |= line_worker(line, cachent->d_name);
  909                         else {
  910                                 if (filename == NULL)
  911                                         filename =
  912                                             strdup(cachent->d_name) ?: "(?)";
  913 
  914                                 struct line *l = calloc(1, sizeof (*l));
  915                                 char *nl = strdup(line);
  916                                 if (l == NULL || nl == NULL) {
  917                                         fprintf(stderr, PROGNAME "[%d]: "
  918                                             "out of memory for \"%s\" in %s\n",
  919                                             getpid(), line, cachent->d_name);
  920                                         free(l);
  921                                         free(nl);
  922                                         continue;
  923                                 }
  924                                 l->line = nl;
  925                                 l->fname = filename;
  926                                 l->next = lines_canmount_not_on;
  927                                 lines_canmount_not_on = l;
  928                         }
  929                 }
  930 
  931                 fclose(cachefile);
  932         }
  933         free(line);
  934 
  935         while (lines_canmount_not_on) {
  936                 struct line *l = lines_canmount_not_on;
  937                 lines_canmount_not_on = l->next;
  938 
  939                 ret |= line_worker(l->line, l->fname);
  940                 if (FREE_STATICS) {
  941                         free(l->line);
  942                         free(l);
  943                 }
  944         }
  945 
  946         if (debug) {
  947                 struct timespec time_end = {};
  948                 clock_gettime(CLOCK_MONOTONIC_RAW, &time_end);
  949 
  950                 struct rusage usage;
  951                 getrusage(RUSAGE_SELF, &usage);
  952                 printf(
  953                     "\n"
  954                     PROGNAME ": "
  955                     "user=%llu.%06us, system=%llu.%06us, maxrss=%ldB\n",
  956                     (unsigned long long) usage.ru_utime.tv_sec,
  957                     (unsigned int) usage.ru_utime.tv_usec,
  958                     (unsigned long long) usage.ru_stime.tv_sec,
  959                     (unsigned int) usage.ru_stime.tv_usec,
  960                     usage.ru_maxrss * 1024);
  961 
  962                 if (time_start.tv_nsec > time_end.tv_nsec) {
  963                         time_end.tv_nsec =
  964                             1000000000 + time_end.tv_nsec - time_start.tv_nsec;
  965                         time_end.tv_sec -= 1;
  966                 } else
  967                         time_end.tv_nsec -= time_start.tv_nsec;
  968                 time_end.tv_sec -= time_start.tv_sec;
  969 
  970                 if (time_init.tv_nsec > time_start.tv_nsec) {
  971                         time_start.tv_nsec =
  972                             1000000000 + time_start.tv_nsec - time_init.tv_nsec;
  973                         time_start.tv_sec -= 1;
  974                 } else
  975                         time_start.tv_nsec -= time_init.tv_nsec;
  976                 time_start.tv_sec -= time_init.tv_sec;
  977 
  978                 time_init.tv_nsec = time_start.tv_nsec + time_end.tv_nsec;
  979                 time_init.tv_sec =
  980                     time_start.tv_sec + time_end.tv_sec +
  981                     time_init.tv_nsec / 1000000000;
  982                 time_init.tv_nsec %= 1000000000;
  983 
  984                 printf(PROGNAME ": "
  985                     "total=%llu.%09llus = "
  986                     "init=%llu.%09llus + real=%llu.%09llus\n",
  987                     (unsigned long long) time_init.tv_sec,
  988                     (unsigned long long) time_init.tv_nsec,
  989                     (unsigned long long) time_start.tv_sec,
  990                     (unsigned long long) time_start.tv_nsec,
  991                     (unsigned long long) time_end.tv_sec,
  992                     (unsigned long long) time_end.tv_nsec);
  993 
  994                 fflush(stdout);
  995         }
  996 
  997         if (FREE_STATICS) {
  998                 closedir(fslist_dir);
  999                 tdestroy(noauto_files, free);
 1000                 tdestroy(known_pools, free);
 1001                 regfree(&uri_regex);
 1002         }
 1003         _exit(ret);
 1004 }

Cache object: c7340d3de1ce4ed792965c2f4fa46b45


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.