/*
 * Utility routines.
 *
 * Copyright (C) 1998 by Erik Andersen <andersee@debian.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Based in part on code from sash, Copyright (c) 1999 by David I. Bell 
 * Permission has been granted to redistribute this code under the GPL.
 *
 */

#include "internal.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <time.h>
#include <utime.h>
#include <sys/stat.h>
#include <unistd.h>



#if defined (BB_CP) || defined (BB_MV)
/*
 * Return TRUE if a fileName is a directory.
 * Nonexistant files return FALSE.
 */
int isDirectory(const char *name)
{
    struct stat statBuf;

    if (stat(name, &statBuf) < 0)
	return FALSE;

    return S_ISDIR(statBuf.st_mode);
}


/*
 * Copy one file to another, while possibly preserving its modes, times,
 * and modes.  Returns TRUE if successful, or FALSE on a failure with an
 * error message output.  (Failure is not indicted if the attributes cannot
 * be set.)
 */
int
copyFile(
	 const char *srcName,
	 const char *destName, int setModes, int followLinks)
{
    int rfd;
    int wfd;
    int rcc;
    int result;
    char buf[BUF_SIZE];
    struct stat srcStatBuf;
    struct stat dstStatBuf;
    struct utimbuf times;

    if (followLinks == FALSE)
	result = stat(srcName, &srcStatBuf);
    else 
	result = lstat(srcName, &srcStatBuf);

    if (result < 0) {
	perror(srcName);
	return FALSE;
    }

    if (followLinks == FALSE)
	result = stat(destName, &dstStatBuf);
    else 
	result = lstat(destName, &dstStatBuf);
    if (result < 0) {
	dstStatBuf.st_ino = -1;
	dstStatBuf.st_dev = -1;
    }

    if ((srcStatBuf.st_dev == dstStatBuf.st_dev) &&
	(srcStatBuf.st_ino == dstStatBuf.st_ino)) {
	fprintf(stderr, "Copying file \"%s\" to itself\n", srcName);
	return FALSE;
    }

    if (S_ISDIR(srcStatBuf.st_mode)) {
	//fprintf(stderr, "copying directory %s to %s\n", srcName, destName);
	/* Make sure the directory is writable */
	if (mkdir(destName, 0777777 ^ umask(0))) {
	    perror(destName);
	    return (FALSE);
	}
    } else if (S_ISLNK(srcStatBuf.st_mode)) {
	char *link_val;
	int link_size;

	//fprintf(stderr, "copying link %s to %s\n", srcName, destName);
	link_val = (char *) alloca(PATH_MAX + 2);
	link_size = readlink(srcName, link_val, PATH_MAX + 1);
	if (link_size < 0) {
	    perror(srcName);
	    return (FALSE);
	}
	link_val[link_size] = '\0';
	if (symlink(link_val, destName)) {
	    perror(destName);
	    return (FALSE);
	}
    } else if (S_ISFIFO(srcStatBuf.st_mode)) {
	//fprintf(stderr, "copying fifo %s to %s\n", srcName, destName);
	if (mkfifo(destName, 644)) {
	    perror(destName);
	    return (FALSE);
	}
    } else if (S_ISBLK(srcStatBuf.st_mode) || S_ISCHR(srcStatBuf.st_mode) 
	    || S_ISSOCK (srcStatBuf.st_mode)) {
	//fprintf(stderr, "copying soc, blk, or chr %s to %s\n", srcName, destName);
	if (mknod(destName, srcStatBuf.st_mode, srcStatBuf.st_rdev)) {
	    perror(destName);
	    return (FALSE);
	}
    } else if (S_ISREG(srcStatBuf.st_mode)) {
	//fprintf(stderr, "copying regular file %s to %s\n", srcName, destName);
	rfd = open(srcName, O_RDONLY);
	if (rfd < 0) {
	    perror(srcName);
	    return FALSE;
	}

	wfd = creat(destName, srcStatBuf.st_mode);
	if (wfd < 0) {
	    perror(destName);
	    close(rfd);
	    return FALSE;
	}

	while ((rcc = read(rfd, buf, sizeof(buf))) > 0) {
	    if (fullWrite(wfd, buf, rcc) < 0)
		goto error_exit;
	}
	if (rcc < 0) {
	    goto error_exit;
	}

	close(rfd);
	if (close(wfd) < 0) {
	    return FALSE;
	}
    }

    if (setModes == TRUE) {
	//fprintf(stderr, "Setting permissions for %s\n", destName);
	chmod(destName, srcStatBuf.st_mode);
	if (followLinks == TRUE)
	    chown(destName, srcStatBuf.st_uid, srcStatBuf.st_gid);
	else
	    lchown(destName, srcStatBuf.st_uid, srcStatBuf.st_gid);

	times.actime = srcStatBuf.st_atime;
	times.modtime = srcStatBuf.st_mtime;

	utime(destName, &times);
    }

    return TRUE;


  error_exit:
    //fprintf(stderr, "choking on %s\n", destName);
    perror(destName);
    close(rfd);
    close(wfd);

    return FALSE;
}
#endif


