/*
	paths.C - part of LyX project
	general path-mangling functions 
	Copyright (C) 1996 Ivan Schreter
	Parts Copyright (C) 1996 Dirk Niggemann
        Parts Copyright (C) 1985, 1990, 1993 Free Software Foundation, Inc.
*/

/* here are some support functions usable for other insets, too (maybe) */
/* quite useful for other stuff, too dabn100 */

#include "config.h"

#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include "forms.h"

#include "form1.h"
#include "paths.h"

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif
# if HAVE_NDIR_H
#  include <ndir.h>
# endif
#endif
 
#include <sys/stat.h>
#include <sys/time.h>
#include <pwd.h>
#include <grp.h>
#include "definitions.h"

//might want to put MAJOR_IN_MKDEV for SYSV

// amazing amounts of mangled stuff to get stats right 
// stolen shamelessly from the GNU fileutils

#if !S_IRUSR
# if S_IREAD
#  define S_IRUSR S_IREAD
# else
#  define S_IRUSR 00400
# endif
#endif

#if !S_IWUSR
# if S_IWRITE
#  define S_IWUSR S_IWRITE
# else
#  define S_IWUSR 00200
# endif
#endif

#if !S_IXUSR
# if S_IEXEC
#  define S_IXUSR S_IEXEC
# else
#  define S_IXUSR 00100
# endif
#endif

#ifdef STAT_MACROS_BROKEN
#undef S_ISBLK
#undef S_ISCHR
#undef S_ISDIR
#undef S_ISFIFO
#undef S_ISLNK
#undef S_ISMPB
#undef S_ISMPC
#undef S_ISNWK
#undef S_ISREG
#undef S_ISSOCK
#endif /* STAT_MACROS_BROKEN.  */

#if !defined(S_ISBLK) && defined(S_IFBLK)
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#endif
#if !defined(S_ISCHR) && defined(S_IFCHR)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#endif
#if !defined(S_ISDIR) && defined(S_IFDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#if !defined(S_ISREG) && defined(S_IFREG)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#if !defined(S_ISFIFO) && defined(S_IFIFO)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#endif
#if !defined(S_ISLNK) && defined(S_IFLNK)
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#endif
#if !defined(S_ISSOCK) && defined(S_IFSOCK)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#endif
#if !defined(S_ISMPB) && defined(S_IFMPB) /* V7 */
#define S_ISMPB(m) (((m) & S_IFMT) == S_IFMPB)
#define S_ISMPC(m) (((m) & S_IFMT) == S_IFMPC)
#endif
#if !defined(S_ISNWK) && defined(S_IFNWK) /* HP/UX */
#define S_ISNWK(m) (((m) & S_IFMT) == S_IFNWK)
#endif

// Since major is a function on SVR4, we can't use `ifndef major'. 
#ifdef MAJOR_IN_MKDEV
#include <sys/mkdev.h>
#define HAVE_MAJOR
#endif
#ifdef MAJOR_IN_SYSMACROS
#include <sys/sysmacros.h>
#define HAVE_MAJOR
#endif
#ifdef major			/* Might be defined in sys/types.h.  */
#define HAVE_MAJOR
#endif

#ifndef HAVE_MAJOR
#define major(dev)  (((dev) >> 8) & 0xff)
#define minor(dev)  ((dev) & 0xff)
#define makedev(maj, min)  (((maj) << 8) | (min))
#endif
#undef HAVE_MAJOR

extern char * system_lyxdir;
extern char * document_path;
extern  WriteAlert(const char* s1, const char* s2, const char* s3);


void WriteFSAlert(const char* s1, const char* s2, const char * s3)
{
   char * tmp = AddExtension (NULL, s2, s3);
   const char * er = strerror (errno);
   WriteAlert (s1, tmp, er);
   delete tmp;
}


// returns a pointer to right-sized buffer for cwd
// which you have to deallocate yourself 

char * safe_getcwd (void)
{
  	int n = 256;
	char * err;
  	char * tbuf = new char [n];
  	// safe. Hopefully all getcwds behave this way!
  	while (((err = getcwd (tbuf, n)) == NULL) && (errno == ERANGE)) {
    		delete tbuf;
    		n = 2*n;
    		tbuf = new char [n];
  	}
	if (err) err = PathCopy (tbuf);
	delete tbuf;
	return err;
}

// will perform automountd correction Real Soon Now
// right now, just checks for PWD and CWD as environment
// otherwise returns safe_getcwd
// NB this is _not_ a real getcwd.
// it just finds the cwd on which we started LyX
// otherwise like safe_getcwd
char * safer_getcwd (void)
{
  const char * p;
  p = getenv ("PWD");
  if (!p) p = getenv ("CWD");
  if (p) return PathCopy (p);
  else return safe_getcwd();
}


char * NameToPath(char *buf, const char *filename)
// strip filename from path name and puts path into buf
// create new buf if buf == NULL
{
	const char *end;
	char *p2;

	if (!filename) {
		if (buf) *buf = 0;
		return buf;
	}
	for (end = filename; *end ; end++);
	while (end > filename && *end != '/') --end;
	if (!buf) buf = new char [end - filename +1];
	p2 = buf; 
	while (end > filename) *p2++ = *filename++;
	*p2++ = 0;
	return buf;
}


char * MakePath(char *buf, const char *primname, const char *secname)
// strips filename from primname and puts resulting path into buf.
// If primname is empty or NULL, it'll do it on secname. If that is
// empty or null, it will use documentpath, if non-null, otherwise
// will make a '.'
// if buf == NULL, allocates buf
{
	if (primname && *primname) NameToPath(buf, primname);
	else if (secname && *secname) NameToPath(buf, secname);
	else if (document_path && *document_path) 
		if (buf) strcpy(buf, document_path);
		else buf = PathCopy (document_path);
	else {
		if (!buf) buf = new char [2];
		buf[0] = 0;
	      }
	if (!buf[0]) strcpy(buf, ".");
 	return buf;
}


