/*
 * Utility routines.
 *
 * Copyright (C) tons of folks.  Tracking down who wrote what
 * isn't something I'm going to worry about...  If you wrote something
 * here, please feel free to acknowledge your work.
 *
 * 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>
#include <ctype.h>

/* volatile so gcc knows this is the enod of the line */
volatile void usage(const char *usage)
{
    fprintf(stderr, "Usage: %s\n", usage);
    exit(FALSE);
}


#if defined (BB_INIT) || defined (BB_PS)

/* Returns kernel version encoded as major*65536 + minor*256 + patch,
 * so, for example,  to check if the kernel is greater than 2.2.11:
 *	if (get_kernel_revision() <= 2*65536+2*256+11) { <stuff> }
 */
int
get_kernel_revision()
{
  FILE *file;
  int major=0, minor=0, patch=0;
  char* filename="/proc/sys/kernel/osrelease";

  file = fopen(filename,"r");
  if (file == NULL) {
    perror(filename);
    return( 0);
  }
  fscanf(file,"%d.%d.%d",&major,&minor,&patch);
  fclose(file);
  return major*65536 + minor*256 + patch;
}

#endif



#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;
    if (S_ISDIR(statBuf.st_mode))
	return TRUE;
    return(FALSE);
}


/*
 * 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.)
 *  -Erik Andersen
 */
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';
	link_size = symlink(link_val, destName);
	if (link_size != 0) {
	    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:
    perror(destName);
    close(rfd);
    close(wfd);

    return FALSE;
}
#endif



#ifdef BB_TAR
/*
 * 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';
    if (S_ISLNK(mode))
	buf[0] = 'l';
    if (S_ISSOCK(mode))
	buf[0] = 's';
    /*
     * 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;
}


/*
 * 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;
}


/*
 * 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).
 *
 * Unfortunatly, while nftw(3) could replace this and reduce 
 * code size a bit, nftw() wasn't supported before GNU libc 2.1, 
 * and so isn't sufficiently portable to take over...
 */
int
recursiveAction(const char *fileName, int recurse, int followLinks, int depthFirst,
		int (*fileAction) (const char *fileName, struct stat* statbuf),
		int (*dirAction) (const char *fileName, struct stat* statbuf))
{
    int status;
    struct stat statbuf;
    struct dirent *next;

    if (followLinks == FALSE)
	status = stat(fileName, &statbuf);
    else
	status = lstat(fileName, &statbuf);

    if (status < 0) {
	perror(fileName);
	return (FALSE);
    }

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

    if (S_ISDIR(statbuf.st_mode)) {
	DIR *dir;
	dir = opendir(fileName);
	if (!dir) {
	    perror(fileName);
	    return (FALSE);
	}
	if (dirAction != NULL && depthFirst == FALSE) {
	    status = dirAction(fileName, &statbuf);
	    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, depthFirst, 
			fileAction, dirAction);
	    if (status < 0) {
		closedir(dir);
		return (FALSE);
	    }
	}
	status = closedir(dir);
	if (status < 0) {
	    perror(fileName);
	    return (FALSE);
	}
	if (dirAction != NULL && depthFirst == TRUE) {
	    status = dirAction(fileName, &statbuf);
	    if (status == FALSE) {
		perror(fileName);
		return (FALSE);
	    }
	}
    } else {
	if (fileAction == NULL)
	    return (TRUE);
	else
	    return (fileAction(fileName, &statbuf));
    }
    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]{+|-|=}[rwxst] */



extern int 
parse_mode( const char* s, mode_t* theMode)
{
	mode_t andMode = S_ISVTX|S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
	mode_t orMode = 0;
	mode_t	mode = 0;
	mode_t	groups = 0;
	char	type;
	char	c;

	do {
		for ( ; ; ) {
			switch ( c = *s++ ) {
			case '\0':
				return -1;
			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 == 0 ) /* The default is "all" */
					groups |= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
				break;
			default:
				if ( isdigit(c) && c >= '0' && c <= '7' && mode == 0 && groups == 0 ) {
					andMode = 0;
					orMode = strtol(--s, NULL, 8);
					*theMode &= andMode;
					*theMode |= orMode;
					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 |= 0;
				continue;
			default:
				*theMode &= andMode;
				*theMode |= orMode;
				return( TRUE);
			}
			break;
		}
		switch ( type ) {
		case '=':
			andMode &= ~(groups);
			/* fall through */
		case '+':
			orMode |= mode & groups;
			break;
		case '-':
			andMode &= ~(mode & groups);
			orMode &= andMode;
			break;
		}
	} while ( c == ',' );
	*theMode &= andMode;
	*theMode |= orMode;
	return (TRUE);
}


#endif







#if defined (BB_CHMOD_CHOWN_CHGRP) || defined (BB_PS)

/* Use this to avoid needing the glibc NSS stuff 
 * This uses storage buf to hold things.
 * */
uid_t 
my_getid(const char *filename, char *name, uid_t id) 
{
	FILE *file;
	char *rname, *start, *end, buf[128];
	uid_t rid;

	file=fopen(filename,"r");
	if (file == NULL) {
	    perror(filename);
	    return (-1);
	}

	while (fgets (buf, 128, file) != NULL) {
		if (buf[0] == '#')
			continue;

		start = buf;
		end = strchr (start, ':');
		if (end == NULL)
			continue;
		*end = '\0';
		rname = start;

		start = end + 1;
		end = strchr (start, ':');
		if (end == NULL)
			continue;

		start = end + 1;
		rid = (uid_t) strtol (start, &end, 10);
		if (end == start)
			continue;

		if (name) {
		    if (0 == strcmp(rname, name))
			return( rid);
                }
		if ( id != -1 && id == rid ) {
		    strncpy(name, rname, 8);
		    return( TRUE);
		}
	}
	fclose(file);
	return (-1);
}

uid_t 
my_getpwnam(char *name) 
{
    return my_getid("/etc/passwd", name, -1);
}

gid_t 
my_getgrnam(char *name) 
{
    return my_getid("/etc/group", name, -1);
}

void
my_getpwuid(char* name, uid_t uid) 
{
    my_getid("/etc/passwd", name, uid);
}

void
my_getgrgid(char* group, gid_t gid) 
{
    my_getid("/etc/group", group, gid);
}


#endif



#if !defined BB_REGEXP && (defined BB_GREP || defined BB_FIND )  
/* This tries to find a needle in a haystack, but does so by
 * only trying to match literal strings (look 'ma, no regexps!)
 * This is short, sweet, and carries _very_ little baggage,
 * unlike its beefier cousin a few lines down...
 *  -Erik Andersen
 */
extern int find_match(char *haystack, char *needle, int ignoreCase)
{

    if (ignoreCase == FALSE) {
	haystack = strstr (haystack, needle);
	if (haystack == NULL)
	    return FALSE;
	return TRUE;
    } else {
	int i;
	char needle1[BUF_SIZE];
	char haystack1[BUF_SIZE];

	strncpy( haystack1, haystack, sizeof(haystack1));
	strncpy( needle1, needle, sizeof(needle1));
	for( i=0; i<sizeof(haystack1) && haystack1[i]; i++)
	    haystack1[i]=tolower( haystack1[i]);
	for( i=0; i<sizeof(needle1) && needle1[i]; i++)
	    needle1[i]=tolower( needle1[i]);
	haystack = strstr (haystack1, needle1);
	if (haystack == NULL)
	    return FALSE;
	return TRUE;
    }
}
#endif

/* END CODE */