/*
 * win32.c
 * - utility functions for cvs under win32
 *
 */

#include <ctype.h>
#include <stdio.h>
#include <conio.h>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <config.h>
#include <winsock.h>
#include <stdlib.h>

#include "cvs.h"

static struct { DWORD err; int dos;} errors[] =
{
        {  ERROR_INVALID_FUNCTION,       EINVAL    },  /* 1 */
        {  ERROR_FILE_NOT_FOUND,         ENOENT    },  /* 2 */
        {  ERROR_PATH_NOT_FOUND,         ENOENT    },  /* 3 */
        {  ERROR_TOO_MANY_OPEN_FILES,    EMFILE    },  /* 4 */
        {  ERROR_ACCESS_DENIED,          EACCES    },  /* 5 */
        {  ERROR_INVALID_HANDLE,         EBADF     },  /* 6 */
        {  ERROR_ARENA_TRASHED,          ENOMEM    },  /* 7 */
        {  ERROR_NOT_ENOUGH_MEMORY,      ENOMEM    },  /* 8 */
        {  ERROR_INVALID_BLOCK,          ENOMEM    },  /* 9 */
        {  ERROR_BAD_ENVIRONMENT,        E2BIG     },  /* 10 */
        {  ERROR_BAD_FORMAT,             ENOEXEC   },  /* 11 */
        {  ERROR_INVALID_ACCESS,         EINVAL    },  /* 12 */
        {  ERROR_INVALID_DATA,           EINVAL    },  /* 13 */
        {  ERROR_INVALID_DRIVE,          ENOENT    },  /* 15 */
        {  ERROR_CURRENT_DIRECTORY,      EACCES    },  /* 16 */
        {  ERROR_NOT_SAME_DEVICE,        EXDEV     },  /* 17 */
        {  ERROR_NO_MORE_FILES,          ENOENT    },  /* 18 */
        {  ERROR_LOCK_VIOLATION,         EACCES    },  /* 33 */
        {  ERROR_BAD_NETPATH,            ENOENT    },  /* 53 */
        {  ERROR_NETWORK_ACCESS_DENIED,  EACCES    },  /* 65 */
        {  ERROR_BAD_NET_NAME,           ENOENT    },  /* 67 */
        {  ERROR_FILE_EXISTS,            EEXIST    },  /* 80 */
        {  ERROR_CANNOT_MAKE,            EACCES    },  /* 82 */
        {  ERROR_FAIL_I24,               EACCES    },  /* 83 */
        {  ERROR_INVALID_PARAMETER,      EINVAL    },  /* 87 */
        {  ERROR_NO_PROC_SLOTS,          EAGAIN    },  /* 89 */
        {  ERROR_DRIVE_LOCKED,           EACCES    },  /* 108 */
        {  ERROR_BROKEN_PIPE,            EPIPE     },  /* 109 */
        {  ERROR_DISK_FULL,              ENOSPC    },  /* 112 */
        {  ERROR_INVALID_TARGET_HANDLE,  EBADF     },  /* 114 */
        {  ERROR_INVALID_HANDLE,         EINVAL    },  /* 124 */
        {  ERROR_WAIT_NO_CHILDREN,       ECHILD    },  /* 128 */
        {  ERROR_CHILD_NOT_COMPLETE,     ECHILD    },  /* 129 */
        {  ERROR_DIRECT_ACCESS_HANDLE,   EBADF     },  /* 130 */
        {  ERROR_NEGATIVE_SEEK,          EINVAL    },  /* 131 */
        {  ERROR_SEEK_ON_DEVICE,         EACCES    },  /* 132 */
        {  ERROR_DIR_NOT_EMPTY,          ENOTEMPTY },  /* 145 */
        {  ERROR_NOT_LOCKED,             EACCES    },  /* 158 */
        {  ERROR_BAD_PATHNAME,           ENOENT    },  /* 161 */
        {  ERROR_MAX_THRDS_REACHED,      EAGAIN    },  /* 164 */
        {  ERROR_LOCK_FAILED,            EACCES    },  /* 167 */
        {  ERROR_ALREADY_EXISTS,         EEXIST    },  /* 183 */
        {  ERROR_FILENAME_EXCED_RANGE,   ENOENT    },  /* 206 */
        {  ERROR_NESTING_NOT_ALLOWED,    EAGAIN    },  /* 215 */
        {  ERROR_NOT_ENOUGH_QUOTA,       ENOMEM    }    /* 1816 */
};