#ifdef BB_MV
/*
 * Build a path name from the specified directory name and file name.
 * If the directory name is NULL, then the original fileName is returned.
 * The built path is in a static area, and is overwritten for each call.
 */
char *buildName(const char *dirName, const char *fileName)
{
    const char *cp;
    static char buf[PATH_LEN];

    if ((dirName == NULL) || (*dirName == '\0')) {
	strcpy(buf, fileName);
	return buf;
    }

    cp = strrchr(fileName, '/');

    if (cp)
	fileName = cp + 1;

    strcpy(buf, dirName);
    strcat(buf, "/");

    return buf;
}
#endif



/*
 * Return the standard ls-like mode string from a file mode.
 * This is static and so is overwritten on each call.
 */
const char *modeString(int mode)
{
    static char buf[12];

    strcpy(buf, "----------");

    /*
     * Fill in the file type.
     */
    if (S_ISDIR(mode))
	buf[0] = 'd';
    if (S_ISCHR(mode))
	buf[0] = 'c';
    if (S_ISBLK(mode))
	buf[0] = 'b';
    if (S_ISFIFO(mode))
	buf[0] = 'p';
#ifdef	S_ISLNK
    if (S_ISLNK(mode))
	buf[0] = 'l';
#endif
#ifdef	S_ISSOCK
    if (S_ISSOCK(mode))
	buf[0] = 's';
#endif

    /*
     * Now fill in the normal file permissions.
     */
    if (mode & S_IRUSR)
	buf[1] = 'r';
    if (mode & S_IWUSR)
	buf[2] = 'w';
    if (mode & S_IXUSR)
	buf[3] = 'x';
    if (mode & S_IRGRP)
	buf[4] = 'r';
    if (mode & S_IWGRP)
	buf[5] = 'w';
    if (mode & S_IXGRP)
	buf[6] = 'x';
    if (mode & S_IROTH)
	buf[7] = 'r';
    if (mode & S_IWOTH)
	buf[8] = 'w';
    if (mode & S_IXOTH)
	buf[9] = 'x';

    /*
     * Finally fill in magic stuff like suid and sticky text.
     */
    if (mode & S_ISUID)
	buf[3] = ((mode & S_IXUSR) ? 's' : 'S');
    if (mode & S_ISGID)
	buf[6] = ((mode & S_IXGRP) ? 's' : 'S');
    if (mode & S_ISVTX)
	buf[9] = ((mode & S_IXOTH) ? 't' : 'T');

    return buf;
}


#ifdef BB_TAR
/*
 * Get the time string to be used for a file.
 * This is down to the minute for new files, but only the date for old files.
 * The string is returned from a static buffer, and so is overwritten for
 * each call.
 */