char * MakeAbsPath(char *buf, const char *relpath, const char* basepath)
// If relpath is absolute, it'll just copy it. If relpath is empty or
// null, it will use the basepath if exists, and if not, then use '.'
// Otherwise, make absolute path based on basepath and put into buf.
// If buf == NULL, allocate a large enough buffer
// Not done efficiently yet....
{
	char buf1[256], *p1 = buf1, *p2;
	char bufp[256], *p, *pp = bufp;
	if (relpath[0] == '/') {
		// already absolute path
		if (!buf) return PathCopy (relpath);
		else return strcpy(buf, relpath);
	}

	strcpy(buf1, basepath);
	strcpy(bufp, relpath);
	// now process relpath
	while (*pp) {
		p = pp;
		while (*pp && *pp != '/') ++pp;
		if (*pp) *(pp++) = 0;
		// now p points to dir to process
		if (strcmp(p, ".") == 0) continue;	// nothing
		if (strcmp(p, "..") == 0) {
			// go up one directory
			p2 = p1 + strlen(p1) - 2;
			if (p2 < p1) p2 = p1;
			while (p2 > p1 && *p2 != '/') --p2;
			if (p2 > p1) *p2 = 0;
			else strcpy(p1, "/");		// root directory
		} else {
			// append directory
			if (*(p1 + strlen(p1) - 1) != '/') strcat(p1, "/");
			strcat(p1, p);
		}
	}
	if (!buf) return PathCopy (p1);
	else return strcpy(buf, p1);
	
}


char * MakeAbsPath2(char *buf, const char *relpath)
// make absolute path from current directory
//also allocates buf if buf == NULL
{
	char *buf2;
	if (relpath[0] == '/') {
		if (!buf) return PathCopy (relpath);
		else return strcpy(buf, relpath);
	}
	//will this always work? Unless something decides to chdir on us
        // yes. Think twice about using behind a chdir.    
	buf2 = safer_getcwd();
	buf = MakeAbsPath(buf, relpath, buf2);
	delete buf2;
   	return buf;
}


char * MakeRelPath(char *buf, const char *abspath, const char *basepath)
// makes relative path out of absolute path. If it is deeper than basepath,
// it's easy. If basepath and abspath share something (they are all deeper
// than some directory), it'll be rendered using ..'s. If they are completely
// different, then the absolute path will be used as relative path
// can now alocate a right-sized buffer by crude means if buf == NULL
// (not fast)
{
	const char *p;
	int i = 0, allobuf = 0;

	while (abspath[i] == basepath[i] && abspath[i] && basepath[i]) ++i;

	if (abspath[i] && basepath[i] || (abspath[i] != '/' && !basepath[i] &&
	    abspath[i]) || (basepath[i] != '/' && !abspath[i] && basepath[i]))
	{
		if (i) --i;	// here was the last match
		while (i && abspath[i] != '/') --i;
	}

	if (i == 0) {
		// actually no match - cannot make it relative
		if (!buf) return PathCopy (abspath);
		else return strcpy(buf, abspath);
	}
 	//cop-out. I should be doing a count somewhere.
	if (!buf) {
		buf = new char [500];
		allobuf = 1;
		}
	*buf = 0;

	// now count how many dirs there are in basepath above match
	// and append as many '..''s into relpath

	p = basepath + i;

	while (*p) {
		if (*p == '/') {
			if (!*(p+1)) break;
			strcat(buf, "../");
		}
		++p;
	}

	// now append relative stuff from common directory to abspath

	if (abspath[i] == '/') ++i;
	strcat(buf, abspath+i);
	if (buf[0] && buf[strlen(buf)-1] == '/') buf[strlen(buf)-1] = 0;
	if (!buf[0]) strcpy(buf, ".");
	if (allobuf) return PathCopy (buf); //shrink-to-fit
	else return buf;
}


char * AddName(char *buf, const char *path, const char *fname)
// correctly append filename to the pathname
// if pathname is '.', then don't use pathname
// this is now more elegant. deletes leading /'s from fname
// (chopping off any path). Appends nothing if fname has a trailing
// '/'   
// if buf == NULL create new buf;
{
 	int siz =0, cppath = 0, siz2;
	const char *cploop;
        char *cploop2;
	const char * fnamstart;
	fnamstart = strrchr (fname, '/');
	if (fnamstart) fname = fnamstart +1; //beyond last
	siz += strlen (fname) +1;
	if (strcmp(path, ".") != 0 && strcmp(path, "./") != 0) {
	   siz2 = strlen (path);
           siz += siz2; 
	   cppath = 1;
 	   if (path [siz2 -1] != '/') { siz++; cppath++; }
           }
	if (!buf) buf = new char [siz];
        cploop2 = buf; 
	if (cppath) { 
	  cploop = path; 
          while (*cploop) *cploop2++ = *cploop++;
	  if (cppath > 1) *cploop2++ = '/';
	}
	cploop = fname;
 	while (*cploop) *cploop2++ = *cploop++;
  	*cploop2++ = 0;
	return buf;
}


char * AddPath(char *buf, const char *path, const char *path2)
// correctly append sub-directory(ies) to the pathname
// if pathname is '.', then don't use pathname
// Deletes leading /'s from path2 
// deals with trailing /'s 
// this is very messy 'cause we have to do all calcs so we can 
// allocate buffer before doing the moves if buf doesn't exist
// sorry, but it means we are bomb-proofed from ever running out
// of allocation space. Unless you want a 192MB path or something.....
// I should do this to all the path manglers when I feel masochistic 
// enough ;-)
{
	const char *path2end, *cploop;
        char *cploop2;
	int siz =0;
	int siz2;
	int cppath = 0; // 0 no path, 1 path, 2 '/' + path 
 	path2end = path2 + strlen (path2) -1;
	while (*path2 == '/') path2++;
	while (*path2end == '/') path2end--;
	siz += (path2end - path2) +1 +2 ;   // +2 for trailing / and NUL
	if (strcmp(path, ".") != 0 && strcmp(path, "./") != 0) {
	   siz2 = strlen (path);
           siz += siz2; 
	   cppath = 1;
 	   if (path [siz2 -1] != '/') { siz++; cppath++; }
           }
	if (!buf) buf = new char [siz];
        cploop2 = buf; 
	if (cppath) { 
	  cploop = path; 
          while (*cploop) *cploop2++ = *cploop++;
	  if (cppath > 1)  *cploop2++ = '/';
	}
        cploop = path2;
 	while (cploop <= path2end) *cploop2++ = *cploop++;
        *cploop2++ = '/';
  	*cploop2++ = 0;
	return buf;
}