void
init_winsock (int *argc, char ***argv)
{
    WSADATA data;

    if (WSAStartup (MAKEWORD (1, 1), &data))
    {
	fprintf (stderr, "cvs: unable to initialize winsock\n");
	exit (1);
    }
}

void
wnt_cleanup (void)
{
    if (WSACleanup ())
    {
#ifdef SERVER_ACTIVE
	if (server_active || error_use_protocol)
	    /* FIXME: how are we supposed to report errors?  As of now
	       (Sep 98), error() can in turn call us (if it is out of
	       memory) and in general is built on top of lots of
	       stuff.  */
	    ;
	else
#endif
	    fprintf (stderr, "cvs: cannot WSACleanup: %s\n",
		     sock_strerror (WSAGetLastError ()));
    }
}

unsigned sleep(unsigned seconds)
{
	Sleep(1000*seconds);
	return 0;
}

#if 0

/* WinSock has a gethostname.  But note that WinSock gethostname may
   want to talk to the network, which is kind of bogus in the
   non-client/server case.  I'm not sure I can think of any obvious
   solution.  Most of the ways I can think of to figure out whether
   to call gethostname or GetComputerName seem kind of kludgey, and/or
   might result in picking the name in a potentially confusing way
   (I'm not sure exactly how the name(s) are set).  */

int gethostname(char* name, int namelen)
{
	DWORD dw = namelen;
	BOOL ret = GetComputerName(name, &dw);
	namelen = dw;
	return (ret) ? 0 : -1;
}
#endif

char *win32getlogin()
{
    static char name[256];
    DWORD dw = 256;
    GetUserName (name, &dw);
    if (name[0] == '\0')
	return NULL;
    else
	return name;
}


pid_t
getpid ()
{
    return (pid_t) GetCurrentProcessId();
}

#ifdef CVSGUI_PIPE
#	undef getpass
#endif

char *
getpass (const char *prompt)
{
#ifndef CVSGUI_SHL
    static char pwd_buf[128];
    size_t i;

    fputs (prompt, stderr);
    fflush (stderr);
    for (i = 0; i < sizeof (pwd_buf) - 1; ++i)
    {
	pwd_buf[i] = _getch ();
	if (pwd_buf[i] == '\r')
	    break;
    }
    pwd_buf[i] = '\0';
    fputs ("\n", stderr);
    return pwd_buf;
#else /* CVSGUI_SHL */
	/* let WinCvs get the password */
	return getenv("CVS_GETPASS");
#endif /* CVSGUI_SHL */
}

static void _dosmaperr(DWORD dwErr)
{
	int n;
	for(n=0; n<sizeof(errors)/sizeof(errors[0]); n++)
	{
		if(errors[n].err==dwErr)
		{
			errno=errors[n].dos;
			return;
		}
	}
	errno=EFAULT;
}