const char *timeString(time_t timeVal)
{
    time_t now;
    char *str;
    static char buf[26];

    time(&now);

    str = ctime(&timeVal);

    strcpy(buf, &str[4]);
    buf[12] = '\0';

    if ((timeVal > now) || (timeVal < now - 365 * 24 * 60 * 60L)) {
	strcpy(&buf[7], &str[20]);
	buf[11] = '\0';
    }

    return buf;
}


/*
 * Routine to see if a text string is matched by a wildcard pattern.
 * Returns TRUE if the text is matched, or FALSE if it is not matched
 * or if the pattern is invalid.
 *  *		matches zero or more characters
 *  ?		matches a single character
 *  [abc]	matches 'a', 'b' or 'c'
 *  \c		quotes character c
 *  Adapted from code written by Ingo Wilken.
 */
int match(const char *text, const char *pattern)
{
    const char *retryPat;
    const char *retryText;
    int ch;
    int found;

    retryPat = NULL;
    retryText = NULL;

    while (*text || *pattern) {
	ch = *pattern++;

	switch (ch) {
	case '*':
	    retryPat = pattern;
	    retryText = text;
	    break;

	case '[':
	    found = FALSE;

	    while ((ch = *pattern++) != ']') {
		if (ch == '\\')
		    ch = *pattern++;

		if (ch == '\0')
		    return FALSE;

		if (*text == ch)
		    found = TRUE;
	    }

	    if (!found) {
		pattern = retryPat;
		text = ++retryText;
	    }

	    /* fall into next case */

	case '?':
	    if (*text++ == '\0')
		return FALSE;

	    break;

	case '\\':
	    ch = *pattern++;

	    if (ch == '\0')
		return FALSE;

	    /* fall into next case */

	default:
	    if (*text == ch) {
		if (*text)
		    text++;
		break;
	    }

	    if (*text) {
		pattern = retryPat;
		text = ++retryText;
		break;
	    }

	    return FALSE;
	}

	if (pattern == NULL)
	    return FALSE;
    }

    return TRUE;
}


/*
 * Write all of the supplied buffer out to a file.
 * This does multiple writes as necessary.
 * Returns the amount written, or -1 on an error.
 */
int fullWrite(int fd, const char *buf, int len)
{
    int cc;
    int total;

    total = 0;

    while (len > 0) {
	cc = write(fd, buf, len);

	if (cc < 0)
	    return -1;

	buf += cc;
	total += cc;
	len -= cc;
    }

    return total;
}


/*
 * Read all of the supplied buffer from a file.
 * This does multiple reads as necessary.
 * Returns the amount read, or -1 on an error.
 * A short read is returned on an end of file.
 */
int fullRead(int fd, char *buf, int len)
{
    int cc;
    int total;

    total = 0;

    while (len > 0) {
	cc = read(fd, buf, len);

	if (cc < 0)
	    return -1;

	if (cc == 0)
	    break;

	buf += cc;
	total += cc;
	len -= cc;
    }

    return total;
}
#endif


#if defined (BB_CHOWN) || defined (BB_CP) || defined (BB_FIND) || defined (BB_LS)
/*
 * Walk down all the directories under the specified 
 * location, and do something (something specified
 * by the fileAction and dirAction function pointers).
 */