/* swaps extension on end of oldname with extension */
/* strips path off oldname if no_path == 1 */
/* if no extension, just appends. */
/* if newname == NULL create it. */
/* returns addr of newname */

char * ChangeExtension (char * newname, const char * oldname, const char * extension, int no_path) 
{ 
   int dot=0;
   int path = 0;
   int n=0;
   int n2=0;
   int i=0;
   int ext_len = 0;
   /* find the end of the string */ 
   for (n=0; oldname[n]; n++);
   
   /* go back to the first dot */ 
   for (n2=n; n2 && oldname[n2]!='.'
	&& oldname[n2]!='/'; n2--);
   
   /* when you find a dot, use this as extension */ 
   if (oldname[n2]=='.')
      dot = n2;
   else
      dot = n;
   if (no_path) {
     for (path=dot; path && oldname[path]!='/';path--);
     if (oldname[path]=='/')
       path++;
   } else path = 0;
   for (ext_len = 0; extension [ext_len]; ext_len++);
   if (newname == NULL) newname = new char[dot+ext_len];
   for (i=path; i<dot; i++) 
      newname[i-path] = oldname[i];
   for (n = 0; n < ext_len; n++) 
         newname[i+n-path] = extension[n];
   newname[i+n-path] = '\0';
   return newname;
}


// This is  a quick hack to create a strcat that can
// do newbuf = strcat (NULL, old1, old2) 
// or strcat (dest, old1, old2) where dest == old1 is allowed  
// either src1 or src2 or both can be NULL
char * AddExtension (char * dest, const char *src1, const char* src2)
{
	char * writep;

	if (!dest) {
		int siz =1;
		if (src1) siz += strlen(src1);
		if (src2) siz += strlen(src2);
		dest = new char [siz];
	}
	
	writep = dest;
	while (src1 && *src1) *writep++ = *src1++;
	while (src2 && *src2) *writep++ = *src2++;
	*writep++ = 0;
	return dest;
}


//dammit, I _hate_ dynamic allocation
//this is just to get a clean i/f for kfopen and LyXGetTextClass
char * MakeFullPath (const char * path, const char *dirs, 
		     const char * name, const char * ext)
{
        char *buf, *obuf;
	buf = AddPath (NULL, path, dirs);
	obuf = buf;
        buf = AddName (NULL, obuf, name);
	delete obuf;
	obuf = buf;
	if ((ext) && (*ext)) { 
 	  buf = AddExtension (NULL, obuf, ".");
	  delete obuf;
	  obuf = buf;
 	  buf = AddExtension (NULL, obuf, ext);
	  delete obuf;
	}
	return buf;
}


//PathPush - changes to directory path and stores old cwd in buf
// which it allocates internally.
// used internally now
// NON-REENTRANT!!
struct PathStack {
       	char * path;
	PathStack *next;
};


static PathStack * aPathStack = NULL;


void PathPush (const char *path)
{
  int err1 = 0;
  char *err;
  PathStack * p;
  //if (IsDirWriteable (path) < 1) 
  //	WriteAlert("Warning!", "This directory is not writeable!", path);
  p = new PathStack;
  err = safe_getcwd();
  p->path = err;
  p->next = aPathStack;
  aPathStack = p;
  if ((path) && (*path)) err1 = chdir (path);
  if ((err == NULL) || (err1)) {
    char * strer;
    strer = strerror (errno);
    //fprintf (stderr, "LyX internal error: could not change to directory: %s\n", strer);
    WriteAlert("LyX internal error:", "could not change to directory: ", strer);
  }
}


// changes back to old path and pops dir.
// deletes oldpath!!!
void PathPop (void)
{
  char *oldpath;
  PathStack *p;
  if (aPathStack) {
	oldpath = aPathStack->path;
	p = aPathStack->next;
	delete aPathStack;
	aPathStack = p;
  	if (chdir (oldpath)) {
    		char * strer;
    		strer = strerror (errno);
    		//wouldn't this have been nice?
    		WriteAlert("LyX internal error:", "could not change to directory: ", strer);
    		//fprintf (stderr, "LyX internal error: could not change to directory: %s\n", strer);
  	}
        delete oldpath;
  }
  else WriteAlert ("LyX internal error:", "Path Stack underflow.", "");
}


// MakeDisplayPath 
// creates a nice compact path for display in the buffer/lastfiles list 
char * MakeDisplayPath (char * buf, const char *path)
{
  char * relbuf;
  char * buf2;
  char *bufp;
  int l, l2, threshold = 30;
  const char * home = getenv ("HOME");
  relbuf = MakeRelPath (NULL, path, home);
  l = strlen (relbuf);
  if (*relbuf == '/') {
    bufp = strtok (relbuf, "/");
    buf2 = new char [l + 7];
    *buf2 = 0;
  } else {
    bufp = strtok (relbuf, "/");
    if (!strcmp (bufp, "..")) {
       //we don't want to go up again
       l = strlen (path);
       buf2 = new char [l +7];
       *buf2 = 0;
       //strcpy (relbuf, path); // cause a segfault ???
       relbuf = StringReplace(relbuf, path); // use this instead
       bufp = strtok (relbuf, "/");
    } else {
      //relative path
      buf2 = new char [l +7];
      strcpy (buf2, "...");
    }
  }
  if (l > threshold) strcat (buf2, "/...");
  while (bufp != NULL) {
    if ( (l > threshold) && ( (l2 = strlen (bufp) +1) < l) ) { 
       l -= l2;
    } else {
     strcat (buf2, "/");
     strcat (buf2, bufp);
    }
    bufp = strtok (NULL, "/");
  }

  if (buf) { 
    strcpy (buf, buf2);
    delete buf2;
    return buf;
  }
  else return buf2;
}


