/* Some wrappers around operating-system specific stuff. 
 *
 * This file is copyright 2002 Jim Kent, but license is hereby
 * granted for all use - public, private or commercial. */

#include "common.h"
#include <dirent.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <pwd.h>
#include <termios.h>
#include "portable.h"
#include "portimpl.h"
#include <sys/wait.h>
#include <regex.h>
#include <utime.h>


static char const rcsid[] = "$Id: osunix.c,v 1.48 2010/06/03 05:14:39 kent Exp $";


off_t fileSize(char *pathname)
/* get file size for pathname. return -1 if not found */
{
struct stat mystat;
ZeroVar(&mystat);
if (stat(pathname,&mystat)==-1)
    {
    return -1;
    }
return mystat.st_size;
}


long clock1000()
/* A millisecond clock. */
{
struct timeval tv;
static long origSec;
gettimeofday(&tv, NULL);
if (origSec == 0)
    origSec = tv.tv_sec;
return (tv.tv_sec-origSec)*1000 + tv.tv_usec / 1000;
}

void sleep1000(int milli)
/* Sleep for given number of 1000ths of second */
{
if (milli > 0)
    {
    struct timeval tv;
    tv.tv_sec = milli/1000;
    tv.tv_usec = (milli%1000)*1000;
    select(0, NULL, NULL, NULL, &tv);
    }
}

long clock1()
/* A seconds clock. */
{
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec;
}

void uglyfBreak()
/* Go into debugger. */
{
static char *nullPt = NULL;
nullPt[0] = 0;
}

char *getCurrentDir()
/* Return current directory.  Abort if it fails. */
{
static char dir[PATH_LEN];

if (getcwd( dir, sizeof(dir) ) == NULL )
    errnoAbort("getCurrentDir: can't get current directory");
return dir;
}

void setCurrentDir(char *newDir)
/* Set current directory.  Abort if it fails. */
{
if (chdir(newDir) != 0)
    errnoAbort("setCurrentDir: can't to set current directory: %s", newDir);
}

boolean maybeSetCurrentDir(char *newDir)
/* Change directory, return FALSE (and set errno) if fail. */
{
return chdir(newDir) == 0;
}

struct slName *listDir(char *dir, char *pattern)
/* Return an alphabetized list of all files that match 
 * the wildcard pattern in directory. */
{
struct slName *list = NULL, *name;
struct dirent *de;
DIR *d;

if ((d = opendir(dir)) == NULL)
    return NULL;
while ((de = readdir(d)) != NULL)
    {
    char *fileName = de->d_name;
    if (differentString(fileName, ".") && differentString(fileName, ".."))
	{
	if (pattern == NULL || wildMatch(pattern, fileName))
	    {
	    name = newSlName(fileName);
	    slAddHead(&list, name);
	    }
	}
    }
closedir(d);
slNameSort(&list);
return list;
}

struct slName *listDirRegEx(char *dir, char *regEx, int flags)
/* Return an alphabetized list of all files that match 
 * the regular expression pattern in directory.
 * See REGCOMP(3) for flags (e.g. REG_ICASE)  */
{
struct slName *list = NULL, *name;
struct dirent *de;
DIR *d;
regex_t re;
int err = regcomp(&re, regEx, flags | REG_NOSUB);
if(err)
    errAbort("regcomp failed; err: %d", err);

if ((d = opendir(dir)) == NULL)
    return NULL;
while ((de = readdir(d)) != NULL)
    {
    char *fileName = de->d_name;
    if (differentString(fileName, ".") && differentString(fileName, ".."))
	{
	if (!regexec(&re, fileName, 0, NULL, 0))
	    {
	    name = newSlName(fileName);
	    slAddHead(&list, name);
	    }
	}
    }
closedir(d);
regfree(&re);
slNameSort(&list);
return list;
}

struct fileInfo *newFileInfo(char *name, off_t size, bool isDir, int statErrno, 
	time_t lastAccess)
/* Return a new fileInfo. */
{
int len = strlen(name);
struct fileInfo *fi = needMem(sizeof(*fi) + len);
fi->size = size;
fi->isDir = isDir;
fi->statErrno = statErrno;
fi->lastAccess = lastAccess;
strcpy(fi->name, name);
return fi;
}

int cmpFileInfo(const void *va, const void *vb)
/* Compare two fileInfo. */
{
const struct fileInfo *a = *((struct fileInfo **)va);
const struct fileInfo *b = *((struct fileInfo **)vb);
return strcmp(a->name, b->name);
}