/* FileTimeToUnixTime()
 *
 * Convert a file time to a Unix time_t structure. This function is as 
 * complicated as it is because it needs to ask what time system the 
 * filetime describes.
 *
 * INPUTS:
 *      const FILETIME * ft: A file time. It may be in UTC or in local 
 *                           time (see local_time, below, for details).
 *
 *      time_t * ut:         The destination for the converted time.
 *
 *      BOOL local_time:     TRUE if the time in *ft is in local time 
 *                           and I need to convert to a real UTC time.
 *
 * OUTPUTS:
 *      time_t * ut:         Store the result in *ut.
 *
 * NOTES:
 *
 * The root of the problem is this: When Windows reports a file time
 * it uses a FILETIME structure. If the file is on a UTC volume
 * (NTFS, HPFS), then ft is already in the proper time system and we
 * need only convert it from a FILETIME to a time_t.
 *
 * On the other hand, if ft comes from a file on a FAT or other file
 * system that uses local times, we need to convert it to UTC.
 *
 * One problem we face is that when Windows returns file times for
 * FAT files from functions such as FindFirstFile(), the times have 
 * been converted incorrectly to UTC so we need to unconvert them
 * with FileTimeToLocalFileTime() (we do this in _statcore), and
 * then do the conversion properly.
 *
 * Fortunately, mktime() makes the conversion properly, so long as we
 * set the tm_isdst flag correctly. mktime() can automatically determine
 * DST using United States rules, so for the purposes of this function,
 * I use US rules.
 *
 * TODO:, BUGS:
 *      Fix the DST determination to work for non-US rules.
 * 
 */
static void FileTimeToUnixTime ( const FILETIME* ft, time_t* ut, BOOL local_time )
{
    if ( local_time ) 
    {
        struct tm atm;
        SYSTEMTIME st;

        FileTimeToSystemTime ( ft, &st );

        /* Important: mktime looks at the tm_isdst field to determine
         * whether to apply the DST correction. If this field is zero,
         * then no DST is applied. If the field is one, then DST is
         * applied. If the field is minus one, then DST is applied
         * if the United States rule calls for it (DST starts at 
         * 02:00 on the first Sunday in April and ends at 02:00 on
         * the last Sunday in October.
         *
         * If you are concerned about time zones that follow different 
         * rules, then you must either use GetTimeZoneInformation() to 
         * get your system's TIME_ZONE_INFO and use the information
         * therein to figure out whether the time in question was in 
         * DST or not, or else use SystemTimeToTzSpecifiedLocalTime()
         * to do the same.
         *
         * I haven't tried playing with SystemTimeToTzSpecifiedLocalTime()
         * so I am nor sure how well it handles funky stuff.
         */
        atm.tm_sec = st.wSecond;
        atm.tm_min = st.wMinute;
        atm.tm_hour = st.wHour;
        atm.tm_mday = st.wDay;
        /* tm_mon is 0 based */
        atm.tm_mon = st.wMonth - 1;
        /* tm_year is 1900 based */
        atm.tm_year = st.wYear>1900?st.wYear - 1900:st.wYear;     
        atm.tm_isdst = -1;      /* see notes above */
        *ut = mktime ( &atm );
    }
    else 
    {

       /* FILETIME = number of 100-nanosecond ticks since midnight 
        * 1 Jan 1601 UTC. time_t = number of 1-second ticks since 
        * midnight 1 Jan 1970 UTC. To translate, we subtract a
        * FILETIME representation of midnight, 1 Jan 1970 from the
        * time in question and divide by the number of 100-ns ticks
        * in one second.
        */

        /* One second = 10,000,000 * 100 nsec */
        const ULONGLONG second = 10000000L;

        const SYSTEMTIME base_st = 
        {
            1970,   /* wYear            */
            1,      /* wMonth           */
            0,      /* wDayOfWeek       */
            1,      /* wDay             */
            0,      /* wHour            */
            0,      /* wMinute          */
            0,      /* wSecond          */
            0       /* wMilliseconds    */
        };
        
        ULARGE_INTEGER itime;
        FILETIME base_ft;

        SystemTimeToFileTime ( &base_st, &base_ft );
        itime.QuadPart = ((ULARGE_INTEGER *)ft)->QuadPart;

        itime.QuadPart -= ((ULARGE_INTEGER *)&base_ft)->QuadPart;
        itime.QuadPart /= second;

        *ut = itime.LowPart;
    }
}