//uses a string formatted like the $PATH
//variable in bash to find a file to open
//bit stupoid right now. Can't cope with 
//pathnames with a ':' in them
//not much of a limitation, really, but
//we should cater for the most demented tastes
//returns dynamically-alloced chr arr to full path of 
//file;
//if path entry begins with $$LyX/, use system_lyxdir
char * FileOpenSearch (const char * cwd, const char *path, 
			const char *name, const char *ext)
{
  FILE * file; 
  char * real_file = NULL, * temp_path = NULL;
  const char * dir = cwd;
  char * tmppath = PathCopy (path);
  char * path_element = strtok (tmppath, ":");
  char * real_name;
  int notfound = 1;
  int makehidden = 0;

  while (notfound && path_element) {
    if ((strlen (path_element) >= 3) && !strncmp (path_element, "$$.", 3)) {
        //$$. turns makehidden on
	path_element += 3;
	makehidden = 1;
    }
    if ((strlen (path_element) >= 5) && !strncmp (path_element, "$$LyX", 5)) {
	//replace $$LyX  with system_lyxdir
	path_element += 5;
	// clear off leading /s 
	while (*path_element == '/') path_element++;
	dir = system_lyxdir;
    }
    temp_path = MakeAbsPath (NULL, path_element, dir);
    if (makehidden) {
       //see if there's a hidden version of it about
       real_name = AddExtension (NULL, ".", name);
       real_file = AddName (NULL, temp_path, real_name);
       delete real_name;
     }
     else real_file = AddName (NULL, temp_path, name);
    //if (makehidden) delete real_name;
    delete temp_path;
    if ((ext) && (*ext)) { 
       temp_path = AddExtension (NULL, real_file, ".");
       delete real_file;
       real_file = AddExtension (NULL, temp_path, ext);
       delete temp_path;
     }
    
    if (lyx_debug_level)
    fprintf(stderr,"LyX: trying to open `%s',\n", real_file);
    
    if (!(file = fopen(real_file, "r"))) {
	    if (lyx_debug_level)
		    fprintf(stderr,"LyX: unable to open `%s',\n", real_file);
        delete real_file;
	path_element = strtok (NULL, ":");
    }
    else {
	notfound = 0;
        fclose (file);
	}
  }
  if (notfound) return NULL;
  else return real_file;
}


//is a file read_only?
//return 1 read-write
//	 0 read_only
//	-1 error (doesn't exist, no access, anything else) 
int IsFileWriteable (const char * path)
{
    FILE * fp;
    fp = fopen (path, "r+");
    if (!fp) {
      if ((errno == EACCES) || (errno == EROFS)) {
        fp = fopen (path, "r");
        if (fp) {
	  fclose (fp);
          return 0;
	}
      }
      return -1;
    }
    fclose (fp);
    return 1;
}


//returns 1: dir writeable
//	  0: not writeable
//	 -1: error- couldn't fnd out
int IsDirWriteable (const char * path)
{
    FILE * fp;
    char * fullname = AddName (NULL, path, "lyx_tempXXXXXX");
    const char * tmpfl = mktemp (fullname);
    if (!tmpfl) {
		WriteAlert ("LyX Internal Error!", 
		"Could not test if directory is writeable", strerror (errno));
		delete fullname;
		return -1;
		}
    fp = fopen (tmpfl, "w+");
    if (!fp) {
       if (errno == EACCES) return 0;
       else { WriteAlert ("LyX internal Error!", 
		"Cannot open directory test file", strerror (errno));
	      delete fullname;
	      return -1;
	    }
    } else {
	if (remove (tmpfl)) {
		WriteAlert ("LyX Internal Error!", 
			"Created test file but cannot remove it?", strerror (errno));
		delete fullname;
 		return -1;
	}
        fclose (fp);
    }
    delete fullname;
    return 1;
}


static int DeleteAllFilesInDir (const char *path)
{
	DIR * dir;
	struct dirent *de;
	dir = opendir(path);
	if (!dir) {
		WriteFSAlert ("Error!", "Cannot open directory: ", path);
		return -1;
		}
	while ((de = readdir(dir))) {
	  if (StringEqual (de->d_name, ".") 
		|| StringEqual (de->d_name,"..")) continue; 
	  char * unlinkpath = AddName (NULL, path, de->d_name);
	  
	  if (lyx_debug_level)
	  fprintf (stderr, "Deleting file: %s\n", unlinkpath);

 	  if (remove (unlinkpath))
		  WriteFSAlert ("Error!", "Could not remove file: ",
				unlinkpath);
	  delete unlinkpath;
        }
	closedir (dir);
	return 0;
}


static char * CreateTmpDir (const char * tempdir, const char * mask)
{
    char * fullname = AddName (NULL, tempdir, mask);
    const char * tmpfl = mktemp (fullname);
    if ((!tmpfl) || mkdir (tmpfl, 0777)) {
	WriteFSAlert ("LyX Internal Error!", 
	"Could not create temporary directory in: ", tempdir);
	delete fullname;
	return NULL;
     }
     return fullname;
}


static int DestroyTmpDir (const char *tmpdir, bool Allfiles)
{
	if ((Allfiles) && (DeleteAllFilesInDir (tmpdir))) return -1;
	if ( rmdir(tmpdir) ) { WriteFSAlert ("LyX Internal Error!", 
		"Could not delete temporary directory", tmpdir);
		return -1;
	}
	return 0; 
} 


char * CreateBufferTmpDir (const char *pathfor)
{
  return CreateTmpDir (pathfor, "lyx_bufrtmpXXXXXX");
}


char * CreateLyXTmpDir (const char * deflt)
{
	if ((deflt) && (*deflt) && (!StringEqual (deflt, "/tmp"))) {
		if (mkdir (deflt, 0777)) { WriteFSAlert ("LyX Internal Error!", 
			"Could not create temporary directory in: ", deflt);
			return NULL;
		} else return PathCopy(deflt);
	} else {
		return CreateTmpDir ("/tmp", "lyx_tmpXXXXXX");
	}
}