boolean makeDir(char *dirName)
/* Make dir.  Returns TRUE on success.  Returns FALSE
 * if failed because directory exists.  Prints error
 * message and aborts on other error. */
{
int err;
if ((err = mkdir(dirName, 0777)) < 0)
    {
    if (errno != EEXIST)
	{
	perror("");
	errAbort("Couldn't make directory %s", dirName);
	}
    return FALSE;
    }
return TRUE;
}


struct fileInfo *listDirXExt(char *dir, char *pattern, boolean fullPath, boolean ignoreStatFailures)
/* Return list of files matching wildcard pattern with
 * extra info. If full path is true then the path will be
 * included in the name of each file. */
{
struct fileInfo *list = NULL, *el;
struct dirent *de;
DIR *d;
int dirNameSize = strlen(dir);
int fileNameOffset = dirNameSize+1;
char pathName[512];

if ((d = opendir(dir)) == NULL)
    return NULL;
memcpy(pathName, dir, dirNameSize);
pathName[dirNameSize] = '/';

while ((de = readdir(d)) != NULL)
    {
    char *fileName = de->d_name;
    if (differentString(fileName, ".") && differentString(fileName, ".."))
	{
	if (pattern == NULL || wildMatch(pattern, fileName))
	    {
	    struct stat st;
	    bool isDir = FALSE;
	    int statErrno = 0;
	    strcpy(pathName+fileNameOffset, fileName);
	    if (stat(pathName, &st) < 0)
		{
		if (ignoreStatFailures)
		    statErrno = errno;
		else
    		    errAbort("stat failed in listDirX");
		}
	    if (S_ISDIR(st.st_mode))
		isDir = TRUE;
	    if (fullPath)
		fileName = pathName;
	    el = newFileInfo(fileName, st.st_size, isDir, statErrno, st.st_atime);
	    slAddHead(&list, el);
	    }
	}
    }
closedir(d);
slSort(&list, cmpFileInfo);
return list;
}

struct fileInfo *listDirX(char *dir, char *pattern, boolean fullPath)
/* Return list of files matching wildcard pattern with
 * extra info. If full path is true then the path will be
 * included in the name of each file. */
{
return listDirXExt(dir, pattern, fullPath, FALSE);
}

time_t fileModTime(char *pathName)
/* Return file last modification time.  The units of
 * these may vary from OS to OS, but you can depend on
 * later files having a larger time. */
{
struct stat st;
if (stat(pathName, &st) < 0)
    errAbort("stat failed in fileModTime: %s", pathName);
return st.st_mtime;
}


char *getHost()
/* Return host name. */
{
static char *hostName = NULL;
static char buf[128];
if (hostName == NULL)
    {
    hostName = getenv("HTTP_HOST");
    if (hostName == NULL)
        {
	hostName = getenv("HOST");
	if (hostName == NULL)
	    {
	    if (hostName == NULL)
		{
		static struct utsname unamebuf;
		if (uname(&unamebuf) >= 0)
		    hostName = unamebuf.nodename;
		else
		    hostName = "unknown";
		}
	    }
        }
    strncpy(buf, hostName, sizeof(buf));
    chopSuffix(buf);
    hostName = buf;
    }
return hostName;
}

char *mysqlHost()
/* Return host computer on network for mySQL database. */
{
boolean gotIt = FALSE;
static char *host = NULL;
if (!gotIt)
    {
    static char hostBuf[128];
    gotIt = TRUE;
    if (fileExists("mysqlHost"))
	{
	return (host = firstWordInFile("mysqlHost", hostBuf, sizeof(hostBuf)));
	}
    else
	return (host = getenv("MYSQLHOST"));
    }
return host;
}

char *semiUniqName(char *base)
/* Figure out a name likely to be unique.
 * Name will have no periods.  Returns a static
 * buffer, so best to clone result unless using
 * immediately. */
{
int pid = getpid();
int num = time(NULL)&0xFFFFF;
char host[512];
strcpy(host, getHost());
char *s = strchr(host, '.');
if (s != NULL)
     *s = 0;
subChar(host, '-', '_');
subChar(host, ':', '_');
static char name[PATH_LEN];
safef(name, sizeof(name), "%s_%s_%x_%x",
	base, host, pid, num);
return name;
}

char *rTempName(char *dir, char *base, char *suffix)
/* Make a temp name that's almost certainly unique. */
{
char *x;
static char fileName[PATH_LEN];
int i;
for (i=0;;++i)
    {
    x = semiUniqName(base);
    safef(fileName, sizeof(fileName), "%s/%s%d%s",
    	dir, x, i, suffix);
    if (!fileExists(fileName))
        break;
    }
return fileName;
}

