// utf-8

/*	Copyright  (c)	Günter Woigk  2002-2003
  					mailto:kio@little-bat.de

	This file is free software

 	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.  

 	Permission to use, copy, modify, distribute, and sell this software and
 	its documentation for any purpose is hereby granted without fee, provided
 	that the above copyright notice appear in all copies and that both that
 	copyright notice and this permission notice appear in supporting
 	documentation, and that the name of the copyright holder not be used
 	in advertising or publicity pertaining to distribution of the software
 	without specific, written prior permission. The copyright holder makes no
 	representations about the suitability of this software for any purpose.
 	It is provided "as is" without express or implied warranty.

 	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 	PERFORMANCE OF THIS SOFTWARE.

	2003-07... kio	changes to use UCS4String library
	2003-08-09 kio	ReadDirectory(): mounted devices for all (?) Unixes
*/


#include "config.h"
#include <sys/param.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <dirent.h>
#include <fnmatch.h>
#if defined(_LINUX)
#include <sys/vfs.h>
#include "unix/os_utilities.h"
#endif
#include "SystemIO.h"
#include "var/var.h"





/* ==== files&folders ==========================================
*/

str ModeStr ( mode_t mode )
{
	char r[]="-r";
	char w[]="-w";
	char x[]="-x";
	char s[]="rwxrwxrwx";
	s[0] = r[(mode&S_IRUSR)!=0];
	s[1] = w[(mode&S_IWUSR)!=0];
	s[2] = x[(mode&S_IXUSR)!=0];
	s[3] = r[(mode&S_IRGRP)!=0];
	s[4] = w[(mode&S_IWGRP)!=0];
	s[5] = x[(mode&S_IXGRP)!=0];
	s[6] = r[(mode&S_IROTH)!=0];
	s[7] = w[(mode&S_IWOTH)!=0];
	s[8] = x[(mode&S_IXOTH)!=0];
	cstr z = mode&S_ISUID ? "u" : mode&S_ISGID ? "g" : "-";
	return CatStr(z,s);
}

static str EffModeStr ( mode_t mode, gid_t file_grp, uid_t file_usr )
{
	gid_t g = getegid();
	uid_t u = geteuid();
	cstr  z = mode&S_ISUID ? "u" : mode&S_ISGID ? "g" : "-";

// mix bits to 'other' bits:	
	if (g==file_grp) mode |= (mode>>3);
	if (u==file_usr) mode |= (mode>>6);
	if (u==0)        mode |= (mode>>6);		// prüfen: ist root==0?
	
	char r[]="-r";
	char w[]="-w";
	char x[]="-x";
	char s[]="rwx";
	s[0] = r[(mode&S_IROTH)!=0];
	s[1] = w[(mode&S_IWOTH)!=0];
	s[2] = x[(mode&S_IXOTH)!=0];
	return CatStr(z,s);
}



void RenameFileOrFolder ( cString& oldname, cString& newname )
{
	// caveat: if newname already exists it will be deleted/overwritten!
	if (NoError()) rename(FullPath(oldname.CString()),FullPath(newname.CString()));
}


String FullPath ( cString& path )
{
	if (path.Len()==0) return path;			// ""  is considered a full path name
	return FullPath(path.CString());
}


/* ----	working directory --------------------------------------
*/
void ChangeWorkingDir ( cstr path )
{
	chdir ( FullPath(path) );		// may set errno
}

cstr WorkingDirStr ( )
{	size_t n = 100;
	str s;
	do { s = getcwd ( QString(n), n*=2 ); } while( !s && n<9999 );
	return s;
}


void DeleteFile ( cstr path )
{
	remove(FullPath(path));	// may set errno
}


void CreateFile ( cstr path )
{
	if (Error()) return;
	path = FullPath(path);
	if (Error()) { if(Error()==ENOENT) ClearError(); else return; }

	int f = open(path,O_CREAT/*|O_NONBLOCK*/,0666);
	if (Error()) return;
	XXTRAP(f<0);
	if (ClassifyFile(f)==s_dir) SetError("can't replace directory with file");
	close(f);
}


