/*
 * Copyright (c) 1980 Regents of the University of California.
 * All rights reserved.  The Berkeley software License Agreement
 * specifies the terms and conditions for redistribution.
 */

/*
 * chmod options mode files
 * where
 *  mode is [ugoa][+-=][rwxXstugo] or an octal number
 *  options are -Rf
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/dir.h>

static  char    *fchdirmsg = "Can't fchdir() back to starting directory";
char    *modestring, *ms;
int um;
int status;
int fflag;
int rflag;

main(argc, argv)
    char *argv[];
{
    register char *p, *flags;
    register int i;
    struct stat st;
    int fcurdir;

    if (argc < 3) {
        fprintf(stderr,
            "Usage: chmod [-Rf] [ugoa][+-=][rwxXstugo] file ...\n");
        exit(-1);
    }
    argv++, --argc;
    while (argc > 0 && argv[0][0] == '-') {
        for (p = &argv[0][1]; *p; p++) switch (*p) {

        case 'R':
            rflag++;
            break;

        case 'f':
            fflag++;
            break;

        default:
            goto done;
        }
        argc--, argv++;
    }
done:
    modestring = argv[0];
    um = umask(0);
    (void) newmode(0);
    if  (rflag)
        {
        fcurdir = open(".", O_RDONLY);
        if  (fcurdir < 0)
            fatal(255, "Can't open .");
        }

    for (i = 1; i < argc; i++) {
        p = argv[i];
        /* do stat for directory arguments */
        if (lstat(p, &st) < 0) {
            status += Perror(p);
            continue;
        }
        if (rflag && (st.st_mode&S_IFMT) == S_IFDIR) {
            status += chmodr(p, newmode(st.st_mode), fcurdir);
            continue;
        }
        if ((st.st_mode&S_IFMT) == S_IFLNK && stat(p, &st) < 0) {
            status += Perror(p);
            continue;
        }
        if (chmod(p, newmode(st.st_mode)) < 0) {
            status += Perror(p);
            continue;
        }
    }
    close(fcurdir);
    exit(status);
}

chmodr(dir, mode, savedir)
    char *dir;
    int mode;
    int savedir;
{
    register DIR *dirp;
    register struct direct *dp;
    struct stat st;
    int ecode;

    /*
     * Change what we are given before doing it's contents
     */
    if (chmod(dir, newmode(mode)) < 0 && Perror(dir))
        return (1);
    if (chdir(dir) < 0) {
        Perror(dir);
        return (1);
    }
    if ((dirp = opendir(".")) == NULL) {
        Perror(dir);
        return (1);
    }
    dp = readdir(dirp);
    dp = readdir(dirp); /* read "." and ".." */
    ecode = 0;
    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
        if (lstat(dp->d_name, &st) < 0) {
            ecode = Perror(dp->d_name);
            if (ecode)
                break;
            continue;
        }
        if ((st.st_mode&S_IFMT) == S_IFDIR) {
            ecode = chmodr(dp->d_name, newmode(st.st_mode), dirfd(dirp));
            if (ecode)
                break;
            continue;
        }
        if ((st.st_mode&S_IFMT) == S_IFLNK)
            continue;
        if (chmod(dp->d_name, newmode(st.st_mode)) < 0 &&
            (ecode = Perror(dp->d_name)))
            break;
    }
    if  (fchdir(savedir) < 0)
        fatal(255, fchdirmsg);
    closedir(dirp);
    return (ecode);
}

error(fmt, a)
    char *fmt, *a;
{

    if (!fflag) {
        fprintf(stderr, "chmod: ");
        fprintf(stderr, fmt, a);
        putc('\n', stderr);
    }
    return (!fflag);
}

fatal(status, fmt, a)
    int status;
    char *fmt, *a;
{

    fflag = 0;
    (void) error(fmt, a);
    exit(status);
}

Perror(s)
    char *s;
{

    if (!fflag) {
        fprintf(stderr, "chmod: ");
        perror(s);
    }
    return (!fflag);
}

newmode(nm)
    unsigned nm;
{
    register o, m, b;
    int savem;

    ms = modestring;
    savem = nm;
    m = abss();
    if (*ms == '\0')
        return (m);
    do {
        m = who();
        while (o = what()) {
            b = where(nm);
            switch (o) {
            case '+':
                nm |= b & m;
                break;
            case '-':
                nm &= ~(b & m);
                break;
            case '=':
                nm &= ~m;
                nm |= b & m;
                break;
            }
        }
    } while (*ms++ == ',');
    if (*--ms)
        fatal(255, "invalid mode");
    return (nm);
}

abss()
{
    register c, i;

    i = 0;
    while ((c = *ms++) >= '0' && c <= '7')
        i = (i << 3) + (c - '0');
    ms--;
    return (i);
}

#define USER    05700   /* user's bits */
#define GROUP   02070   /* group's bits */
#define OTHER   00007   /* other's bits */
#define ALL 01777   /* all (note absence of setuid, etc) */

#define READ    00444   /* read permit */
#define WRITE   00222   /* write permit */
#define EXEC    00111   /* exec permit */
#define SETID   06000   /* set[ug]id */
#define STICKY  01000   /* sticky bit */

who()
{
    register m;

    m = 0;
    for (;;) switch (*ms++) {
    case 'u':
        m |= USER;
        continue;
    case 'g':
        m |= GROUP;
        continue;
    case 'o':
        m |= OTHER;
        continue;
    case 'a':
        m |= ALL;
        continue;
    default:
        ms--;
        if (m == 0)
            m = ALL & ~um;
        return (m);
    }
}

what()
{

    switch (*ms) {
    case '+':
    case '-':
    case '=':
        return (*ms++);
    }
    return (0);
}

where(om)
    register om;
{
    register m;

    m = 0;
    switch (*ms) {
    case 'u':
        m = (om & USER) >> 6;
        goto dup;
    case 'g':
        m = (om & GROUP) >> 3;
        goto dup;
    case 'o':
        m = (om & OTHER);
    dup:
        m &= (READ|WRITE|EXEC);
        m |= (m << 3) | (m << 6);
        ++ms;
        return (m);
    }
    for (;;) switch (*ms++) {
    case 'r':
        m |= READ;
        continue;
    case 'w':
        m |= WRITE;
        continue;
    case 'x':
        m |= EXEC;
        continue;
    case 'X':
        if ((om & S_IFDIR) || (om & EXEC))
            m |= EXEC;
        continue;
    case 's':
        m |= SETID;
        continue;
    case 't':
        m |= STICKY;
        continue;
    default:
        ms--;
        return (m);
    }
}