static void eatSlashSlashInPath(char *path)
/* Convert multiple // to single // */
{
char *s, *d;
s = d = path;
char c, lastC = 0;
while ((c = *s++) != 0)
    {
    if (c == '/' && lastC == c)
        continue;
    *d++ = c;
    lastC = c;
    }
*d = 0;
}

static void eatExcessDotDotInPath(char *path)
/* If there's a /.. in path take it out.  Turns 
 *      'this/long/../dir/file' to 'this/dir/file
 * and
 *      'this/../file' to 'file'  
 *
 * and
 *      'this/long/..' to 'this'
 * and
 *      'this/..' to  ''   
 * and
 *       /this/..' to '/' */
{
/* Take out each /../ individually */
for (;;)
    {
    /* Find first bit that needs to be taken out. */
    char *excess= strstr(path, "/../");
    char *excessEnd = excess+4;
    if (excess == NULL || excess == path)
        break;

    /* Look for a '/' before this */
    char *excessStart = matchingCharBeforeInLimits(path, excess, '/');
    if (excessStart == NULL) /* Preceding '/' not found */
         excessStart = path;
    else 
         excessStart += 1;
    strcpy(excessStart, excessEnd);
    }

/* Take out final /.. if any */
if (endsWith(path, "/.."))
    {
    if (!sameString(path, "/.."))  /* We don't want to turn this to blank. */
	{
	int len = strlen(path);
	char *excessStart = matchingCharBeforeInLimits(path, path+len-3, '/');
	if (excessStart == NULL) /* Preceding '/' not found */
	     excessStart = path;
	else 
	     excessStart += 1;
	*excessStart = 0;
	}
    }
}

char *simplifyPathToDir(char *path)
/* Return path with ~ and .. taken out.  Also any // or trailing /.   
 * freeMem result when done. */
{
/* Expand ~ if any with result in newPath */
char newPath[PATH_LEN];
int newLen = 0;
char *s = path;
if (*s == '~')
    {
    char *homeDir = getenv("HOME");
    if (homeDir == NULL)
        errAbort("No HOME environment var defined after ~ in simplifyPathToDir");
    ++s;
    if (*s == '/')  /*    ~/something      */
        {
	++s;
	safef(newPath, sizeof(newPath), "%s/", homeDir);
	}
    else            /*   ~something        */
	{
	safef(newPath, sizeof(newPath), "%s/../", homeDir);
	}
    newLen = strlen(newPath);
    }
int remainingLen  = strlen(s);
if (newLen + remainingLen >= sizeof(newPath))
    errAbort("path too big in simplifyPathToDir");
strcpy(newPath+newLen, s);

/* Remove //, .. and trailing / */
eatSlashSlashInPath(newPath);
eatExcessDotDotInPath(newPath);
int lastPos = strlen(newPath)-1;
if (lastPos > 0 && newPath[lastPos] == '/')
    newPath[lastPos] = 0;

return cloneString(newPath);
}

#ifdef DEBUG
void simplifyPathToDirSelfTest()
{
/* First test some cases which should remain the same. */
assert(sameString(simplifyPathToDir(""),""));
assert(sameString(simplifyPathToDir("a"),"a"));
assert(sameString(simplifyPathToDir("a/b"),"a/b"));
assert(sameString(simplifyPathToDir("/"),"/"));
assert(sameString(simplifyPathToDir("/.."),"/.."));
assert(sameString(simplifyPathToDir("/../a"),"/../a"));

/* Now test removing trailing slash. */
assert(sameString(simplifyPathToDir("a/"),"a"));
assert(sameString(simplifyPathToDir("a/b/"),"a/b"));

/* Test .. removal. */
assert(sameString(simplifyPathToDir("a/.."),""));
assert(sameString(simplifyPathToDir("a/../"),""));
assert(sameString(simplifyPathToDir("a/../b"),"b"));
assert(sameString(simplifyPathToDir("/a/.."),"/"));
assert(sameString(simplifyPathToDir("/a/../"),"/"));
assert(sameString(simplifyPathToDir("/a/../b"),"/b"));
assert(sameString(simplifyPathToDir("a/b/.."),"a"));
assert(sameString(simplifyPathToDir("a/b/../"),"a"));
assert(sameString(simplifyPathToDir("a/b/../c"),"a/c"));
assert(sameString(simplifyPathToDir("a/../b/../c"),"c"));
assert(sameString(simplifyPathToDir("a/../b/../c/.."),""));
assert(sameString(simplifyPathToDir("/a/../b/../c/.."),"/"));

/* Test // removal */
assert(sameString(simplifyPathToDir("//"),"/"));
assert(sameString(simplifyPathToDir("//../"),"/.."));
assert(sameString(simplifyPathToDir("a//b///c"),"a/b/c"));
assert(sameString(simplifyPathToDir("a/b///"),"a/b"));
}
#endif /* DEBUG */