void CreateDir ( cstr path )
{
	if (Error()) return;
	path = FullPath(path);
	if (Error()) { if(Error()==ENOENT) ClearError(); else return; }

	mkdir ( path, 0777 );
}


void SwapFiles ( cstr v, cstr w )
{
	if (Error()) return;
	XXTRAP(!(v&&*v&&w&&*w));
	if (LastChar(v)==dirsep) v = LeftStr(v,strlen(v)-1);	v = FullPath(v);
	if (LastChar(w)==dirsep) w = LeftStr(w,strlen(v)-1);	w = FullPath(w);

	cstr z = TempFileName(w);	XTRAP(Error());
	rename(v,z);				if (Error()) return;		// v and w can't be swapped.
	rename(w,v);				XTRAP(Error());			// it must be possible to rename z into v , if rename(v,z) worked.
	rename(z,w);				XTRAP(Error());			// must work, because this is guaranteed by TempFileName().
}


/*
void LogMountInfo ( bool wait=no )
{
	stream = stderr_stream;

	struct statfs* info;
#if defined(_BSD)	// exists for: freebsd, MACOSX
	int n = getmntinfo(&info, wait?MNT_WAIT:MNT_NOWAIT);
#else
	NIMP();
#endif
	
	Log(Using("\nMounted file systems: %d\n",n));
#if XXXLOG
	Log ( "type  flags bsize iosize   blocks    bfree   bavail    files    ffree              fsid owner          fstype                         mounton                       mountfrom\n" );
	Log ( "----- ----- ----- ------ -------- -------- -------- -------- -------- --------.-------- ----- --------------- ------------------------------- -------------------------------\n" );
#else
	Log ( "type  flags bsize iosize   blocks    bfree   bavail    files    ffree          fstype                         mounton                       mountfrom\n" );
	Log ( "----- ----- ----- ------ -------- -------- -------- -------- -------- --------------- ------------------------------- -------------------------------\n" );
#endif
	for (int i=0;i<n;i++)
	{	
		struct statfs& s = info[i];
		char flags[]="WXSD-";
		short f = s.f_flags;
		if (f&MNT_RDONLY)  		flags[0]='r';
		if (f&MNT_NOEXEC)  		flags[1]='-';
		if (f&MNT_NOSUID)  		flags[2]='-';
		if (f&MNT_NODEV)   		flags[3]='-';
		if (f&MNT_UNION)   		flags[4]='U';
	//	if (f&MNT_SYNCHRONOUS)	flags[5]='Y';
		
		if (!XXLOG)
		{
			if ( s.f_files+s.f_ffree==0 ) continue;			// special: volfs, free outomount slots
			if ( SameStr(s.f_mntonname,"/dev") ) continue;	// devfs, fdesc
		}
#if XXXLOG		
		Log ( Using( "$%04hx %s %5ld %6ld %8ld %8ld %8ld %8ld %8ld %08lx.%08lx %5d %15s %31s %31s\n", 
			  s.f_type, flags, s.f_bsize, s.f_iosize, s.f_blocks, s.f_bfree, s.f_bavail, s.f_files, s.f_ffree, s.f_fsid, s.f_owner, 
			  s.f_fstypename, s.f_mntonname, s.f_mntfromname ) );
#else
		Log ( Using( "$%04hx %s %5ld %6ld %8ld %8ld %8ld %8ld %8ld %15s %31s %31s\n", 
			  s.f_type, flags, s.f_bsize, s.f_iosize, s.f_blocks, s.f_bfree, s.f_bavail, s.f_files, s.f_ffree,
			  s.f_fstypename, s.f_mntonname, s.f_mntfromname ) );
#endif
	}
	LogNL();
}
*/

