// -------------------------------------------------------------------------------------
// misc file utilities
// -------------------------------------------------------------------------------------
// Permission is granted to freely redistribute this source code, and to use fragments
// of this code in your own applications if you find them to be useful.  This class,
// along with the source code, come with no warranty of any kind, and the user assumes
// all responsibility for its use.
// -------------------------------------------------------------------------------------

#import <appkit/appkit.h>
#import <stdio.h>
#import <string.h>
#import <ctype.h>
#import <libc.h>
#import <pwd.h>
#import <mntent.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/param.h>
#import <sys/dir.h>
#import "fileUtils.h"

/* return file extension */
const char *XFileExtension(const char *file)
{
	char	*temp = rindex(file, '.');
	return temp? temp : "";
}

/* return file name/extension */
const char *XFileNameExtension(const char *file)
{
	char	*temp = rindex(file, '/');
	return temp? temp + 1 : file;
}

/* return true if file exists */
int XFileExists(const char *file)
{
	struct stat	st;
	return file && !stat((char*)file,&st)? 1 : 0;
}

/* return true if file exists */
int XFileIsDirectory(const char *file)
{
	struct stat	st;
	return file && !stat((char*)file,&st) && (st.st_mode & S_IFDIR)? 1 : 0;
}

/* return true if file exists */
int XFileIsLink(const char *file)
{
	struct stat	st;
	return file && !lstat((char*)file,&st) && ((st.st_mode & S_IFMT)==S_IFLNK)? 1 : 0;
}

/* return true if file exists */
char *XFileReadLink(const char *file, char *link)
{
	int	len;
	if (!XFileIsLink(file)) return (char*)NULL;
	if ((len = readlink(file, link, MAXPATHLEN)) >= 0) link[len] = 0;
	else *link = 0;
	return len > 0? link : (char*)NULL;
}

/* return a file size */
long XFileSize(const char *file)
{
	struct stat	st;
	return file && !stat((char*)file,&st)? st.st_size : -1L;
}

/* return current dir */
char *XCurrentDir(char *dir)
{
	return (!dir || !getwd(dir))? (char*)NULL : dir;
}

/* change dir */
int XChangeDir(char *path)
{
	return (!path || (chdir(path) < 0))? -1 : 0;
}

/* copy a file */
int XCopyFile(const char *fromName, const char *toName, int backup)
{
	int			fi = -1, fo = -1, c, stm, mode;
	char		b[10240], bku[MAXPATHLEN + 1];
	struct stat	st;

	/* get file mode of toFile */
	mode = ((stm = stat(toName, &st)) >= 0)? (st.st_mode & 0777) : 0644;
	
	/* file already exist? */
	if (backup && (stm >= 0)) {
		sprintf(bku, "%s~", toName);
		if (rename(toName, bku)) {
			NXLogError("copyFile: Unable to rename %s to %s", toName, bku);
			return -1;
		}
	}

	/* remove to-file */
	unlink(toName);

	/* open/copy/close */
	if (((fi = open(fromName, O_RDONLY)) >= 0) &&
		((fo = open(toName, O_WRONLY | O_CREAT, 0666)) >= 0)) {
		for (c = 0; !c && ((c = read(fi, b, sizeof(b))) > 0);) c -= write(fo, b, c);
	} else c = -1;
	if (fi >= 0) close(fi);
	if (fo >= 0) close(fo);
	
	/* restore backup if error */
	if (c && backup && rename(bku, toName)) {
		NXLogError("copyFile: Unable to restore backup %s after error", bku);
		return -2;
	}
	
	return c? -3: 0;
	
}