int DestroyLyXTmpDir (const char * tmpdir)
{
       return DestroyTmpDir (tmpdir, false); // Why false?
}


int DestroyBufferTmpDir (const char * tmpdir)
{
       return DestroyTmpDir (tmpdir, true);
}
 


// helper functions for the file selector
// shamelessly purloined from the GNU fileutils

// first some declarations

struct userid
{
  union
    {
      uid_t u;
      gid_t g;
    } id;
  char *name;
  struct userid *next;
};

static struct userid *user_alist;

// The members of this list have names not in the local passwd file. 
static struct userid *nouser_alist;

// Use the same struct as for userids.
static struct userid *group_alist;
static struct userid *nogroup_alist;


char * getuser (uid_t uid);
uid_t *getuidbyname (char *user);
char *getgroup (gid_t gid);
gid_t *getgidbyname (char * group);

void mode_string (unsigned short mode, char *str);
static char ftypelet (long bits);
static void rwx (unsigned short bits, char *chars);
static void setst (unsigned short bits, char * chars);


// Translate UID to a login name or a stringified number,
//   with cache. 

char *getuser (uid_t uid)
{
  register struct userid *tail;
  struct passwd *pwent;
  char usernum_string[20];

  for (tail = user_alist; tail; tail = tail->next)
    if (tail->id.u == uid)
      return tail->name;

  pwent = getpwuid (uid);
  //tail = (struct userid *) malloc (sizeof (struct userid));
  tail = new userid;
  tail->id.u = uid;
  if (pwent == 0)
    {
      sprintf (usernum_string, "%u", (unsigned) uid);
      tail->name = strdup (usernum_string);
    }
  else
    tail->name = strdup (pwent->pw_name);

  // Add to the head of the list, so most recently used is first. 
  tail->next = user_alist;
  user_alist = tail;
  return tail->name;
}

//   Translate USER to a UID, with cache.
//   Return NULL if there is no such user.
//   (We also cache which user names have no passwd entry,
//   so we don't keep looking them up.)  

uid_t *getuidbyname (char *user)
{
  register struct userid *tail;
  struct passwd *pwent;

  for (tail = user_alist; tail; tail = tail->next)
    // Avoid a function call for the most common case.  
    if (*tail->name == *user && !strcmp (tail->name, user))
      return &tail->id.u;

  for (tail = nouser_alist; tail; tail = tail->next)
    // Avoid a function call for the most common case. 
    if (*tail->name == *user && !strcmp (tail->name, user))
      return 0;

  pwent = getpwnam (user);

  //tail = (struct userid *) malloc (sizeof (struct userid));
  tail = new userid;
  tail->name = strdup (user);

  // Add to the head of the list, so most recently used is first.
  if (pwent)
    {
      tail->id.u = pwent->pw_uid;
      tail->next = user_alist;
      user_alist = tail;
      return &tail->id.u;
    }

  tail->next = nouser_alist;
  nouser_alist = tail;
  return 0;
}


// Translate GID to a group name or a stringified number,
//   with cache. 

char *getgroup (gid_t gid)
{
  register struct userid *tail;
  struct group *grent;
  char groupnum_string[20];

  for (tail = group_alist; tail; tail = tail->next)
    if (tail->id.g == gid)
      return tail->name;

  grent = getgrgid (gid);
  //tail = (struct userid *) malloc (sizeof (struct userid));
  tail = new userid;
  tail->id.g = gid;
  if (grent == 0)
    {
      sprintf (groupnum_string, "%u", (unsigned int) gid);
      tail->name = strdup (groupnum_string);
    }
  else
    tail->name = strdup (grent->gr_name);

  // Add to the head of the list, so most recently used is first. 
  tail->next = group_alist;
  group_alist = tail;
  return tail->name;
}


// Translate GROUP to a UID, with cache.
//   Return NULL if there is no such group.
//   (We also cache which group names have no group entry,
//   so we don't keep looking them up.)  

gid_t *getgidbyname (char * group)
{
  register struct userid *tail;
  struct group *grent;

  for (tail = group_alist; tail; tail = tail->next)
    // Avoid a function call for the most common case. 
    if (*tail->name == *group && !strcmp (tail->name, group))
      return &tail->id.g;

  for (tail = nogroup_alist; tail; tail = tail->next)
    // Avoid a function call for the most common case. 
    if (*tail->name == *group && !strcmp (tail->name, group))
      return 0;

  grent = getgrnam (group);

  //tail = (struct userid *) malloc (sizeof (struct userid));
  tail = new userid;
  tail->name = strdup (group);

  // Add to the head of the list, so most recently used is first. 
  if (grent)
    {
      tail->id.g = grent->gr_gid;
      tail->next = group_alist;
      group_alist = tail;
      return &tail->id.g;
    }

  tail->next = nogroup_alist;
  nogroup_alist = tail;
  return 0;
}


// mode_string - fill in string STR with an ls-style ASCII
//   representation of the st_mode field of file stats block STATP.
//   10 characters are stored in STR; no terminating null is added.
//   The characters stored in STR are:
//
//   0	File type.  'd' for directory, 'c' for character
//	special, 'b' for block special, 'm' for multiplex,
//	'l' for symbolic link, 's' for socket, 'p' for fifo,
//	'-' for regular, '?' for any other file type
//
//   1	'r' if the owner may read, '-' otherwise.
//
//   2	'w' if the owner may write, '-' otherwise.
//
//   3	'x' if the owner may execute, 's' if the file is
//	set-user-id, '-' otherwise.
//	'S' if the file is set-user-id, but the execute
//	bit isn't set.
//
//   4	'r' if group members may read, '-' otherwise.
//
//   5	'w' if group members may write, '-' otherwise.
//
//   6	'x' if group members may execute, 's' if the file is
//	set-group-id, '-' otherwise.
//	'S' if it is set-group-id but not executable.
//
//   7	'r' if any user may read, '-' otherwise.
//
//   8	'w' if any user may write, '-' otherwise.
//
//   9	'x' if any user may execute, 't' if the file is "sticky"
//	(will be retained in swap space after execution), '-'
//	otherwise.
//	'T' if the file is sticky but not executable.  
//
//
void mode_string (unsigned short mode, char *str)
{
  str[0] = ftypelet ((long) mode);
  rwx ((mode & 0700) << 0, &str[1]);
  rwx ((mode & 0070) << 3, &str[4]);
  rwx ((mode & 0007) << 6, &str[7]);
  setst (mode, str);
}


