"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.10/dotlock.c" (25 Mar 2018, 13645 Bytes) of package /linux/misc/s-nail-14.9.10.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.9_vs_14.9.10.

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