char *getUser()
/* Get user name */
{
uid_t uid = geteuid();
struct passwd *pw = getpwuid(uid);
if (pw == NULL)
    errnoAbort("getUser: can't get user name for uid %d", (int)uid);
return pw->pw_name;
}

int mustFork()
/* Fork or abort. */
{
int childId = fork();
if (childId == -1)
    errnoAbort("mustFork: Unable to fork");
return childId;
}

int rawKeyIn()
/* Read in an unbuffered, unechoed character from keyboard. */
{
struct termios attr;
tcflag_t old;
char c;

/* Set terminal to non-echoing non-buffered state. */
if (tcgetattr(STDIN_FILENO, &attr) != 0)
    errAbort("Couldn't do tcgetattr");
old = attr.c_lflag;
attr.c_lflag &= ~ICANON;
attr.c_lflag &= ~ECHO;
if (tcsetattr(STDIN_FILENO, TCSANOW, &attr) == -1)
    errAbort("Couldn't do tcsetattr");

/* Read one byte */
if (read(STDIN_FILENO,&c,1) != 1)
   errnoAbort("rawKeyIn: I/O error");

/* Put back terminal to how it was. */
attr.c_lflag = old;
if (tcsetattr(STDIN_FILENO, TCSANOW, &attr) == -1)
    errAbort("Couldn't do tcsetattr2");
return c;
}

boolean isPipe(int fd)
/* determine in an open file is a pipe  */
{
struct stat buf;
if (fstat(fd, &buf) < 0)
    errnoAbort("isPipe: fstat failed");
return S_ISFIFO(buf.st_mode);
}

static void execPStack(pid_t ppid)
/* exec pstack on the specified pid */
{
char *cmd[3], pidStr[32];
safef(pidStr, sizeof(pidStr), "%ld", (long)ppid);
cmd[0] = "pstack";
cmd[1] = pidStr;
cmd[2] = NULL;

// redirect stdout to stderr
if (dup2(2, 1) < 0)
    errAbort("dup2 failed");

execvp(cmd[0], cmd);
errAbort("exec failed: %s", cmd[0]);
}

void vaDumpStack(char *format, va_list args)
/* debugging function to run the pstack program on the current process. In
 * prints a message, following by a new line, and then the stack track.  Just
 * prints errors to stderr rather than aborts. For debugging purposes
 * only.  */
{
static boolean inDumpStack = FALSE;  // don't allow re-entry if called from error handler
if (inDumpStack)
    return;
inDumpStack = TRUE;

fflush(stdout);  // clear buffer before forking
vfprintf(stderr, format, args);
fputc('\n', stderr);
fflush(stderr);
pid_t ppid = getpid();
pid_t pid = fork();
if (pid < 0)
    {
    perror("can't fork pstack");
    return;
    }
if (pid == 0)
    execPStack(ppid);
int wstat;
if (waitpid(pid, &wstat, 0) < 0)
    perror("waitpid on pstack failed");
else
    {
    if (WIFEXITED(wstat))
        {
        if (WEXITSTATUS(wstat) != 0)
            fprintf(stderr, "pstack failed\n");
        }
    else if (WIFSIGNALED(wstat))
        fprintf(stderr, "pstack signaled %d\n", WTERMSIG(wstat));
    }
inDumpStack = FALSE;
}

void dumpStack(char *format, ...)
/* debugging function to run the pstack program on the current process. In
 * prints a message, following by a new line, and then the stack track.  Just
 * prints errors to stderr rather than aborts. For debugging purposes
 * only.  */
{
va_list args;
va_start(args, format);
vaDumpStack(format, args);
va_end(args);
}

boolean maybeTouchFile(char *fileName)
/* If file exists, set its access and mod times to now.  If it doesn't exist, create it.
 * Return FALSE if we have a problem doing so (e.g. when qateam is gdb'ing and code tries 
 * to touch some file owned by www). */
{
if (fileExists(fileName))
    {
    struct utimbuf ut;
    ut.actime = ut.modtime = clock1();
    int ret = utime(fileName, &ut);
    if (ret != 0)
	{
	warn("utime(%s) failed (ownership?)", fileName);
	return FALSE;
	}
    }
else
    {
    FILE *f = fopen(fileName, "w");
    if (f == NULL)
	return FALSE;
    else
	carefulClose(&f);
    }
return TRUE;
}