ulong VolumeFlags ( cstr path )
{
	bool wprot   = 1;	// 1
	bool ejectbl = 0;	// 2
	bool mounted = 0;	// 4

#if !defined(_SOLARIS)
	struct statfs fs;
	int n = statfs(FullPath(path),&fs); 
	if (n) return 1+0+0;	// errno set
#endif

#if defined(_LINUX) || defined(_SOLARIS)
	wprot  =0;
	ejectbl=0;
	mounted=1;
	XLog("\n*** SystemIO.cp:\n*** VolumeFlags(): not yet implemented for your platform.\n");
#else
	short f = fs.f_flags;
	wprot   = f&MNT_RDONLY;
    ejectbl = (f&MNT_NOSUID) && (f&MNT_NODEV);
	mounted = fs.f_blocks>0;
#endif

	return (wprot<<0)+(ejectbl<<1)+(mounted<<2);
}


double VolumeFree ( cstr path )
{
#if defined(_SOLARIS)
	Log("\n*** SystemIO.cp:\n*** VolumeFree(): not yet implemented for your platform.\n");
	return 0.0;
#else
	struct statfs fs;
	int n = statfs(FullPath(path),&fs);
	if (n) return 0;		// errno set
	return (double)fs.f_bsize * (double)fs.f_bavail;
#endif
}


/* ----	load binary file into string variable -------------------
		v is made a string even if load failed
*/
void LoadFile ( cString& filename, Var& v )
{
	FILE* f = fopen(FullPath(CString(filename)),"r");
	if (!f) return;		// errno should be set
		
	ulong len = FileLength(f);
	v = String(len,ucs1);
	ulong n = fread ( v.Text().Text(), 1, len, f );
	if (n<len) SetError(ferror(f));
	fclose(f);
}


/* ----	save string variable into binary file -------------------
*/
void SaveFile ( cString& filename, Var& v )
{
	XXTRAP(!v.IsText());
	FILE* f = fopen(FullPath(CString(filename)),"w");
	if (!f) return;		// errno should be set
		
	ulong n = fwrite ( v.Text().Text(), 1, v.Text().Len(), f );
	if (n<(ulong)v.Text().Len()) SetError(ferror(f));
	fclose(f);
}