int
recursiveAction(const char *fileName, int recurse, int followLinks,
		int (*fileAction) (const char *fileName),
		int (*dirAction) (const char *fileName))
{
    int status;
    struct stat statbuf;
    struct dirent *next;

    if (followLinks)
	status = lstat(fileName, &statbuf);
    else
	status = stat(fileName, &statbuf);
    if (status < 0) {
	perror(fileName);
	return (FALSE);
    }

    if (recurse == FALSE) {
	if (S_ISDIR(statbuf.st_mode)) {
	    if (dirAction == NULL)
		return (TRUE);
	    else
		return (dirAction(fileName));
	} else {
	    if (fileAction == NULL)
		return (TRUE);
	    else
		return (fileAction(fileName));
	}
    }

    if (S_ISDIR(statbuf.st_mode)) {
	DIR *dir;
	dir = opendir(fileName);
	if (!dir) {
	    perror(fileName);
	    return (FALSE);
	}
	if (dirAction != NULL) {
	    status = dirAction(fileName);
	    if (status == FALSE) {
		perror(fileName);
		return (FALSE);
	    }
	}
	while ((next = readdir(dir)) != NULL) {
	    char nextFile[NAME_MAX];
	    if ((strcmp(next->d_name, "..") == 0)
		|| (strcmp(next->d_name, ".") == 0)) {
		continue;
	    }
	    sprintf(nextFile, "%s/%s", fileName, next->d_name);
	    status =
		recursiveAction(nextFile, TRUE, followLinks, fileAction,
				dirAction);
	    if (status < 0) {
		closedir(dir);
		return (FALSE);
	    }
	}
	status = closedir(dir);
	if (status < 0) {
	    perror(fileName);
	    return (FALSE);
	}
    } else {
	if (fileAction == NULL)
	    return (TRUE);
	else
	    return (fileAction(fileName));
    }
    return (TRUE);
}

#endif



#if defined (BB_TAR) || defined (BB_MKDIR)
/*
 * Attempt to create the directories along the specified path, except for
 * the final component.  The mode is given for the final directory only,
 * while all previous ones get default protections.  Errors are not reported
 * here, as failures to restore files can be reported later.
 */
extern void createPath (const char *name, int mode)
{
    char *cp;
    char *cpOld;
    char buf[NAME_MAX];

    strcpy (buf, name);

    cp = strchr (buf, '/');

    while (cp) {
	cpOld = cp;
	cp = strchr (cp + 1, '/');

	*cpOld = '\0';

	if (mkdir (buf, cp ? 0777 : mode) == 0)
	    printf ("Directory \"%s\" created\n", buf);

	*cpOld = '/';
    }
}
#endif



#if defined (BB_CHMOD_CHOWN_CHGRP) || defined (BB_MKDIR)
/* [ugoa]{+|-|=}[rwxstl] */
extern int parse_mode( const char* s, mode_t* theMode)
{
	mode_t or;
	mode_t and;
	mode_t	mode = 0;
	mode_t	groups = S_ISVTX;
	char	type;
	char	c;

	do {
		for ( ; ; ) {
			switch ( c = *s++ ) {
			case '\0':
				return (FALSE);
			case 'u':
				groups |= S_ISUID|S_IRWXU;
				continue;
			case 'g':
				groups |= S_ISGID|S_IRWXG;
				continue;
			case 'o':
				groups |= S_IRWXO;
				continue;
			case 'a':
				groups |= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
				continue;
			case '+':
			case '=':
			case '-':
				type = c;
				if ( groups == S_ISVTX ) /* The default is "all" */
					groups |= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
				break;
			default:
				if ( c >= '0' && c <= '7' && mode == 0 && groups == S_ISVTX ) {
					and = 0;
					or = strtol(--s, 0, 010);
					return (TRUE);
				}
				else
					return (FALSE);
			}
			break;
		}

		while ( (c = *s++) != '\0' ) {
			switch ( c ) {
			case ',':
				break;
			case 'r':
				mode |= S_IRUSR|S_IRGRP|S_IROTH;
				continue;
			case 'w':
				mode |= S_IWUSR|S_IWGRP|S_IWOTH;
				continue;
			case 'x':
				mode |= S_IXUSR|S_IXGRP|S_IXOTH;
				continue;
			case 's':
				mode |= S_IXGRP|S_ISUID|S_ISGID;
				continue;
			case 't':
				mode |= S_ISVTX;
				continue;
			default:
				return (FALSE);
			}
			break;
		}
		switch ( type ) {
		case '=':
			and &= ~(groups);
			/* fall through */
		case '+':
			or |= mode & groups;
			break;
		case '-':
			and &= ~(mode & groups);
			or &= and;
			break;
		}
	} while ( c == ',' );
	return (TRUE);
}
#endif

/* END CODE */