/*
 * LTlock.c -- Lsof Test locking tests
 *
 * V. Abell
 * Purdue University
 */

/*
 * Copyright 2002 Purdue Research Foundation, West Lafayette, Indiana
 * 47907.  All rights reserved.
 *
 * Written by V. Abell.
 *
 * This software is not subject to any license of the American Telephone
 * and Telegraph Company or the Regents of the University of California.
 *
 * Permission is granted to anyone to use this software for any purpose on
 * any computer system, and to alter it and redistribute it freely, subject
 * to the following restrictions:
 *
 * 1. Neither the authors nor Purdue University are responsible for any
 *    consequences of the use of this software.
 *
 * 2. The origin of this software must not be misrepresented, either by
 *    explicit claim or by omission.  Credit to the authors and Purdue
 *    University must appear in documentation and sources.
 *
 * 3. Altered versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 4. This notice may not be removed or altered.
 */

#include "LsofTest.h"
#include "lsof_fields.h"

#if defined(LT_DIAL_aix)
/*
 * AIX-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_aix) */

#if defined(LT_DIAL_bsdi)
/*
 * BSDI-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_bsdi) */

#if defined(LT_DIAL_darwin)
/*
 * Darwin-specific items
 */

/*
 * There is no Darwin USE_* definition, because lock support in lsof for
 * Darwin is inadequate for this test.
 */
#endif /* defined(LT_DIAL_darwin) */

#if defined(LT_DIAL_du)
/*
 * DEC_OSF/1|Digital_UNIX|Tru64_UNIX-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_du) */

#if defined(LT_DIAL_freebsd)
/*
 * FreeBSD-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_freebsd) */

#if defined(LT_DIAL_linux)
/*
 * Linux-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_linux) */

#if defined(LT_DIAL_netbsd)
/*
 * NetBSD-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_netbsd) */

#if defined(LT_DIAL_openbsd)
/*
 * OpenBSD-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_openbsd) */

#if defined(LT_DIAL_hpux)
/*
 * HP-UX-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_hpux) */

#if defined(LT_DIAL_ns)
/*
 * NEXTSTEP-specific items
 */

#    define USE_FLOCK
#endif /* defined(LT_DIAL_ns) */

#if defined(LT_DIAL_osr)
/*
 * OSR-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_osr) */

#if defined(LT_DIAL_ou)
/*
 * OpenUNIX-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_ou) */

#if defined(LT_DIAL_openbsd)
/*
 * OpenBSD-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_openbsd) */

#if defined(LT_DIAL_solaris)
/*
 * Solaris-specific items
 */

#    define USE_FCNTL
#endif /* defined(solaris) */

#if defined(LT_DIAL_uw)
/*
 * UnixWare-specific items
 */

#    define USE_FCNTL
#endif /* defined(LT_DIAL_uw) */

#if !defined(USE_FLOCK) && !defined(USE_FCNTL)
/*
 * Here begins the version of this program for dialects that don't support
 * flock() or fcntl() locking.
 */

/*
 * Main program for dialects that don't support flock() of fcntl() locking.
 */

int main(int argc,     /* argument count */
         char *argv[]) /* arguments */
{
    char *pn; /* program name */
              /*
               * Get program name and issue error message.
               */
    if ((pn = (char *)strrchr(argv[0], '/')))
        pn++;
    else
        pn = argv[0];
    (void)printf("%s ... %s\n", pn, LT_DONT_DO_TEST);
    return (0);
}
#else /* defined(USE_FLOCK) || defined(USE_FCNTL) */

/*
 * Local definitions
 */

#    define FULL_EX_LOCK 0 /* get a full file exclusive lock */
#    define FULL_SH_LOCK 1 /* get a full file shared lock */
#    define PART_EX_LOCK 2 /* get a partial file exclusive lock */
#    define PART_SH_LOCK 3 /* get a partial file shared lock */

/*
 * Globals
 */

int Fd = -1;               /* test file descriptor; open if >= 0 */
pid_t MyPid = (pid_t)0;    /* PID of this process */
char *Path = (char *)NULL; /* test file path; none if NULL */
char *Pn = (char *)NULL;   /* program name */

/*
 * Local function prototypes
 */

static void cleanup(void);
static char *lkfile(int ty);
static char *tstwlsof(char *opt, char *xlk);
static char *unlkfile(int ty);