// Return a character indicating the type of file described by
//   file mode BITS:
//   'd' for directories
//   'b' for block special files
//   'c' for character special files
//   'm' for multiplexor files
//   'l' for symbolic links
//   's' for sockets
//   'p' for fifos
//   '-' for regular files
//   '?' for any other file type.  

static char ftypelet (long bits)
{
#ifdef S_ISBLK
  if (S_ISBLK (bits))
    return 'b';
#endif
  if (S_ISCHR (bits))
    return 'c';
  if (S_ISDIR (bits))
    return 'd';
  if (S_ISREG (bits))
    return '-';
#ifdef S_ISFIFO
  if (S_ISFIFO (bits))
    return 'p';
#endif
#ifdef S_ISLNK
  if (S_ISLNK (bits))
    return 'l';
#endif
#ifdef S_ISSOCK
  if (S_ISSOCK (bits))
    return 's';
#endif
#ifdef S_ISMPC
  if (S_ISMPC (bits))
    return 'm';
#endif
#ifdef S_ISNWK
  if (S_ISNWK (bits))
    return 'n';
#endif
  return '?';
}


// return a character type indicator for mode
static char * type_indicator (unsigned int mode)
{
  if (S_ISDIR (mode))
    return ("/");

#ifdef S_ISLNK
  if (S_ISLNK (mode))
    return ("@");
#endif

#ifdef S_ISFIFO
  if (S_ISFIFO (mode))
    return ("|");
#endif

#ifdef S_ISSOCK
  if (S_ISSOCK (mode))
    return ("=");
#endif

  if (S_ISREG (mode) && (mode & (S_IEXEC | S_IXGRP | S_IXOTH)))
    return ("*");
  return "";
}

// Look at read, write, and execute bits in BITS and set
//   flags in CHARS accordingly.  

static void rwx (unsigned short bits, char *chars)
{
  chars[0] = (bits & S_IRUSR) ? 'r' : '-';
  chars[1] = (bits & S_IWUSR) ? 'w' : '-';
  chars[2] = (bits & S_IXUSR) ? 'x' : '-';
}

// Set the 's' and 't' flags in file attributes string CHARS,
//   according to the file mode BITS.  

static void setst (unsigned short bits, char * chars)
{
#ifdef S_ISUID
  if (bits & S_ISUID)
    {
      if (chars[3] != 'x')
	/* Set-uid, but not executable by owner.  */
	chars[3] = 'S';
      else
	chars[3] = 's';
    }
#endif
#ifdef S_ISGID
  if (bits & S_ISGID)
    {
      if (chars[6] != 'x')
	/* Set-gid, but not executable by group.  */
	chars[6] = 'S';
      else
	chars[6] = 's';
    }
#endif
#ifdef S_ISVTX
  if (bits & S_ISVTX)
    {
      if (chars[9] != 'x')
	/* Sticky, but not executable by others.  */
	chars[9] = 'T';
      else
	chars[9] = 't';
    }
#endif
}


// the file selector procedures
static FD_FileDlg *filedlg_form = NULL;
static char *fdlg_path1 = NULL;
static char *fdlg_path2 = NULL;
static char *fdlg_dir = NULL;
static char *fdlg_mask = NULL;
static char fdlg_fname[512];
static int fdlg_depth = 0;
static int lastsel;
static long lasttime;
static char * info_line  = NULL; 

struct entry_names {
       char *name;
       char *displayed;
       char *lsentry;
};

static entry_names * curnames =NULL;
static int numnames = 0;

static int compfselproc(const struct entry_names *r1, const struct entry_names *r2)
{
	bool r1d = (*(r1->name) && r1->name[strlen(r1->name)-1] == '/');
	bool r2d = (*(r2->name) && r2->name[strlen(r2->name)-1] == '/');

	if (r1d && !r2d) return -1;
	if (!r1d && r2d) return 1;
	return strcmp(r1->name, r2->name);
}