/* return server and path to specified file (eg "server:/xyz/file") */
char *XMountPath(char *path, char *mountPath)
{
	FILE			*mNum;
	struct mntent	*m;
	struct stat		stpath, stdir;
	char			savName[MAXPATHLEN + 1], savPath[MAXPATHLEN + 1];
	int				sc;

	/* init mountPath */
	if (mountPath != path) *mountPath = 0;

	/* make sure file exists */
	if (!path || !*path || (stat(path, &stpath) < 0)) return (char*)NULL;
	
	/* open mount table */
	if (!(mNum = setmntent(MOUNTED, "r"))) return (char*)NULL;

	/* find remote mount */
	*savName = *savPath = 0;
	for (sc = 0; m = getmntent(mNum); ) {
		int l;
		
		/* validation testing */
		if (!strcmp(m->mnt_type, MNTTYPE_IGNORE)) continue;		// ignored
		if (!strcmp(m->mnt_type, MNTTYPE_SWAP  )) continue;		// ignored
		if (stat(m->mnt_dir, &stdir) < 0) continue;				// can't stat
		if (stpath.st_dev != stdir.st_dev) continue;			// same device
		if (!(l = strlen(m->mnt_dir))) continue;				// empty dir string
		if (strncmp(m->mnt_dir, path, l)) continue;				// not equal
		
		/* save dir */
		if (!path[l] || (path[l] == '/')) {
			char *c = m->mnt_dir;
			int n = 0;
			for (; *c; c++) { if (*c == '/') n++; }
			if (!sc || (n > sc)) {
				sc = n;
				strcpy(savName, m->mnt_fsname);
				strcpy(savPath, path + l);
			}
		} else
		if ((l == 1) && (*m->mnt_dir == '/') && !sc) {
			strcpy(savName, m->mnt_fsname);
			strcpy(savPath, path);
		}
		
	}
	
	/* build mounted path */
	if (index(savName, ':')) {
		sprintf(mountPath, "%s%s", savName, savPath);
	} else {
		char *sName = strncmp(savName,"/dev/",5)? savName : savName + 5;
		char *sPath = *savPath? savPath: "/";
		sprintf(mountPath, "#%s:%s", sName, sPath);
	}
	
	return mountPath;
}

/* resolve all symbolic links in file path */
char *XResolveLinks(char *path, char *nPath)	// can do in-place conversion
{
	char wPath[MAXPATHLEN+1], *p, *t;

	/* init nPath */
	if (nPath != path) *nPath = 0;

	/* return if path not specified */
	if (!path || !*path) return (char*)NULL;
	
	/* create absolute path to file */
	if (*path == '/') strcpy(wPath, path); else
	if (*path == '~') {
		struct passwd *pw;
		char user[128], *p = path + 1;
		{ char *u = user; for (;*p && (*p != '/');) *u++ = *p++; *u = 0; }
		if (!(pw = *user? getpwnam(user) : getpwuid(getuid()))) return (char*)NULL;
		sprintf(wPath, "%s%s", pw->pw_dir, p);
	} else {
		char curdir[MAXPATHLEN + 1];
		if (!getwd(curdir)) return (char*)NULL;
		sprintf(wPath, "%s/%s", curdir, path);
	}

	/* resolve all symbolic links in path */
	for (p = wPath, t = nPath; ; *t++ = *p++) {
		if ((!*p || (*p == '/')) && (t > nPath)) {
			int l;
			struct stat st;
			char sl[MAXPATHLEN + 1], *cp;
			*t = 0;
			cp = rindex(nPath, '/');	// should never be null
			if ((lstat(nPath,&st) >= 0) && ((st.st_mode & S_IFMT) == S_IFLNK) &&
					((l = readlink(nPath, sl, sizeof(sl)-1)) > 0)) {
				sl[l] = 0;
				if (*sl == '/') {		// recursion to resolve new link
					XResolveLinks(sl, nPath);
				} else {
					*++cp = 0;
					strcpy(cp, sl);
					XResolveLinks(nPath, nPath);
				}
				t = nPath + strlen(nPath);
			} else
			if (!*(cp + 1)) {			// ignore duplicate '/'
				*(t = cp) = 0;
			} else
			if (!strcmp(cp, "/.")) {	// ignore self references
				*(t = cp) = 0;
			} else
			if (!strcmp(cp, "/..")) {	// back up path
				*(t = cp) = 0;
				if (cp = rindex(nPath, '/')) *(t = cp) = 0;
			}
		}
		if (!*p) break;
	}
	*t = 0;
	if (!*nPath) strcpy(nPath, "/");

	return nPath;
}