/*
 * Main program for dialects that support locking tests.
 */

int main(int argc,     /* argument count */
         char *argv[]) /* arguments */
{
    char buf[2048];            /* temporary buffer */
    char *em;                  /* error message pointer */
    int ti;                    /* temporary index */
    char *tcp;                 /* temporary character pointer */
    int tlen;                  /* temporary length -- e.g., as
                                * returned by MkStrCpy() */
    char *tstR = (char *)NULL; /* "R" lock test result */
    char *tstr = (char *)NULL; /* "r" lock test result */
    char *tstW = (char *)NULL; /* "W" lock test result */
    char *tstw = (char *)NULL; /* "w" lock test result */
    int xv = 0;                /* exit value */
                               /*
                                * Get program name and PID, issue start message, and build space prefix.
                                */
    if ((Pn = strrchr(argv[0], '/')))
        Pn++;
    else
        Pn = argv[0];
    MyPid = getpid();
    (void)printf("%s ... ", Pn);
    (void)fflush(stdout);
    (void)PrtMsg((char *)NULL, Pn);
    /*
     * Process arguments.
     */
    if (ScanArg(argc, argv, "hp:", Pn))
        xv = 1;
    if (xv || LTopt_h) {
        (void)PrtMsg("usage: [-h] [-p path]", Pn);
        (void)PrtMsg("       -h       print help (this panel)", Pn);
        (void)PrtMsgX("       -p path  define test file path", Pn, cleanup, xv);
    }
    /*
     * See if lsof can be executed and can access kernel memory.
     */
    if ((em = IsLsofExec()))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((em = CanRdKmem()))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    /*
     * If a path was supplied in an "-p path" option, use it.  Otherwise
     * construct a path in the CWD.
     */
    if (!(Path = LTopt_p)) {
        (void)snprintf(buf, sizeof(buf), "./config.LTlock%ld", (long)MyPid);
        buf[sizeof(buf) - 1] = '\0';
        Path = MkStrCpy(buf, &tlen);
    }
    /*
     * Fill buffer for writing to the test file.
     */
    for (ti = 0; ti < sizeof(buf); ti++) {
        buf[ti] = (char)(ti & 0xff);
    }
    /*
     * Open a new test file at the specified path.
     */
    (void)unlink(Path);
    if ((Fd = open(Path, O_RDWR | O_CREAT, 0600)) < 0) {
        (void)fprintf(stderr, "ERROR!!!  can't open %s\n", Path);

    print_file_error:

        MsgStat = 1;
        (void)snprintf(buf, sizeof(buf) - 1, "      Errno %d: %s", errno,
                       strerror(errno));
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsgX(buf, Pn, cleanup, 1);
    }
    /*
     * Write a buffer load at the beginning of the file.
     */
    if (write(Fd, buf, sizeof(buf)) != sizeof(buf)) {
        (void)fprintf(stderr,
                      "ERROR!!!  can't write %d bytes to the beginning of %s\n",
                      (int)sizeof(buf), Path);
        goto print_file_error;
    }
    /*
     * Fsync() the file.
     */
    if (fsync(Fd)) {
        (void)fprintf(stderr, "ERROR!!!  can't fsync %s\n", Path);
        goto print_file_error;
    }
    /*
     * Quit (with a hint) if the test file is on an NFS file system.
     */
    if (!tstwlsof("-wNa", " ")) {
        (void)printf("ERROR!!!  %s is NFS-mounted.\n", Path);
        MsgStat = 1;
        (void)PrtMsg("Lsof can't report lock information on files that", Pn);
        (void)PrtMsg("are located on file systems mounted from a remote", Pn);
        (void)PrtMsg("NFS server.\n", Pn);
        (void)PrtMsg("Hint: try using \"-p path\" to supply a path in a", Pn);
        (void)PrtMsg("non-NFS file system.\n", Pn);
        (void)PrtMsgX("See 00FAQ and 00TEST for more information.", Pn, cleanup,
                      1);
    }
    /*
     * Get an exclusive lock on the entire file and test it with lsof.
     */
    if ((em = lkfile(FULL_EX_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((tstW = tstwlsof("-w", "W")))
        (void)PrtMsg(tstW, Pn);
    /*
     * Get a shared lock on the entire file and test it with lsof.
     */
    if ((em = unlkfile(FULL_EX_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((em = lkfile(FULL_SH_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((tstR = tstwlsof("-w", "R")))
        (void)PrtMsg(tstR, Pn);

#    if defined(USE_FLOCK)
    /*
     * If using flock(), skip the byte lock tests.
     */
    tstr = tstw = (char *)NULL;
#    endif /* defined(USE_FLOCK) */

#    if defined(USE_FCNTL)
    /*
     * If using fcntl(), do exclusive and shared byte lock tests,
     */
    if ((em = unlkfile(FULL_SH_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((em = lkfile(PART_EX_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((tstw = tstwlsof("-w", "w")))
        (void)PrtMsg(tstw, Pn);
    if ((em = unlkfile(PART_EX_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((em = lkfile(PART_SH_LOCK)))
        (void)PrtMsgX(em, Pn, cleanup, 1);
    if ((tstr = tstwlsof("-w", "r")))
        (void)PrtMsg(tstr, Pn);
#    endif /* defined(USE_FCNTL) */

    /*
     * Compute exit value and exit.
     */
    if (tstr || tstR || tstw || tstW) {
        tcp = (char *)NULL;
#    if defined(LT_DIAL_freebsd)
        /*
         * Note: in future FreeBSD releases with
         * https://reviews.freebsd.org/D34756, this will work as non-root user
         */
        (void)PrtMsg("Hint: lsof is unable to get the lock status when running "
                     "as non-root",
                     Pn);
        (void)PrtMsg("in certain FreeBSD releases (e.g. FreeBSD 13.1), thus "
                     "skipping the test",
                     Pn);
        xv = 77;
#    elif defined(LT_DIAL_openbsd)
        (void)PrtMsg("Hint: lsof is unable to get the lock status in OpenBSD",
                     Pn);
        (void)PrtMsg(
            "since OpenBSD kernel forbids kvm_read, thus skipping the test",
            Pn);
        xv = 77;
#    else
        xv = 1;
#    endif
    } else {
        tcp = "OK";
        xv = 0;
    }
    (void)PrtMsgX(tcp, Pn, cleanup, xv);
    return (0);
}

/*
 * cleanup() -- release resources
 */

static void cleanup() {
    if (Fd >= 0) {
        (void)close(Fd);
        Fd = -1;
        if (Path) {
            (void)unlink(Path);
            Path = (char *)NULL;
        }
    }
}

/*
 * lkfile() -- lock the test file
 */

static char *lkfile(int ty) /* a *_*_LOCK requested */
{
    char buf[2048];  /* temporary buffer */
    int ti;          /* temporary integer */

#    if defined(USE_FLOCK)
    int flf;         /* flock() function */
#    endif /* defined(USE_FLOCK) */

#    if defined(USE_FCNTL)
    struct flock fl; /* flock control structure */
                     /*
                      * Check fcntl() lock request.
                      */
    (void)memset((void *)&fl, 0, sizeof(fl));
    switch (ty) {
    case FULL_EX_LOCK:
        fl.l_type = F_WRLCK;
        break;
    case FULL_SH_LOCK:
        fl.l_type = F_RDLCK;
        break;
    case PART_EX_LOCK:
        fl.l_type = F_WRLCK;
        fl.l_len = (off_t)1;
        break;
    case PART_SH_LOCK:
        fl.l_type = F_RDLCK;
        fl.l_len = (off_t)1;
        break;
    default:
        (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  unknown lock type: %d",
                       ty);
        buf[sizeof(buf) - 1] = '\0';
        return (MkStrCpy(buf, &ti));
    }
    /*
     * Lock test file with fcntl().
     */
    if (fcntl(Fd, F_SETLK, &fl) != -1)
        return ((char *)NULL);
    (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  fcntl() lock error: %s",
                   strerror(errno));
    buf[sizeof(buf) - 1] = '\0';
    return (MkStrCpy(buf, &ti));
#    endif /* defined(USE_FCNTL) */

#    if defined(USE_FLOCK)
    /*
     * Check flock() lock request.
     */
    switch (ty) {
    case FULL_EX_LOCK:
        flf = LOCK_EX;
        break;
    case FULL_SH_LOCK:
        flf = LOCK_SH;
        break;
    case PART_EX_LOCK:
    case PART_SH_LOCK:
        return ("ERROR!!!  flock() doesn't support partial locks");
        break;
    default:
        (void)snprintf(buf, sizeof(buf) - 1,
                       "ERROR!!!  unknown flock() type: %d", ty);
        buf[sizeof(buf) - 1] = '\0';
        return (MkStrCpy(buf, &ti));
    }
    /*
     * Acquire lock.
     */
    if (!flock(Fd, flf))
        return ((char *)NULL);
    (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  flock() %s lock failed: %s",
                   (flf == LOCK_EX) ? "exclusive" : "shared", strerror(errno));
    buf[sizeof(buf) - 1] = '\0';
    return (MkStrCpy(buf, &ti));
#    endif /* defined(USE_FLOCK) */
}

/*
 * tstwlsof() -- test the open file with lsof
 */

static char *tstwlsof(char *opt, /* extra lsof options */
                      char *xlk) /* expected lock value */
{
    char buf[2048];           /* temporary buffer */
    LTfldo_t *cmdp;           /* command pointer */
    LTfldo_t *devp;           /* device pointer */
    char *cem;                /* current error message pointer */
    int ff = 0;               /* file found status */
    LTfldo_t *fop;            /* field output pointer */
    LTfldo_t *inop;           /* inode number pointer */
    LTfldo_t *lkp;            /* lock pointer */
    LTdev_t lsofdc;           /* lsof device components */
    int nf;                   /* number of fields */
    LTfldo_t *nmp;            /* file name pointer */
    char *opv[4];             /* option vector for ExecLsof() */
    char *pem = (char *)NULL; /* previous error message pointer */
    pid_t pid;                /* PID */
    int pids = 0;             /* PID found status */
    struct stat sb;           /* stat(2) buffer */
    LTdev_t stdc;             /* stat(2) device components */
    char *tcp;                /* temporary character pointer */
    int ti;                   /* temporary integer */
    LTfldo_t *typ;            /* file type pointer */
                              /*
                               * Make sure there is an expected lock value.
                               */
    if (!xlk || !*xlk)
        (void)PrtMsgX("ERROR!!!  no expected lock value", Pn, cleanup, 1);
    /*
     * Get test file's information.
     */
    if (stat(Path, &sb)) {
        (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  can't stat(2) %s: %s",
                       Path, strerror(errno));
        buf[sizeof(buf) - 1] = '\0';
        (void)PrtMsgX(buf, Pn, cleanup, 1);
    }
    /*
     * Extract components from test file's device number.
     */
    if ((cem = ConvStatDev(&sb.st_dev, &stdc)))
        (void)PrtMsgX(cem, Pn, cleanup, 1);
    /*
     * Complete the option vector and start lsof execution.
     */
    ti = 0;
    if (opt && *opt)
        opv[ti++] = opt;

#    if defined(USE_LSOF_C_OPT)
    opv[ti++] = "-C";
#    endif /* defined(USE_LSOF_C_OPT) */

    opv[ti++] = Path;
    opv[ti] = (char *)NULL;
    if ((cem = ExecLsof(opv)))
        return (cem);
    /*
     * Read lsof output.
     */
    while (!ff && (fop = RdFrLsof(&nf, &cem))) {
        if (cem) {
            if (pem)
                (void)PrtMsg(pem, Pn);
            return (cem);
        }
        switch (fop->ft) {
        case LSOF_FID_PID:

            /*
             * This is a process information line.
             */
            pid = (pid_t)atoi(fop->v);
            pids = 1;
            cmdp = (LTfldo_t *)NULL;
            for (fop++, ti = 1; ti < nf; fop++, ti++) {
                switch (fop->ft) {
                case LSOF_FID_CMD:
                    cmdp = fop;
                    break;
                }
            }
            if (!cmdp || (pid != MyPid))
                pids = 0;
            break;
        case LSOF_FID_FD:

            /*
             * This is a file descriptor line.  Make sure its number matches the
             * test file's descriptor number.
             *
             * Scan for lock and name fields.
             */
            if (!pids)
                break;
            for (ti = 0, tcp = fop->v; *tcp; tcp++) {

                /*
                 * Convert file descriptor to a number.
                 */
                if (*tcp == ' ')
                    continue;
                if (((int)*tcp < (int)'0') || ((int)*tcp > (int)'9')) {
                    ti = -1;
                    break;
                }
                ti = (ti * 10) + (int)*tcp - (int)'0';
            }
            if (Fd != ti)
                break;
            devp = inop = lkp = nmp = (LTfldo_t *)NULL;
            for (fop++, ti = 1; ti < nf; fop++, ti++) {
                switch (fop->ft) {
                case LSOF_FID_DEVN:
                    devp = fop;
                    break;
                case LSOF_FID_INODE:
                    inop = fop;
                    break;
                case LSOF_FID_LOCK:
                    lkp = fop;
                    break;
                case LSOF_FID_NAME:
                    nmp = fop;
                    break;
                case LSOF_FID_TYPE:
                    typ = fop;
                    break;
                }
            }
            /*
             * Check the results of the file descriptor field scan.
             *
             * (Don't compare path names because of symbolic link interference.)
             */
            if (!devp || !inop || !nmp || !typ)
                break;
            if (strcasecmp(typ->v, "reg") && strcasecmp(typ->v, "vreg"))
                break;
            if (ConvLsofDev(devp->v, &lsofdc))
                break;
            if ((stdc.maj != lsofdc.maj) || (stdc.min != lsofdc.min) ||
                (stdc.unit != lsofdc.unit))
                break;
            (void)snprintf(buf, sizeof(buf) - 1, "%u", (unsigned int)sb.st_ino);
            buf[sizeof(buf) - 1] = '\0';
            if (strcmp(inop->v, buf))
                break;
            /*
             * The specified file has been located.  Check its lock status.
             */
            ff = 1;
            if (!lkp || strcmp(lkp->v, xlk)) {
                if (pem)
                    (void)PrtMsg(pem, Pn);
                (void)snprintf(buf, sizeof(buf) - 1,
                               "lock mismatch: expected %s, got \"%s\"", xlk,
                               lkp ? lkp->v : "(none)");
                pem = MkStrCpy(buf, &ti);
            }
            break;
        }
    }
    (void)StopLsof();
    if (!ff) {
        if (pem)
            (void)PrtMsg(pem, Pn);
        (void)snprintf(buf, sizeof(buf) - 1,
                       "lock test file %s not found by lsof", Path);
        buf[sizeof(buf) - 1] = '\0';
        return (MkStrCpy(buf, &ti));
    }
    return (pem);
}

/*
 * unlkfile() -- unlock the test file
 */

static char *unlkfile(int ty) /* current *_*_LOCK lock typ */
{
    char buf[2048];  /* temporary buffer */
    int ti;          /* temporary integer */

#    if defined(USE_FCNTL)
    struct flock fl; /* flock control structure */
                     /*
                      * Check current fcntl() lock type.
                      */
    (void)memset((void *)&fl, 0, sizeof(fl));
    switch (ty) {
    case FULL_EX_LOCK:
    case FULL_SH_LOCK:
        break;
    case PART_EX_LOCK:
    case PART_SH_LOCK:
        fl.l_len = (off_t)1;
        break;
    default:
        (void)snprintf(buf, sizeof(buf) - 1,
                       "ERROR!!!  unknown unlock type: %d", ty);
        buf[sizeof(buf) - 1] = '\0';
        return (MkStrCpy(buf, &ti));
    }
    /*
     * Unlock test file with fcntl().
     */
    fl.l_type = F_UNLCK;
    if (fcntl(Fd, F_SETLK, &fl) != -1)
        return ((char *)NULL);
    (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  fcntl() unlock error: %s",
                   strerror(errno));
    buf[sizeof(buf) - 1] = '\0';
    return (MkStrCpy(buf, &ti));
#    endif /* defined(USE_FCNTL) */

#    if defined(USE_FLOCK)
    /*
     * Check current flock() lock type.
     */
    switch (ty) {
    case FULL_EX_LOCK:
    case FULL_SH_LOCK:
        break;
    default:
        (void)snprintf(buf, sizeof(buf) - 1,
                       "ERROR!!!   unknown unlock type: %s", ty);
        buf[sizeof(buf) - 1] = '\0';
        return (MkStrCpy(buf, &ti));
    }
    /*
     * Unlock file with flock().
     */
    if (!flock(Fd, LOCK_UN))
        return ((char *)NULL);
    (void)snprintf(buf, sizeof(buf) - 1, "ERROR!!!  flock() unlock error: %s",
                   strerror(errno));
    return (MkStrCpy(buf, &ti));
#    endif /* defined(USE_FLOCK) */
}
#endif     /* !defined(USE_FLOCK) && !defined(USE_FCNTL) */