static void FSelReread()
// set up directory label and reread directory + show in the browser
{
	char *p, *pp, buf[256], dirbuf[1024],
	     prefix[80], postfix[80], midfix[80], *linkbuf2 = NULL;
	int i, cnt;
	DIR *dir;
	struct dirent *de;
	struct stat sb, sb2;
	struct entry_names *names;
	char modebuf [20];
	char timebuf [40];
	time_t current_time;

	// clear the present namelist
	if (curnames) {
		for (i = 0; i < numnames; i++) {
           	  if (curnames [i].name) delete curnames [i].name;
           	  if (curnames [i].displayed) delete curnames [i].displayed;
           	  if (curnames [i].lsentry) delete curnames [i].lsentry;
		}
		delete curnames;
	}
        current_time = time (NULL);

	dir = opendir(fdlg_dir);
	if (!dir) {
		char cwdbuf[1024];
		sprintf(cwdbuf, "Directory %s", fdlg_dir);
		// error - set old directory
		fl_show_alert("Warning", cwdbuf, "not found. Sorry.", 0);
		delete fdlg_dir;
		getcwd(cwdbuf, 1024);
		fdlg_dir = StringCopy(cwdbuf);
		dir = opendir(cwdbuf);
	}

	fl_hide_object(filedlg_form->List);
	fl_clear_browser(filedlg_form->List);

	fl_set_input(filedlg_form->DirBox, fdlg_dir);

	// now split that into directories and compute depth
	strcpy(dirbuf, fdlg_dir);
	p = dirbuf;
	fdlg_depth = 0;
	while (*p) {
		pp = p;
		while (*p && *p != '/') ++p;
		if (*p) *(p++) = 0;
		for (i = 0; i < fdlg_depth; ++i) buf[i] = ' ';
		strcpy(buf + fdlg_depth, pp);
		strcat(buf, "/");
		fdlg_depth++;
		fl_add_browser_line(filedlg_form->List, buf);
	}
	// split mask into prefix and postfix
	p = prefix;
	pp = fdlg_mask;
	while (*pp && *pp != '*') *(p++) = *(pp++);
	*p = 0;
	p = postfix;
	if (*pp) ++pp;
	while (*pp && *pp != '*') *(p++) = *(pp++);
	*p = 0;
	if (*pp) {
		strcpy(midfix, postfix);
		p = postfix;
		++pp;
		while (*pp) *(p++) = *(pp++);
		*p = 0;
	} else {
		midfix[0] = 0;
	}

	// now read and sort all directories and files found in this subdirectory
	cnt = 0;
	while ((de = readdir(dir))) ++cnt;	// count directory entrier
	rewinddir(dir);
	names = new struct entry_names [cnt];
	cnt = 0;
	while ((de = readdir(dir))) {
		int islink = 0, isdir = 0;
		// int isexec = 0; // unused
		if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..")
			== 0) continue;
		AddName(dirbuf, fdlg_dir, de->d_name);
		lstat(dirbuf, &sb);
		if ((sb.st_mode & S_IFLNK) == S_IFLNK) {
		     char linkbuf [500];
		     islink=1;
		     stat (dirbuf, &sb2);
		     int nread = readlink (dirbuf, linkbuf, 500);
		     linkbuf2 = new char [nread + 10];
		     strcpy (linkbuf2, " -> ");
		     if (nread > 0) strncpy (linkbuf2 +4, linkbuf, nread);
		     linkbuf2 [nread+4] = 0;
		     strcat (linkbuf2, type_indicator(sb2.st_mode)); 
                } 
		else sb2 = sb;
                if (((sb2.st_mode & S_IFREG) == S_IFREG) || 
			S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode)
			|| S_ISFIFO (sb.st_mode)) {
			// match regular exp
			if (prefix[0] && strncmp(de->d_name, prefix,
			     	strlen(prefix)) != 0) continue;
			if (midfix[0] && !strstr(de->d_name + strlen(
				prefix), midfix)) continue;
			if (postfix[0] && strlen(de->d_name) >= strlen(
				postfix) && strcmp(de->d_name + strlen(
				de->d_name) - strlen(postfix), postfix)
				!= 0) continue;    
                } else if ((sb2.st_mode & S_IFDIR) == S_IFDIR) { 
 			isdir = 1;
		} else continue;
		// create used name
		strcpy(buf, de->d_name);
		if (isdir) strcat(buf, "/");
		names [cnt].name = StringCopy(buf);
		// create displayed name
		strcpy(buf, de->d_name);
 		if (islink) strcat (buf, "@"); 
		strcat (buf, type_indicator (sb2.st_mode));
		names[cnt].displayed = StringCopy(buf);
		// create name for 'ls' style field
		mode_string (sb.st_mode, modebuf);
		modebuf [10] = 0;
                strcpy (timebuf, ctime (&sb.st_mtime));
                if (current_time > sb.st_mtime + 6L * 30L * 24L * 60L * 60L /* Old. */
	           || current_time < sb.st_mtime - 60L * 60L) /* In the future. */
	        {
	  		/* The file is fairly old or in the future.
	     		POSIX says the cutoff is 6 months old;
	     		approximate this by 6*30 days.
	     		Allow a 1 hour slop factor for what is considered "the future",
	     		to allow for NFS server/client clock disagreement.
	     		Show the year instead of the time of day.  */
	  		strcpy (timebuf + 11, timebuf + 19);
		}
      		timebuf[16] = 0;
		p = buf;
  		// The space between the mode and the number of links is the POSIX
     		// "optional alternate access method flag". 
  		sprintf (p, "%s %u ", modebuf, (unsigned int) sb.st_nlink);
  		p += strlen (p);
                sprintf (p, "%s ", getuser (sb.st_uid));
  		p += strlen (p);
		sprintf (p, "%s ", getgroup (sb.st_gid));
      		p += strlen (p);
  		sprintf (p, "%s ", timebuf + 4);
  		p += strlen (p);
                if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode))
     		   sprintf (p, "%u, %u ", (unsigned) major (sb.st_rdev),
	           (unsigned) minor (sb.st_rdev));
  		else
    		   sprintf (p, "%lu ", (unsigned long) sb.st_size);
  		p += strlen (p);
		strcat (buf, de->d_name);
		strcat (buf, type_indicator (sb.st_mode));
		if (islink) {
		   strcat (buf, linkbuf2);
		   delete linkbuf2;
		}
		names[cnt++].lsentry = StringCopy (buf);
 
	}
	closedir(dir);

	// now sort names and add them into directory box
	qsort(names, cnt, sizeof(entry_names), (int (*)(const void *, const void *))
		compfselproc);
	for (i = 0; i < fdlg_depth; ++i) buf[i] = ' ';
	for (i = 0; i < cnt; ++i) {
		strcpy(buf+fdlg_depth, names[i].displayed);
		fl_add_browser_line(filedlg_form->List, buf);
	}
	curnames = names;
	numnames = cnt;
	fl_show_object(filedlg_form->List);
	lastsel = -1;
}


static void FSelSetDir(const char *path)
{
	char tbf[1024], cwdbf[1024];

	if (fdlg_dir) {
		MakeAbsPath(tbf, path, fdlg_dir);
		MakeAbsPath2(cwdbf, tbf);
		delete fdlg_dir;
	} else {
		MakeAbsPath2(cwdbf, path);
	}
	fdlg_dir = StringCopy(cwdbf);
	FSelReread();
}


static void FSelSetMask(const char *mask)
{
	if (fdlg_mask) delete fdlg_mask;
	fdlg_mask = StringCopy(mask);
	fl_set_input(filedlg_form->PatBox, mask);
	FSelReread();
}