static int _statcore(HANDLE hFile, const char *filename, struct stat *buf)
{
    BY_HANDLE_FILE_INFORMATION bhfi;
	int isdev;
	char szFs[32];
	int is_gmt_fs = -1;

	if(hFile)
	{
		/* Find out what kind of handle underlies filedes
		*/
		isdev = GetFileType(hFile) & ~FILE_TYPE_REMOTE;

		if ( isdev != FILE_TYPE_DISK )
		{
			/* not a disk file. probably a device or pipe
			 */
			if ( (isdev == FILE_TYPE_CHAR) || (isdev == FILE_TYPE_PIPE) )
			{
				/* treat pipes and devices similarly. no further info is
				 * available from any API, so set the fields as reasonably
				 * as possible and return.
				 */
				if ( isdev == FILE_TYPE_CHAR )
					buf->st_mode = _S_IFCHR;
				else
					buf->st_mode = _S_IFIFO;

				buf->st_rdev = buf->st_dev = (_dev_t)hFile;
				buf->st_nlink = 1;
				buf->st_uid = buf->st_gid = buf->st_ino = 0;
				buf->st_atime = buf->st_mtime = buf->st_ctime = 0;
				if ( isdev == FILE_TYPE_CHAR )
					buf->st_size = 0;
				else
				{
					unsigned long ulAvail;
					int rc;
					rc = PeekNamedPipe(hFile,NULL,0,NULL,&ulAvail,NULL);
					if (rc)
						buf->st_size = (_off_t)ulAvail;
					else 
						buf->st_size = (_off_t)0;
				}

				return 0;
			}
			else if ( isdev == FILE_TYPE_UNKNOWN )
			{
				// Network device
				errno = EBADF;
				return -1;
			}
		}
	}

    /* set the common fields
     */
    buf->st_ino = buf->st_uid = buf->st_gid = buf->st_mode = 0;
    buf->st_nlink = 1;

	if(hFile)
	{
		/* use the file handle to get all the info about the file
		 */
		if ( !GetFileInformationByHandle(hFile, &bhfi) )
		{
			_dosmaperr(GetLastError());
			return -1;
		}
	}
	else
	{
		WIN32_FIND_DATA fd;
		HANDLE hFind;
		
		if(strlen(filename)==3 && !stricmp(filename+1,":\\"))
		{
			// Can't do anything about the root directory...
			// Win2k and Win98 can return info, but Win95 can't, so
			// it's best to do nothing.
			memset(&bhfi,0,sizeof(bhfi));
			bhfi.dwFileAttributes=GetFileAttributes(filename);
			if(bhfi.dwFileAttributes==0xFFFFFFFF)
			{
				_dosmaperr(GetLastError());
				return -1;
			}
			bhfi.nNumberOfLinks=1;
		}
		else
		{
			hFind=FindFirstFile(filename,&fd);
			if(hFind==INVALID_HANDLE_VALUE)
			{
				_dosmaperr(GetLastError());
				return -1;
			}
			FindClose(hFind);
			memset(&bhfi,0,sizeof(bhfi));
			bhfi.dwFileAttributes=fd.dwFileAttributes;
			bhfi.ftCreationTime=fd.ftCreationTime;
			bhfi.ftLastAccessTime=fd.ftLastAccessTime;
			bhfi.ftLastWriteTime=fd.ftLastWriteTime;
			bhfi.nFileSizeLow=fd.nFileSizeLow;
			bhfi.nFileSizeHigh=fd.nFileSizeHigh;
			bhfi.nNumberOfLinks=1;
		}
	}

    if ( bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY )
        buf->st_mode |= (_S_IREAD + (_S_IREAD >> 3) + (_S_IREAD >> 6));
    else
        buf->st_mode |= ((_S_IREAD|_S_IWRITE) + ((_S_IREAD|_S_IWRITE) >> 3)
          + ((_S_IREAD|_S_IWRITE) >> 6));
	if (bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
        buf->st_mode |= (_S_IEXEC + (_S_IEXEC >> 3) + (_S_IEXEC >> 6));

	buf->st_nlink=bhfi.nNumberOfLinks;

	if(!filename)
	{
		// We have to assume here that the repository doesn't span drives, so the
		// current directory is correct.
		*szFs='\0';
		GetVolumeInformation(NULL,NULL,0,NULL,NULL,NULL,szFs,32);
		if(!stricmp(szFs,"NTFS"))
			is_gmt_fs = 1;
		else
			is_gmt_fs = 0;
	}
	else
	{
		if(filename[1]!=':')
		{
			if((filename[0]=='\\' || filename[0]=='/') && (filename[1]=='\\' || filename[1]=='/'))
			{
				// UNC pathname, assumed to be on an NTFS server.  No way to
				// check, actually (GVI doesn't return anything useful).
				is_gmt_fs = 1;
			}
			else
			{
				// Relative path, treat as local
				GetVolumeInformation(NULL,NULL,0,NULL,NULL,NULL,szFs,sizeof(szFs));
				if(!stricmp(szFs,"NTFS"))
					is_gmt_fs = 1;
				else
					is_gmt_fs = 0;
			}
		}
		else
		{
			// Drive specified...
			char szRootPath[4] = "?:\\";
			szRootPath[0]=filename[0];
			*szFs='\0';
			GetVolumeInformation(szRootPath,NULL,0,NULL,NULL,NULL,szFs,sizeof(szFs));
			if(!stricmp(szFs,"NTFS") || !stricmp(szFs,"HPFS"))
				is_gmt_fs = 1;
			else
				is_gmt_fs = 0;
		}
	}

	if(is_gmt_fs) // NTFS - everything is in GMT already
	{
		FileTimeToUnixTime ( &bhfi.ftLastAccessTime, &buf->st_atime, FALSE );
		FileTimeToUnixTime ( &bhfi.ftLastWriteTime, &buf->st_mtime, FALSE );
		FileTimeToUnixTime ( &bhfi.ftCreationTime, &buf->st_ctime, FALSE );
	}
	else 
	{
        // FAT - timestamps are in incorrectly translated local time.
        // translate them back and let FileTimeToUnixTime() do the
        // job properly.

        FILETIME At,Wt,Ct;

		FileTimeToLocalFileTime ( &bhfi.ftLastAccessTime, &At );
		FileTimeToLocalFileTime ( &bhfi.ftLastWriteTime, &Wt );
		FileTimeToLocalFileTime ( &bhfi.ftCreationTime, &Ct );
		FileTimeToUnixTime ( &At, &buf->st_atime, TRUE );
		FileTimeToUnixTime ( &Wt, &buf->st_mtime, TRUE );
		FileTimeToUnixTime ( &Ct, &buf->st_ctime, TRUE );
	}

    buf->st_size = bhfi.nFileSizeLow;
	if(bhfi.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
		buf->st_mode |= _S_IFDIR;
	else
		buf->st_mode |= _S_IFREG;

    /* On DOS, this field contains the drive number, but
     * the drive number is not available on this platform.
     * Also, for UNC network names, there is no drive number.
     */
    buf->st_rdev = buf->st_dev = 0;
	return 0;
}

int wnt_stat(const char *name, struct stat *buf)
{
	return _statcore(NULL,name,buf);
}

int wnt_fstat (int fildes, struct stat *buf)
{
	return _statcore((HANDLE)_get_osfhandle(fildes),NULL,buf);
}

int wnt_lstat (const char *name, struct stat *buf)
{
	return _statcore(NULL,name,buf);
}

#undef asctime
#undef ctime
/* ANSI C compatibility for timestamps - VC pads with zeros, C standard pads with spaces */

char *wnt_asctime(const struct tm *tm)
{
	char *buf = asctime(tm);
	if(buf[8]=='0') buf[8]=' ';
	return buf;
}

char *wnt_ctime(const time_t *t)
{
	char *buf = ctime(t);
	if(buf[8]=='0') buf[8]=' ';
	return buf;
}
