#define _GNU_SOURCE
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <sys/types.h>
#include <pwd.h>
#include "iidstr.h"

#ifdef IID_PRAGMA

#define CFP_FILE    0x01
#define CFP_DIR     0x02
#define CFP_MODE    0x04
#define CFP_USER    0x08
#define CFP_GROUP   0x10

#endif /* IID_PRAGMA */

#define CFP_CHECK   (CFP_FILE|CFP_DIR)
#define CFP_MASK    (CFP_CHECK|CFP_MODE|CFP_USER|CFP_GROUP)

/*
    name:
        mkdirpath

    description:
        similar function as system function mkdir(2), 
        but creates complete path as in utility mkdir -p

    arguments:
        path        path to directory to create
        user        (optional) owner name
        group       (optional) group name
        mode        access mode (see mkdir(2))
        cflag       if != 0, check path only (no create action)
                    flag mask:  CFP_FILE    endpoint is file or fifo
                                CFP_DIR     endpoint is directory
                                CFP_MOD     check mode of endpoint
                                CFP_UG      check endpoint user/group
        fail        path component causing failure

    return:
        0           success
        >0          fail (see errno(3))
                    special for check mode: unsatisfied check for file
                    return ENFILE

    remarks:
        this function uses ystrtok() and xfree() from libiidstr
        and explode from libiiddynarray
        if user or group are set to NULL, the current user/group is used
        mode and user/group are applied to new created directories only
*/
static int genPath(const char *path, const char *user, const char *group,
                   int mode, int cflag, char **fail)
{
    char *rpath __attribute__ ((__cleanup__(xfree))) = NULL;
    char *check __attribute__ ((__cleanup__(xfree))) = NULL;
    char *cp, *ctx, *tok;
    struct stat sb;
    struct passwd *pw = NULL;
    int rtc;
    uid_t uid;
    gid_t gid;

    // path must not be empty
    if (!path)
        return EINVAL;

    // set up uid and gid for new dir parts
    if (user) {
        if (!(pw = getpwnam(user)))
            return errno;  
        uid = pw->pw_uid;
    }
    else {
        uid = getuid();
    }

    if (group) {
        if (!pw)
            if (!(pw = getpwnam(user)))
                return errno;  
        gid = pw->pw_gid;
    }
    else {
        gid = getgid();
    }

    rpath = strdup(path);
    check = strdup(path);
    cp = check;

    // step through all parts of the real path startinfg at root
    // uses non-greedy ystrtok
    for (tok = ystrtok(rpath, "/", &ctx); tok; 
         tok = ystrtok(NULL, "/", &ctx)) {
        // append part to check path
        if (tok != rpath)
            cp = stpcpy(cp, "/");
        cp = stpcpy(cp, tok);
printf("tok: [%s]  check: [%s]\n",tok, check);
        if (!*check)
            continue;
        rtc = stat(check, &sb);
        if (rtc) {
            int error = 0;
            if (cflag & (CFP_CHECK)) {
                if (errno == ENOENT)
                    error = ENOENT;
                else if ((cflag & CFP_FILE) && 
                         !(sb.st_mode & (S_IFREG|S_IFIFO)))
                    error = ENFILE;
                else if ((cflag & CFP_DIR) && !(sb.st_mode & S_IFDIR)) 
                    error = ENOTDIR;
                else if ((cflag & CFP_MODE) && (sb.st_mode&07777) != mode)
                    error = EPERM;
                else if ((cflag & CFP_USER) && user && sb.st_uid != uid)
                    error = EPERM;
                else if ((cflag & CFP_GROUP) && group && sb.st_gid != gid)
                    error = EPERM;
                if (error) {
                    if (fail)
                        *fail = strdup(check);
                    return error;
                }
            }
            if (errno != ENOENT)
                error = errno;
            else if ((rtc = mkdir(check, mode)))
                error = errno;
            else if((rtc = chown(check, user ? uid : -1, group ? gid : -1)))
                error = errno;
            if (error) {
                if (fail)
                    *fail = strdup(check);
                return error;
            }
        }
    }
    return (*check) ? 0 : ENODATA;
}

/*
    name:
        mkPath

    description:
        similar function as libc function mkdir(2), 
        but creates complete path as in utility mkdir -p

    arguments:
        path        path to directory to create
        user        (optional) owner name
        group       (optional) group name
        mode        access mode for new path parts
        cflag       if != 0, check path only (no create action)
                    flag mask:  CFP_FILE    endpoint is file or fifo
                                CFP_DIR     endpoint is directory
                                CFP_MOD     check mode of endpoint
                                CFP_UG      check endpoint user/group
    return:
        0           success
        >0          fail (see errno(3))

    remarks:
        this function uses xstrtok() and xfree() from libiidstr
        if user or group are set to NULL, the current user/group is used
        mode and user/group are applied to new created directories only
*/
int mkPath(const char *path, const char *user, const char *group, int mode)
{
    const int cflag = (CFP_USER|CFP_GROUP|CFP_MODE);
    return genPath(path, user, group, mode, cflag, NULL);
}

/*
    name:
        checkPath

    description:
        check existence and access mode of _all_ components in path

    arguments:
        path        path to check
        user        (optional) owner name
        group       (optional) group name
        mode        access mode for new path parts
        cflag       if != 0, check path only (no create action)
                    flag mask:  CFP_FILE    endpoint is file or fifo
                                CFP_DIR     endpoint is directory
                                CFP_MOD     check mode of endpoint
                                CFP_UG      check endpoint user/group
        fail        if not NULL pointer is set to failing partial path
                    (must be free()'ed by caller)

    return:
        0           success, all parts of path exists and modes match
        >0          fail (see errno(3))
*/
int checkPath(const char *path, const char *user, const char *group,
              int mode, int cflag, char **fail)
{
    // we expect exactly one of CFP_FILE or CFP_DIR
    if ((cflag & CFP_CHECK) != CFP_FILE && (cflag & CFP_CHECK) != CFP_DIR) 
        return EINVAL;
    return genPath(path, user, group, mode, (cflag & CFP_MASK), fail);
}
