"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.11/dotlock.c" (8 Aug 2018, 14487 Bytes) of package /linux/misc/s-nail-14.9.11.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dotlock.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.10_vs_14.9.11.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ n_dotlock(): creation of an exclusive "dotlock" file.
    3  *
    4  * Copyright (c) 2016 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    5  * SPDX-License-Identifier: ISC
    6  *
    7  * Permission to use, copy, modify, and/or distribute this software for any
    8  * purpose with or without fee is hereby granted, provided that the above
    9  * copyright notice and this permission notice appear in all copies.
   10  *
   11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   18  */
   19 #undef n_FILE
   20 #define n_FILE dotlock
   21 
   22 #ifndef HAVE_AMALGAMATION
   23 # include "nail.h"
   24 #endif
   25 
   26 #ifdef HAVE_DOTLOCK
   27 # include "dotlock.h"
   28 #endif
   29 
   30 /* XXX Our Popen() main() takes void, temporary global data store */
   31 #ifdef HAVE_DOTLOCK
   32 static enum n_file_lock_type a_dotlock_flt;
   33 static int a_dotlock_fd;
   34 struct n_dotlock_info *a_dotlock_dip;
   35 #endif
   36 
   37 /* main() of fork(2)ed dot file locker */
   38 #ifdef HAVE_DOTLOCK
   39 static int a_dotlock_main(void);
   40 #endif
   41 
   42 #ifdef HAVE_DOTLOCK
   43 static int
   44 a_dotlock_main(void){
   45    /* Use PATH_MAX not NAME_MAX to catch those "we proclaim the minimum value"
   46     * problems (SunOS), since the pathconf(3) value comes too late! */
   47    char name[PATH_MAX +1];
   48    struct n_dotlock_info di;
   49    struct stat stb, fdstb;
   50    enum n_dotlock_state dls;
   51    char const *cp;
   52    int fd;
   53    enum n_file_lock_type flt;
   54    NYD_ENTER;
   55 
   56    /* Ignore SIGPIPE, we will see n_ERR_PIPE and "fall through" */
   57    safe_signal(SIGPIPE, SIG_IGN);
   58 
   59    /* Get the arguments "passed to us" */
   60    flt = a_dotlock_flt;
   61    fd = a_dotlock_fd;
   62    n_UNUSED(fd);
   63    di = *a_dotlock_dip;
   64 
   65    /* chdir(2)? */
   66 jislink:
   67    dls = n_DLS_CANT_CHDIR | n_DLS_ABANDON;
   68 
   69    if((cp = strrchr(di.di_file_name, '/')) != NULL){
   70       char const *fname = cp + 1;
   71 
   72       while(PTRCMP(cp - 1, >, di.di_file_name) && cp[-1] == '/')
   73          --cp;
   74       cp = savestrbuf(di.di_file_name, PTR2SIZE(cp - di.di_file_name));
   75       if(chdir(cp))
   76          goto jmsg;
   77 
   78       di.di_file_name = fname;
   79    }
   80 
   81    /* So we are here, but then again the file can be a symbolic link!
   82     * This is however only true if we do not have realpath(3) available since
   83     * that will have resolved the path already otherwise; nonetheless, let
   84     * readlink(2) be a precondition for dotlocking and keep this code */
   85    if(lstat(cp = di.di_file_name, &stb) == -1)
   86       goto jmsg;
   87    if(S_ISLNK(stb.st_mode)){
   88       /* Use n_autorec_alloc() and hope we stay in built-in buffer.. */
   89       char *x;
   90       size_t i;
   91       ssize_t sr;
   92 
   93       for(x = NULL, i = PATH_MAX;; i += PATH_MAX){
   94          x = n_autorec_alloc(i +1);
   95          sr = readlink(cp, x, i);
   96          if(sr <= 0){
   97             dls = n_DLS_FISHY | n_DLS_ABANDON;
   98             goto jmsg;
   99          }
  100          if(UICMP(z, sr, <, i)){
  101             x[sr] = '\0';
  102             break;
  103          }
  104       }
  105       di.di_file_name = x;
  106       goto jislink;
  107    }
  108 
  109    dls = n_DLS_FISHY | n_DLS_ABANDON;
  110 
  111    /* Bail out if the file has changed its identity in the meanwhile */
  112    if(fstat(fd, &fdstb) == -1 ||
  113          fdstb.st_dev != stb.st_dev || fdstb.st_ino != stb.st_ino ||
  114          fdstb.st_uid != stb.st_uid || fdstb.st_gid != stb.st_gid ||
  115          fdstb.st_mode != stb.st_mode)
  116       goto jmsg;
  117 
  118    /* Be aware, even if the error is false!  Note the shared code in dotlock.h
  119     * *requires* that it is possible to create a filename at least one byte
  120     * longer than di_lock_name! */
  121    do/* while(0) breaker */{
  122 # ifdef HAVE_PATHCONF
  123       long pc;
  124 # endif
  125       int i;
  126 
  127       i = snprintf(name, sizeof name, "%s.lock", di.di_file_name);
  128       if(i < 0 || UICMP(32, i, >=, sizeof name)){
  129 jenametool:
  130          dls = n_DLS_NAMETOOLONG | n_DLS_ABANDON;
  131          goto jmsg;
  132       }
  133 
  134       /* fd is a file, not portable to use for _PC_NAME_MAX */
  135 # ifdef HAVE_PATHCONF
  136       n_err_no = n_ERR_NONE;
  137       if((pc = pathconf(".", _PC_NAME_MAX)) == -1){
  138          /* n_err_no unchanged: no limit */
  139          if(n_err_no == 0)
  140             break;
  141 # endif
  142          if(UICMP(z, NAME_MAX - 1, <, i))
  143             goto jenametool;
  144 # ifdef HAVE_PATHCONF
  145       }else if(pc - 1 >= i)
  146          break;
  147       else
  148          goto jenametool;
  149 # endif
  150    }while(0);
  151 
  152    di.di_lock_name = name;
  153 
  154    /* We are in the directory of the mailbox for which we have to create
  155     * a dotlock file for.  Any symbolic links have been resolved.
  156     * We do not know whether we have realpath(3) available,and manually
  157     * resolving the path is due especially given that S-nail supports the
  158     * special "%:" syntax to warp any file into a "system mailbox"; there may
  159     * also be multiple system mailbox directories...
  160     * So what we do is that we fstat(2) the mailbox and check its UID and
  161     * GID against that of our own process: if any of those mismatch we must
  162     * either assume a directory we are not allowed to write in, or that we run
  163     * via -u/$USER/%USER as someone else, in which case we favour our
  164     * privilege-separated dotlock process */
  165    assert(cp != NULL); /* Ugly: avoid a useless var and reuse that one */
  166    if(access(".", W_OK)){
  167       /* This may however also indicate a read-only filesystem, which is not
  168        * really an error from our point of view since the mailbox will degrade
  169        * to a readonly one for which no dotlock is needed, then, and errors
  170        * may arise only due to actions which require box modifications */
  171       if(n_err_no == n_ERR_ROFS){
  172          dls = n_DLS_ROFS | n_DLS_ABANDON;
  173          goto jmsg;
  174       }
  175       cp = NULL;
  176    }
  177    if(cp == NULL || stb.st_uid != n_user_id || stb.st_gid != n_group_id){
  178       char itoabuf[64];
  179       char const *args[13];
  180 
  181       snprintf(itoabuf, sizeof itoabuf, "%" PRIuZ, di.di_pollmsecs);
  182       args[ 0] = VAL_PRIVSEP;
  183       args[ 1] = (flt == FLT_READ ? "rdotlock" : "wdotlock");
  184       args[ 2] = "mailbox";   args[ 3] = di.di_file_name;
  185       args[ 4] = "name";      args[ 5] = di.di_lock_name;
  186       args[ 6] = "hostname";  args[ 7] = di.di_hostname;
  187       args[ 8] = "randstr";   args[ 9] = di.di_randstr;
  188       args[10] = "pollmsecs"; args[11] = itoabuf;
  189       args[12] = NULL;
  190       execv(VAL_LIBEXECDIR "/" VAL_UAGENT "-privsep", n_UNCONST(args));
  191 
  192       dls = n_DLS_NOEXEC;
  193       write(STDOUT_FILENO, &dls, sizeof dls);
  194       /* But fall through and try it with normal privileges! */
  195    }
  196 
  197    /* So let's try and call it ourselfs!  Note we do not block signals just
  198     * like our privsep child does, the user will anyway be able to remove his
  199     * file again, and if we are in -u/$USER mode then we are allowed to access
  200     * the user's box: shall we leave behind a stale dotlock then at least we
  201     * start a friendly human conversation.  Since we cannot handle SIGKILL and
  202     * SIGSTOP malicious things could happen whatever we do */
  203    safe_signal(SIGHUP, SIG_IGN);
  204    safe_signal(SIGINT, SIG_IGN);
  205    safe_signal(SIGQUIT, SIG_IGN);
  206    safe_signal(SIGTERM, SIG_IGN);
  207 
  208    NYD;
  209    dls = a_dotlock_create(&di);
  210    NYD;
  211 
  212    /* Finally: notify our parent about the actual lock state.. */
  213 jmsg:
  214    write(STDOUT_FILENO, &dls, sizeof dls);
  215    close(STDOUT_FILENO);
  216 
  217    /* ..then eventually wait until we shall remove the lock again, which will
  218     * be notified via the read returning */
  219    if(dls == n_DLS_NONE){
  220       read(STDIN_FILENO, &dls, sizeof dls);
  221 
  222       unlink(name);
  223    }
  224    NYD_LEAVE;
  225    return n_EXIT_OK;
  226 }
  227 #endif /* HAVE_DOTLOCK */
  228 
  229 FL FILE *
  230 n_dotlock(char const *fname, int fd, enum n_file_lock_type flt,
  231       off_t off, off_t len, size_t pollmsecs){
  232 #undef _DOMSG
  233 #define _DOMSG() \
  234    n_err(_("Creating file lock for %s "), n_shexp_quote_cp(fname, FAL0))
  235 
  236 #ifdef HAVE_DOTLOCK
  237    int cpipe[2];
  238    struct n_dotlock_info di;
  239    enum n_dotlock_state dls;
  240    char const *emsg;
  241 #endif
  242    int serr;
  243    union {size_t tries; int (*ptf)(void); char const *sh; ssize_t r;} u;
  244    bool_t flocked, didmsg;
  245    FILE *rv;
  246    NYD_ENTER;
  247 
  248    if(pollmsecs == UIZ_MAX)
  249       pollmsecs = FILE_LOCK_MILLIS;
  250 
  251    rv = NULL;
  252    didmsg = FAL0;
  253    n_UNINIT(serr, 0);
  254 #ifdef HAVE_DOTLOCK
  255    emsg = NULL;
  256 #endif
  257 
  258    if(n_poption & n_PO_D_VV){
  259       _DOMSG();
  260       didmsg = TRUM1;
  261    }
  262 
  263    flocked = FAL0;
  264    for(u.tries = 0; !n_file_lock(fd, flt, off, len, 0);)
  265       switch((serr = n_err_no)){
  266       case n_ERR_ACCES:
  267       case n_ERR_AGAIN:
  268       case n_ERR_NOLCK:
  269          if(pollmsecs > 0 && ++u.tries < FILE_LOCK_TRIES){
  270             if(!didmsg)
  271                _DOMSG();
  272             n_err(".");
  273             didmsg = TRUM1;
  274             n_msleep(pollmsecs, FAL0);
  275             continue;
  276          }
  277          /* FALLTHRU */
  278       default:
  279          goto jleave;
  280       }
  281    flocked = TRU1;
  282 
  283 #ifndef HAVE_DOTLOCK
  284 jleave:
  285    if(didmsg == TRUM1)
  286       n_err("\n");
  287    if(flocked)
  288       rv = (FILE*)-1;
  289    else
  290       n_err_no = serr;
  291    NYD_LEAVE;
  292    return rv;
  293 
  294 #else
  295    if(ok_blook(dotlock_disable)){
  296       rv = (FILE*)-1;
  297       goto jleave;
  298    }
  299 
  300    /* Create control-pipe for our dot file locker process, which will remove
  301     * the lock and terminate once the pipe is closed, for whatever reason */
  302    if(pipe_cloexec(cpipe) == -1){
  303       serr = n_err_no;
  304       emsg = N_("  Cannot create dotlock file control pipe\n");
  305       goto jemsg;
  306    }
  307 
  308    /* And the locker process itself; it will be a (rather cheap) thread only
  309     * unless the lock has to be placed in the system spool and we have our
  310     * privilege-separated dotlock program available, in which case that will be
  311     * executed and do "it" */
  312    di.di_file_name = fname;
  313    di.di_pollmsecs = pollmsecs;
  314    /* Initialize some more stuff; query the two strings in the parent in order
  315     * to cache the result of the former and anyway minimalize child page-ins.
  316     * Especially uname(3) may hang for multiple seconds when it is called the
  317     * first time! */
  318    di.di_hostname = n_nodename(FAL0);
  319    di.di_randstr = n_random_create_cp(16, NULL);
  320    a_dotlock_flt = flt;
  321    a_dotlock_fd = fd;
  322    a_dotlock_dip = &di;
  323 
  324    u.ptf = &a_dotlock_main;
  325    rv = Popen((char*)-1, "W", u.sh, NULL, cpipe[1]);
  326    serr = n_err_no;
  327 
  328    close(cpipe[1]);
  329    if(rv == NULL){
  330       close(cpipe[0]);
  331       emsg = N_("  Cannot create file lock process\n");
  332       goto jemsg;
  333    }
  334 
  335    /* Let's check whether we were able to create the dotlock file */
  336    for(;;){
  337       u.r = read(cpipe[0], &dls, sizeof dls);
  338       if(UICMP(z, u.r, !=, sizeof dls)){
  339          serr = (u.r != -1) ? n_ERR_AGAIN : n_err_no;
  340          dls = n_DLS_DUNNO | n_DLS_ABANDON;
  341       }else
  342          serr = n_ERR_NONE;
  343 
  344       if(dls == n_DLS_NONE || (dls & n_DLS_ABANDON))
  345          close(cpipe[0]);
  346 
  347       switch(dls & ~n_DLS_ABANDON){
  348       case n_DLS_NONE:
  349          goto jleave;
  350       case n_DLS_CANT_CHDIR:
  351          if(n_poption & n_PO_D_V)
  352             emsg = N_("  Cannot change directory, please check permissions\n");
  353          serr = n_ERR_ACCES;
  354          break;
  355       case n_DLS_NAMETOOLONG:
  356          emsg = N_("Resulting dotlock filename would be too long\n");
  357          serr = n_ERR_ACCES;
  358          break;
  359       case n_DLS_ROFS:
  360          assert(dls & n_DLS_ABANDON);
  361          if(n_poption & n_PO_D_V)
  362             emsg = N_("  Read-only filesystem, not creating lock file\n");
  363          serr = n_ERR_ROFS;
  364          break;
  365       case n_DLS_NOPERM:
  366          if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V))
  367             emsg = N_("  Cannot create a dotlock file, "
  368                   "please check permissions\n"
  369                   "  (Or set *dotlock-disable*, then try again)\n");
  370          serr = n_ERR_ACCES;
  371          break;
  372       case n_DLS_NOEXEC:
  373          if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_DOTLOCK_PRIVSEP_NOTED)
  374                ) == n_PSO_INTERACTIVE || (n_poption & n_PO_D_V)){
  375             n_psonce |= n_PSO_DOTLOCK_PRIVSEP_NOTED;
  376             emsg = N_("  Cannot find privilege-separated dotlock program\n");
  377          }
  378          serr = n_ERR_NOENT;
  379          break;
  380       case n_DLS_PRIVFAILED:
  381          emsg = N_("  Privilege-separated dotlock program cannot change "
  382                "privileges\n");
  383          serr = n_ERR_PERM;
  384          break;
  385       case n_DLS_EXIST:
  386          emsg = N_("  It seems there is a stale dotlock file?\n"
  387                "  Please remove the lock file manually, then retry\n");
  388          serr = n_ERR_EXIST;
  389          break;
  390       case n_DLS_FISHY:
  391          emsg = N_("  Fishy!  Is someone trying to \"steal\" foreign files?\n"
  392                "  Please check the mailbox file etc. manually, then retry\n");
  393          serr = n_ERR_AGAIN; /* ? Hack to ignore *dotlock-ignore-error* xxx */
  394          break;
  395       default:
  396       case n_DLS_DUNNO:
  397          emsg = N_("  Unspecified dotlock file control process error.\n"
  398                "  Like broken I/O pipe; this one is unlikely to happen\n");
  399          if(serr != n_ERR_AGAIN)
  400             serr = n_ERR_INVAL;
  401          break;
  402       case n_DLS_PING:
  403          if(!didmsg)
  404             _DOMSG();
  405          n_err(".");
  406          didmsg = TRUM1;
  407          continue;
  408       }
  409 
  410       if(emsg != NULL){
  411          if(!didmsg){
  412             _DOMSG();
  413             didmsg = TRUM1;
  414          }
  415          if(didmsg == TRUM1)
  416             n_err(_(". failed\n"));
  417          didmsg = TRU1;
  418          n_err(V_(emsg));
  419          emsg = NULL;
  420       }
  421 
  422       if(dls & n_DLS_ABANDON){
  423          Pclose(rv, FAL0);
  424          rv = NULL;
  425          break;
  426       }
  427    }
  428 
  429 jleave:
  430    if(didmsg == TRUM1)
  431       n_err(". %s\n", (rv != NULL ? _("ok") : _("failed")));
  432    if(rv == NULL) {
  433       if(flocked){
  434          if(serr == n_ERR_ROFS)
  435             rv = (FILE*)-1;
  436          else if(serr != n_ERR_AGAIN && serr != n_ERR_EXIST &&
  437                ok_blook(dotlock_ignore_error)){
  438             n_OBSOLETE(_("*dotlock-ignore-error*: please use "
  439                "*dotlock-disable* instead"));
  440             if(n_poption & n_PO_D_V)
  441                n_err(_("  *dotlock-ignore-error* set: continuing\n"));
  442             rv = (FILE*)-1;
  443          }else
  444             goto jserr;
  445       }else
  446 jserr:
  447          n_err_no = serr;
  448    }
  449    NYD_LEAVE;
  450    return rv;
  451 jemsg:
  452    if(!didmsg)
  453       _DOMSG();
  454    n_err("\n");
  455    didmsg = TRU1;
  456    n_err(V_(emsg));
  457    goto jleave;
  458 #endif /* HAVE_DOTLOCK */
  459 #undef _DOMSG
  460 }
  461 
  462 /* s-it-mode */