static void FSelSetInfoLine (const char *line)
{
 	if (info_line) delete info_line;
	info_line = StringCopy (line);
  	fl_set_object_label (filedlg_form->FileInfo, info_line);
}


const char *SelectFile(const char *title, const char *path,
		       const char *mask, const char* suggested)
// better file selection dialog
{
	bool ok;
	FL_OBJECT *obj;
	const char *p;
	struct timeval tv;
	long tm;
      	int sel;

	if (!filedlg_form) {
		filedlg_form = create_form_FileDlg();
		fl_hide_object(filedlg_form->User1);
		fl_hide_object(filedlg_form->User2);
	}
	if (!path) path = ".";
	if (!mask) mask = "*";
	if (!fdlg_dir || strcmp(fdlg_dir, path) != 0) {
		if (fdlg_mask) delete fdlg_mask;
		fdlg_mask = StringCopy(mask);
		fl_set_input(filedlg_form->PatBox, mask);
		FSelSetDir(path);
	} else if (!fdlg_mask || strcmp(mask, fdlg_mask) != 0) {
		FSelSetMask(mask);
	} else {
		fl_select_browser_line(filedlg_form->List, 1);
		fl_set_browser_topline(filedlg_form->List, 1);
	}
	FSelSetInfoLine ("");
	fl_set_input(filedlg_form->Filename, suggested);
	fl_deactivate_all_forms();
	fl_set_focus_object(filedlg_form->FileDlg, filedlg_form->Filename);
	fl_show_form(filedlg_form->FileDlg, FL_PLACE_MOUSE | FL_FREE_SIZE,
		FL_FULLBORDER, title);

	while (true) {
		ok = false;

		obj = fl_do_forms();

		if (obj == filedlg_form->Ready) {
			ok = true;
			break;
		}
		if (obj == filedlg_form->Cancel) {
			break;
		}
		if (obj == filedlg_form->List) {	// set fname to that from selection or chdir
			sel = fl_get_browser(filedlg_form->List);
			if (sel > fdlg_depth)  {
				p = curnames[sel-fdlg_depth -1].name;
	                        FSelSetInfoLine(curnames[sel-fdlg_depth -1].lsentry);
			}
                        else {
				p = fl_get_browser_line(filedlg_form->List, sel);
	                        FSelSetInfoLine("");
 			}
			gettimeofday(&tv, NULL);
			tm = tv.tv_sec * 1000 + tv.tv_usec/1000;
			// check for directory
			if (sel == lastsel && tm - lasttime < 500
			    && tm - lasttime > 80) {
				// doubleclick - confirmed
				ok = true;
			}
			lastsel = sel;
		       	lasttime = tm;
			if (p[strlen(p)-1] == '/') {
				// first count spaces
				int spc = 0, i;
				char buf[512];
				if (!ok) continue;
				while (*(p++) == ' ') ++spc;
				--p;
				if ((spc == fdlg_depth) || ((spc == 0) && (sel > fdlg_depth))) {
					strcpy(buf, (char *) fl_get_input(filedlg_form->DirBox));
					if (buf[strlen(buf)-1] != '/')
						strcat(buf, "/");
					strcat(buf, p);
				} else {
					// use first spc+1 items to form dirname
					buf[0] = 0;
					for (i = 0; i <= spc; ++i)
					     strcat(buf, fl_get_browser_line(
						filedlg_form->List, i+1) + i);
				}
				FSelSetDir(buf);
				continue;
			}
			fl_set_input(filedlg_form->Filename, p);
			if (ok) break;
			else continue;
		}
	}

	fl_hide_form(filedlg_form->FileDlg);
	fl_activate_all_forms();
        // This is still a bit dodgy. 
  	// We should completely ignore the user's 
	// attempts to input ../'s on the filename 
        // important for getcwd safety
	// should work now! user can only load files from 
	// current (visible) dir
	if (ok && fl_get_input(filedlg_form->Filename)[0]) {
		AddName(fdlg_fname, 
			(char *) fl_get_input(filedlg_form->DirBox), 
			(char *) fl_get_input(filedlg_form->Filename));

		return fdlg_fname;
	} else {
		return NULL;
	}
}


void SetFSelButton(int idx, const char *name, const char *path)
// set file selector button 0 or 1 to name that will generate path path
{
	FL_OBJECT *obj;
	char **p;

	if (!filedlg_form) {
		filedlg_form = create_form_FileDlg();
		fl_hide_object(filedlg_form->User1);
		fl_hide_object(filedlg_form->User2);
	}

	if (idx == 0) {
		obj = filedlg_form->User1;
		p = &fdlg_path1;
	} else if (idx == 1) {			
		obj = filedlg_form->User2;
		p = &fdlg_path2;
	} else return;

	if (name) {
		fl_set_object_label(obj, name);
		fl_show_object(obj);
		if (*p) delete *p;
		if (path) *p = StringCopy(path);
			else *p = NULL;
	} else {
		fl_hide_object(obj);
		if (*p) delete *p;
		*p = NULL;
	}
}


char * GetFSelDir (char *buf) 
{
   char *p;
   if (fdlg_dir) p = fdlg_dir;
   else p = "."; //cop-out. we could have chdir'd in between!!!
   if (buf) strcpy (buf, p);
   else buf = PathCopy (p);
   return buf;
}


void FileDlgCB(FL_OBJECT *, long arg)
{
	switch (arg) {
		case 10:	// rescan
			FSelReread();
			break;
		case 11:	// home
			FSelSetDir(getenv("HOME"));
			break;
		case 12:	// user 1
			if (fdlg_path1) FSelSetDir(fdlg_path1);
			break;
		case 13:	// user 2
			if (fdlg_path2) FSelSetDir(fdlg_path2);
			break;
		case 0:		// get directory
			FSelSetDir((char *) fl_get_input(filedlg_form->DirBox));
			break;
		case 1:		// get mask
			FSelSetMask((char *) fl_get_input(filedlg_form->PatBox));
			break;
		case 19:	// cancel
		case 20:	// ready
			fl_hide_form(filedlg_form->FileDlg);
			break;
	}
}