/* ----	read directory into vektor variable -------------------
		v is made a vektor even if load failed
		v is shrinked to fit
*/
void ReadDirectory ( cString& path, Var& v )
{
	XXXTRAP ( !&v );
	v.SetType(isList);
	if (Error()) return;

	Var tmpl(isList);
	tmpl[0].SetName("fname");		// "name" is a token...
	tmpl[1].SetName("flags");
	tmpl[2].SetName("length");
	tmpl[3].SetName("atime");		// last access
	tmpl[4].SetName("mtime");		// last modification on content
	tmpl[5].SetName("ctime");		// last modification on control info
	tmpl.ResizeList(6);

	if (path.Len()==0)
	{
//	 	''root''  =>  mounted file systems
//		tmpl[0].SetName("fname");			// "/mount/point/"
//		tmpl[1].SetName("flags");			// "drwx" ... "d---"
//		tmpl[2].SetName("length");			// number of files&dirs in root directory of mounted device
//		tmpl[3].SetName("atime");			// number: st_atimespec.tv_sec		((last access time))
//		tmpl[4].SetName("mtime");			// number: st_mtimespec.tv_sec		((last modification time))
//		tmpl[5].SetName("ctime");			// number: st_ctimespec.tv_sec		((last modification time of meta info))
		tmpl[6].SetName("fs_type_id");		// number: statfs.f_type			((magic numbers for misc. fs types))
		tmpl[7].SetName("mountflags");		// number: BSD: statfs.f_flags; LINUX: /bin/mount
		tmpl[8].SetName("blocksize");		// number: statfs.f_bsize			((block size; best tfu))
		tmpl[9].SetName("iosize");			// number: BSD: statfs.f_iosize		((best tfu))
		tmpl[10].SetName("blocks");			// number: statfs.f_blocks			((blocks total))
		tmpl[11].SetName("blocksfree");		// number: statfs.f_bavail			((blocks free user))
		tmpl[12].SetName("blocksfree_su");	// number: statfs.f_bfree			((blocks free su))
		tmpl[13].SetName("files");			// number: statfs.f_files			((inodes total))
		tmpl[14].SetName("filesfree");		// number: statfs.f_ffree			((inodes free))
		tmpl[15].SetName("owner");			// ?       BSD: statfs.f_owner
		tmpl[16].SetName("fstype");			// text:   BSD: statfs.f_fstypename; LINUX: /bin/mount
		tmpl[17].SetName("mountpoint");		// text:   BSD: statfs.f_mntonname;  LINUX: /bin/mount
		tmpl[18].SetName("device");			// text:   BSD: statfs.f_mntfromname;LINUX: /bin/mount
		tmpl[19].SetName("bytes");			// number: blocksize * blocks
		tmpl[20].SetName("bytesfree");		// number: blocksize * blocksfree
		tmpl[21].SetName("bytesfree_su");	// number: blocksize * blocksfree_su

	#if defined(_BSD)
		struct statfs* info;
		int n = getmntinfo(&info, 1?MNT_WAIT:MNT_NOWAIT);
	#else
		str bu = ExecCmd("/bin/mount",NULL);		TRAP(!bu);
		Var mi = Var(String(bu)); delete[]bu;		// assumes UTF-8
		mi.SplitString(nl);							// split into lines[]
		mi.SplitString(' ');						// split into fields[][]
		// mi[i] = { device, "on", mountpoint, "type", fs_type, (flags) }
		int n = mi.ListSize();
	#endif

		for (int i=0;i<n;i++)
		{
		#if defined(_BSD)
			struct statfs& fs = info[i];						// file system info
			String mpath = fs.f_mntonname/*was: f_mntonname. typo?*/;						// utf-8
			String mdev  = fs.f_mntfromname;					// utf-8
			if ( fs.f_files+fs.f_ffree==0 ) continue;			// special: volfs, free automount slots
			if ( mpath=="/dev" ) continue;						// devfs, fdesc
		#else
			String mpath = mi[i][2].Text();
			String mdev  = mi[i][0].Text();
			struct statfs fs; if(statfs(mpath.CString(),&fs)) return;	// file system info
		#endif

			if ( mdev.Find('/')==-1 ) continue;					// "none", "usbdevfs", etc.

			struct stat d;										// mount dir info
			if (stat(mpath.CString(), &d)) return;				// error
			XXTRAP(!S_ISDIR(d.st_mode));

			DIR* D = opendir(mpath.CString()); if (!D) return;	// error
			int len; for(len=-2;readdir(D);len++) {}			// used inodes: count files&dirs
			closedir(D);

			char flags[] = "drwx";
		#if defined(_BSD)
			short f = fs.f_flags;
			if (f&MNT_RDONLY) flags[2]='-';
		#else
			// stat.st_mode		***TODO***
		#endif

			Var* w = new Var(tmpl); if (!w) { SetError(outofmemory); return; }
			v.AppendItem(w);
			(*w)[0] = mpath;
			(*w)[1] = flags;
			(*w)[2] = len;						// files&dirs in mount dir

		#if !defined(_POSIX_SOURCE) && !defined(_SOLARIS)
			(*w)[3] = d.st_atimespec.tv_sec;
			(*w)[4] = d.st_mtimespec.tv_sec;
			(*w)[5] = d.st_ctimespec.tv_sec;
		#else
			(*w)[3] = d.st_atime;
			(*w)[4] = d.st_mtime;
			(*w)[5] = d.st_ctime;
		#endif

			(*w)[6] = fs.f_type;

		#if defined(_BSD)
			(*w)[7] = fs.f_flags;
		#else
			(*w)[7] = "((not implemented))";
		//	***TODO*** ToNumber( mi[i][5] )
		#endif

			(*w)[8] = fs.f_bsize;

		#if defined(_BSD)
			(*w)[9] = fs.f_iosize;
		#else
			(*w)[9] = (*w)[8];
		#endif

			(*w)[10] = fs.f_blocks;
			(*w)[11] = fs.f_bavail;
			(*w)[12] = fs.f_bfree;
			(*w)[13] = fs.f_files;
			(*w)[14] = fs.f_ffree;

		#if defined(_BSD)
			(*w)[15] = fs.f_owner;
		#else
			(*w)[15] = "((unknown))";		// ***TODO*** /bin/ls
		#endif

		#if defined(_BSD)
			(*w)[16] = fs.f_fstypename;
		#else
			(*w)[16] = mi[i][4];
		#endif

			(*w)[17] = mpath;
			(*w)[18] = mdev;
			(*w)[19] = (double)fs.f_bsize * fs.f_blocks;
			(*w)[20] = (double)fs.f_bsize * fs.f_bavail;
			(*w)[20] = (double)fs.f_bsize * fs.f_bfree;
		}
	}

	else	// path != ""
	{
	// 	=>	file or directory
	//	=>	v.name     = "file_or_subdirectory_name"
	//		v.flags    = "drwx" ... "----"
	//		v.length   = file length or files in sub directory
	//		v.atime	   = last access t_time
	//		v.mtime	   = last modification t_time
	//		v.ctime	   = last modification t_time to meta data

		cstr dirpath = FullPath(CString(path));
		cstr pattern = NULL;
		if (LastChar(dirpath)!=dirsep)
		{
			if (ClassifyFile(dirpath)==s_dir)
			{
				dirpath = Using("%s%c",dirpath,dirsep);
			}
			else
			{
				pattern = FileNameFromPath(dirpath);
				dirpath = DirectoryPathFromPath(dirpath);
				ClearError();
			}
		}
		DIR* dir = opendir(dirpath);  if (!dir) return; 	// error
		struct dirent* direntry;
		readdir(dir);	// skip ..
		readdir(dir);	// skip .

		dirpath = NewCopy(dirpath);		// make durable
		pattern = NewCopy(pattern);		// make durable

		while ( (direntry=readdir(dir)) && NoError() )
		{
			cstr filename = direntry->d_name;
			if (pattern&&fnmatch(pattern,filename,FNM_NOESCAPE)) continue;
			cstr filepath = CatStr(dirpath,filename);
			struct stat filestat;
			if(stat(filepath,&filestat)) break;		// error

			long n = filestat.st_size;
			if(S_ISDIR(filestat.st_mode))
			{
				if(LastChar(filename)!=dirsep) filename=Using("%s%c",filename,dirsep);
				DIR* D = opendir(filepath);
				if (D)
				{ 	for ( n=0-2; readdir(D); n++ ) {}	// count items in subdir
					closedir(D);
				}
				else ClearError();
			}

			char* flags = EffModeStr(filestat.st_mode,filestat.st_gid,filestat.st_uid);
			if(S_ISDIR (filestat.st_mode)) flags[0]='d';

			Var* w = new Var(tmpl); if (!w) { SetError(outofmemory); break; }
			v.AppendItem(w);
			(*w)[0] = filename;
			(*w)[1] = flags;
			(*w)[2] = n;		// files&dirs in mount dir
#if !defined(_POSIX_SOURCE) && !defined(_SOLARIS)
			(*w)[3] = filestat.st_atimespec.tv_sec;
			(*w)[4] = filestat.st_mtimespec.tv_sec;
			(*w)[5] = filestat.st_ctimespec.tv_sec;
#else
			(*w)[3] = filestat.st_atime;
			(*w)[4] = filestat.st_mtime;
			(*w)[5] = filestat.st_ctime;
#endif
		}

		delete[] pattern;
		delete[] dirpath;

		closedir(dir);
	}
}






