/*  Part of SWI-Prolog

    Author:        Jan Wielemaker
    E-mail:        J.Wielemaker@vu.nl
    WWW:           http://www.swi-prolog.org
    Copyright (c)  2011-2015, University of Amsterdam
                              Vu University Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#define UNICODE 1
#define _UNICODE 1

#define _UXNT_KERNEL 1
#include "uxnt.h"			/* my prototypes */
#include "utf8.c"

#include <windows.h>
#include <tchar.h>
#include <wchar.h>
#include "dirent.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#undef mkdir				/* avoid conflict */
#include <direct.h>
#ifndef __LCC__				/* not delete altogether? */
#define mkdir _xos_mkdir
#endif
#include <errno.h>
#include <fcntl.h>

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

#ifdef __LCC__
#define _close close
#define _read read
#define _write write
#define _lseek lseek
#define _tell tell
#define _chdir chdir
#define _mkdir mkdir
#define _rmdir rmdir
#define _getcwd getcwd
#endif

#define XENOMAP 1
#define XENOMEM 2

#ifndef PATH_MAX
#define PATH_MAX 260
#endif

static int exists_file_or_dir(const TCHAR *path, int flags);


		 /*******************************
		 *	       ERRNO		*
		 *******************************/

int
_xos_errno(void)
{ return errno;
}

		 /*******************************
		 *		UTF-8		*
		 *******************************/

static char *
wcstoutf8(char *dest, const wchar_t *src, size_t len)
{ char *o = dest;
  char *e = &o[len];

  for(; *src; src++)
  { if ( o+6 >= e && o+utf8_code_bytes(*src) >= e )
    { errno = ENAMETOOLONG;
      return NULL;
    }
    o = utf8_put_char(o, *src);
  }
  *o = '\0';

  return dest;
}


/* length of src in UTF-8, excluding terminating EOS */

static size_t
wcutf8len(const wchar_t *src)
{ size_t len = 0;

  for(; *src; src++)
  { if ( *src < 0x80 )
    { len++;
    } else
    { char o[6];
      char *e;
      e = utf8_put_char(o, *src);
      len += e-o;
    }
  }

  return len;
}


static wchar_t *
utf8towcs(wchar_t *dest, const char *src, size_t len)
{ wchar_t *o = dest;
  wchar_t *e = &o[len-1];

  for( ; *src; )
  { int wc;

    src = utf8_get_char(src, &wc);
    if ( o >= e )
    { errno = ENAMETOOLONG;
      return NULL;
    }
    *o++ = wc;
  }
  *o = 0;

  return dest;
}


static size_t
utf8_strlen(const char *s, size_t len)
{ const char *e = &s[len];
  unsigned int l = 0;

  while(s<e)
  { int chr;

    s = utf8_get_char(s, &chr);
    l++;
  }

  return l;
}



		 /*******************************
		 *	       HOME		*
		 *******************************/

static int
existsAndWriteableDir(const TCHAR *name)
{ DWORD a;

  if ( (a=GetFileAttributes(name)) != 0xFFFFFFFF )
  { if ( a & FILE_ATTRIBUTE_DIRECTORY )
    { if ( !(a & FILE_ATTRIBUTE_READONLY) )
	return TRUE;
    }
  }

  return FALSE;
}


char *
_xos_home()				/* expansion of ~ */
{ static char home[PATH_MAX];
  static int done = FALSE;

  if ( !done )
  { TCHAR h[PATH_MAX];

					/* Unix, set by user */
    if ( GetEnvironmentVariable(_T("HOME"), h, sizeof(h)) &&
	 existsAndWriteableDir(h) )
    { _xos_canonical_filenameW(h, home, sizeof(home), 0);
    } else if ( GetEnvironmentVariable(_T("USERPROFILE"), h, sizeof(h)) &&
		existsAndWriteableDir(h) )
    { _xos_canonical_filenameW(h, home, sizeof(home), 0);
    } else
    { TCHAR d[100];
      TCHAR p[PATH_MAX];
      TCHAR tmp[PATH_MAX];
      int haved, havep;

      haved = GetEnvironmentVariable(_T("HOMEDRIVE"), d, sizeof(d));
      havep = GetEnvironmentVariable(_T("HOMEPATH"),  p, sizeof(p));

      tmp[0] = '\0';
      if ( haved && havep )		/* Windows-NT */
      { _tcscpy(tmp, d);
	_tcscat(tmp, p);
      } else if ( haved )
      { _tcscpy(tmp, d);
	_tcscat(tmp, _T("\\"));
      } else if ( havep )
      { _tcscpy(tmp, p);
      } else if ( GetWindowsDirectory(tmp, sizeof(tmp)) == 0 )
      { int drv = _getdrive();		/* A=1 */

	tmp[0] = drv-1+'a';
	_tcscpy(&tmp[1], _T(":\\"));
      }

      _xos_canonical_filenameW(tmp, home, sizeof(home), 0);
    }

    done = TRUE;
  }

  return home;
}


		 /*******************************
		 *	  NAME CONVERSION	*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Map a UTF-8 string in Prolog internal representation to a UNICODE string
to be used with the Windows UNICODE access functions.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

wchar_t *
_xos_os_filenameW(const char *cname, wchar_t *osname, size_t len)
{ wchar_t *s = osname;
  wchar_t *e = &osname[len-1];
  const char *q = cname;

  s = osname;
					/* /c:/ --> c:/ */
  if ( q[0] == '/' && q[1] < 0x80 && isalpha(q[1]) && q[2] == ':' &&
       (q[3] == '/' || q[3] == '\0') )
  { if ( s+2 >= e )
    { errno = ENAMETOOLONG;
      return NULL;
    }
    *s++ = q[1];
    *s++ = ':';
    q += 3;
  }

  if ( (q[0] == '/' || q[0] == '\\') &&
       (q[1] == '/' || q[1] == '\\') )	/* deal with //host/share */
  { if ( s+1 >= e )
    { errno = ENAMETOOLONG;
      return NULL;
    }
    *s++ = '\\';
  }

  while( *q )				/* map / --> \, delete multiple '\' */
  { if ( *q == '/' || *q == '\\' )
    { if ( s+1 >= e )
      { errno = ENAMETOOLONG;
	return NULL;
      }
      *s++ = '\\';
      q++;
      while(*q == '/' || *q == '\\')
	q++;
    } else
    { int wc;

      q = utf8_get_char(q, &wc);
      if ( s+2 >= e )
      { errno = ENAMETOOLONG;
	return NULL;
      }
      *s++ = wc;
    }
  }

  while(s > osname+1 && s[-1] == '\\' )	/* delete trailing '\' */
    s--;
					/* d: --> d:\ */
  if ( s == &osname[2] && osname[1] == ':' &&
       osname[0] < 0x80 && isalpha(osname[0]) )
    *s++ = '\\';
  *s = '\0';

  return osname;
}


char *
_xos_os_filename(const char *cname, char *osname, size_t len)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(cname, buf, PATH_MAX) )
    return NULL;

  return wcstoutf8(osname, buf, len);
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Transform a UNICODE Windows filename into a UTF-8 representation of the
filename in Prolog canonical representation.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

char *
_xos_canonical_filenameW(const wchar_t *spec,
			 char *xname, size_t len,
			 int flags)
{ const wchar_t *s = spec;
  char *p = xname;
  char *e = &xname[len];

  if ( s[0] < 0x80 && isupper(s[0]) && s[1] == ':' )
  { *p++ = tolower(s[0]);
    *p++ = (char)s[1];
    s += 2;
  }

  for(; *s; s++)
  { int c = *s;

    if ( c == '\\' )
    { c = '/';
    } else if ( (flags&XOS_DOWNCASE) )
    { c = towlower((wchar_t)c);
    }

    if ( p+6 >= e )
    { errno = ENAMETOOLONG;
      return NULL;
    }
    p = utf8_put_char(p, c);
  }
  *p = '\0';

  return xname;
}


char *
_xos_canonical_filename(const char *spec, char *xname, size_t len, int flags)
{ TCHAR buf[PATH_MAX];

  if ( !utf8towcs(buf, spec, PATH_MAX) )
    return NULL;

  return _xos_canonical_filenameW(buf, xname, len, flags);
}


int
_xos_is_absolute_filename(const char *spec)
{ TCHAR buf[PATH_MAX];

  _xos_os_filenameW(spec, buf, PATH_MAX);
  if ( buf[1] == ':' && buf[0] < 0x80 && iswalpha(buf[0]) )
    return TRUE;			/* drive */
  if ( buf[0] == '\\' && buf[1] == '\\' )
    return TRUE;			/* UNC */

  return FALSE;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Get rid of possible  8+3  characters   in  the  path.  The documentation
suggests  you  can  do   that   using    a   single   FindFirstFile   or
GetFullPathName, but it appears you cannot.  If   you  like, here is the
code that doesn't work:

char *
_xos_long_file_nameW(const char *file, char *longname)
{ DWORD len;
  LPTSTR fp;

  if ( !(len=GetFullPathName(file, PATH_MAX, longname, &fp)) ||
       len >= PATH_MAX )
    strcpy(longname, file);

  return longname;
}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

TCHAR *
_xos_long_file_nameW(const TCHAR *file, TCHAR *longname, size_t len)
{ const TCHAR *i = file;
  TCHAR *o = longname;
  TCHAR *ok = longname;
  TCHAR *e = &longname[len-1];
  int changed = 0;

  while(*i)
  { int dirty = FALSE;

    while(*i && *i != '\\' && *i != '/' )
    { if ( *i == '~' )
	dirty++;
      if ( o >= e )
      { errno = ENAMETOOLONG;
	return NULL;
      }
      *o++ = *i++;
    }
    if ( dirty )
    { WIN32_FIND_DATA data;
      HANDLE h;

      *o = '\0';
      if ( (h=FindFirstFile(longname, &data)) != INVALID_HANDLE_VALUE )
      { size_t l = _tcslen(data.cFileName);

	if ( ok+l >= e )
	{ errno = ENAMETOOLONG;
	  FindClose(h);
	  return NULL;
	}

	_tcscpy(ok, data.cFileName);
	FindClose(h);
	o = ok + l;
	changed++;
      }
    }
    if ( *i )
    { if ( o >= e )
      { errno = ENAMETOOLONG;
	return NULL;
      }
      *o++ = *i++;
    }
    ok = o;
  }

  *o = '\0';

  return longname;
}


char *
_xos_long_file_name_toA(const wchar_t *file, char *longname, size_t len)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_long_file_nameW(file, buf, PATH_MAX) )
    return NULL;

  return wcstoutf8(longname, buf, len);
}


char *
_xos_long_file_name(const char *file, char *longname, size_t len)
{ TCHAR in[PATH_MAX];
  TCHAR out[PATH_MAX];

  if ( !utf8towcs(in, file, PATH_MAX) )
    return NULL;

  if ( !_xos_long_file_nameW(in, out, PATH_MAX) )
    return NULL;

  return wcstoutf8(longname, out, len);
}


char *
_xos_absolute_filename(const char *local, char *absolute, size_t len)
{ TCHAR buf[PATH_MAX];
  TCHAR *filepart;
  TCHAR abs[PATH_MAX];

  if ( !_xos_os_filenameW(local, buf, PATH_MAX) )
    return NULL;

  if ( GetFullPathName(buf, PATH_MAX, abs, &filepart) )
    return _xos_canonical_filenameW(abs, absolute, len, 0);

  return NULL;
}


int
_xos_same_file(const char *p1, const char *p2)
{ if ( strcmp(p1, p2) == 0 )
  { return TRUE;
  } else
  { TCHAR osp1[PATH_MAX], osp2[PATH_MAX];
    TCHAR abs1[PATH_MAX], abs2[PATH_MAX];
    TCHAR *fp;

    if ( !_xos_os_filenameW(p1, osp1, PATH_MAX) ||
	 !_xos_os_filenameW(p2, osp2, PATH_MAX) )
      return -1;			/* error */

    if ( !GetFullPathName(osp1, PATH_MAX, abs1, &fp) ||
	 !GetFullPathName(osp2, PATH_MAX, abs2, &fp) )
      return -1;

    //fwprintf(stderr, _T("f1='%s'\nf2='%s'\n"), abs1, abs2);

    if ( _tcsicmp(abs1, abs2) == 0 )
      return TRUE;
  }

  return FALSE;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Apply file-name limitations to a   path-name.  For DOS-type filesystems,
this implies limitation to  the  8+3   convention  and  omitting illegal
characters.  NT doesn't have all this,   but  filenames are matched case
insensitive, so we map everything to one case.  Note that both arguments
are in UTF-8 encoding!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

char *
_xos_limited_os_filename(const char *spec, char *limited)
{ const char *i = spec;
  char *o = limited;

  while(*i)
  { int wc;

    i = utf8_get_char(i, &wc);
    wc = towlower((wchar_t)wc);
    o = utf8_put_char(o, wc);
  }
  *o = '\0';

  return limited;
}


		 /*******************************
		 *	  FILE READ/WRITE	*
		 *******************************/

int
_xos_open(const char *path, int access, ...)
{ va_list args;
  TCHAR buf[PATH_MAX];
  int mode;

  va_start(args, access);
  mode = va_arg(args, int);
  va_end(args);

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wopen(buf, access, mode);
}


int
_xos_close(int handle)
{ return _close(handle);
}


ssize_t
_xos_read(int handle, void *buf, size_t size)
{ return _read(handle, buf, (unsigned int)size);
}


ssize_t
_xos_write(int handle, const void *buf, size_t size)
{ return _write(handle, buf, (unsigned int)size);
}


long
_xos_lseek(int handle, long offset, int whence)
{ return _lseek(handle, offset, whence);
}


long
_xos_tell(int handle)
{ return _tell(handle);
}


#define MAX_FOPEN_FLAGS 10

FILE *
_xos_fopen(const char *path, const char *mode)
{ TCHAR buf[PATH_MAX];
  TCHAR m[MAX_FOPEN_FLAGS];
  int i;

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return NULL;

  for(i=0; *mode && i < MAX_FOPEN_FLAGS-1; )
    m[i++] = (*mode++)&0xff;
  m[i] = 0;

  return _wfopen(buf, m);
}


		 /*******************************
		 *      FILE MANIPULATIONS	*
		 *******************************/

static int win_file_access_check = XOS_ACCESS_OPENCLOSE;

int
_xos_set_win_file_access_check(int new)
{ int old = win_file_access_check;

  win_file_access_check = new;
  return old;
}

int
_xos_get_win_file_access_check(void)
{ return win_file_access_check;
}

int
_xos_access(const char *path, int mode)
{ TCHAR buf[PATH_MAX];
  char sd_buf[512];
  SECURITY_DESCRIPTOR *sd;
  BOOL access_status;
  DWORD desired_access = 0;
  DWORD sd_size, granted_access;
  HANDLE token = 0, imp_token = 0;
  GENERIC_MAPPING generic_mapping;
  PRIVILEGE_SET privelege_set;
  DWORD  priv_set_len = sizeof(PRIVILEGE_SET);
  int retval = -1;
  SECURITY_INFORMATION sec_info =
    DACL_SECURITY_INFORMATION |
    OWNER_SECURITY_INFORMATION |
    GROUP_SECURITY_INFORMATION;

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  if ( mode == F_OK || win_file_access_check == XOS_ACCESS_ACCESS )
    return _waccess(buf, mode);

  if ( win_file_access_check == XOS_ACCESS_OPENCLOSE )
  { int m = 0;
    int fd;

    if ( exists_file_or_dir(buf, _XOS_DIR) )
      return _waccess(buf, mode);

    if ( mode & X_OK )
      mode |= R_OK;

    if ( (mode&(R_OK|W_OK)) == (R_OK|W_OK) )
      m = _O_RDWR;
    else if ( mode&R_OK )
      m = _O_RDONLY;
    else
      m = _O_WRONLY;

    if ( (fd=_wopen(buf, m)) >= 0 )
    { _close(fd);
      return 0;
    }
    return -1;
  }

  sd = (SECURITY_DESCRIPTOR*)&sd_buf;
  if ( !GetFileSecurity(buf, sec_info, sd, sizeof(sd_buf), &sd_size) )
  { if ( GetLastError() == ERROR_INVALID_FUNCTION )
    { goto simple;
    } else if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER )
    { errno = ENOENT;
      return -1;
    }

    if ( !(sd = malloc(sd_size)) )
    { errno = ENOMEM;
      return -1;
    }

    if ( !GetFileSecurity(buf, sec_info, sd, sd_size, &sd_size) )
      goto simple;
  }

  if ( mode & W_OK )
  { if ( _waccess(buf, W_OK ) < 0 )		/* read-only bit set */
      goto out;
  }

  if ( !OpenThreadToken(GetCurrentThread(),
			TOKEN_DUPLICATE | TOKEN_READ,
			TRUE,
			&token) )
  { if ( GetLastError() != ERROR_NO_TOKEN )
      goto simple;

    if ( !OpenProcessToken(GetCurrentProcess(),
			   TOKEN_DUPLICATE | TOKEN_READ,
			   &token) )
      goto simple;
  }

  if ( !DuplicateToken(token,
		       SecurityImpersonation,
		       &imp_token) )
    goto simple;

  if (mode & R_OK) desired_access |= GENERIC_READ;
  if (mode & W_OK) desired_access |= GENERIC_WRITE;
  if (mode & X_OK) desired_access |= GENERIC_EXECUTE;

  generic_mapping.GenericRead    = FILE_GENERIC_READ;
  generic_mapping.GenericWrite   = FILE_GENERIC_WRITE;
  generic_mapping.GenericExecute = FILE_GENERIC_EXECUTE;
  generic_mapping.GenericAll     = FILE_ALL_ACCESS;
  MapGenericMask(&desired_access, &generic_mapping);

  if ( !AccessCheck(sd,
		    imp_token,
		    desired_access,
		    &generic_mapping,
		    &privelege_set,
		    &priv_set_len,
		    &granted_access,
		    &access_status) )
    goto simple;

  if ( access_status )
    retval  = 0;

out:
  if ( sd && (char*)sd != sd_buf ) free(sd);
  if (imp_token) CloseHandle(imp_token);
  if (token) CloseHandle(token);

  return retval;

simple:
  retval = _waccess(buf, mode);
  goto out;
}


int
_xos_access_dir(const char *path, int mode)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _waccess(buf, mode);
}


int
_xos_chmod(const char *path, int mode)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wchmod(buf, mode);
}


int
_xos_remove(const char *path)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wremove(buf);
}


int
_xos_rename(const char *old, const char *new)
{ TCHAR osold[PATH_MAX];
  TCHAR osnew[PATH_MAX];

  if ( !_xos_os_filenameW(old, osold, PATH_MAX) ||
       !_xos_os_filenameW(new, osnew, PATH_MAX) )
    return -1;

  if ( MoveFileEx(osold, osnew, MOVEFILE_REPLACE_EXISTING) )
    return 0;

  errno = EPERM;
  return -1;				/* TBD: map error codes */
}


int
_xos_stat(const char *path, struct _stati64 *sbuf)
{ TCHAR buf[PATH_MAX];

   if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wstati64(buf, sbuf);
}


static int
exists_file_or_dir(const TCHAR *path, int flags)
{ DWORD a;

  if ( (a=GetFileAttributes(path)) != 0xFFFFFFFF )
  { if ( flags & _XOS_DIR )
    { if ( a & FILE_ATTRIBUTE_DIRECTORY )
	return TRUE;
      else
	return FALSE;
    }
    if ( flags & _XOS_FILE )
    { if ( a & FILE_ATTRIBUTE_DIRECTORY )
	return FALSE;
    }

    return TRUE;
  }

  return FALSE;
}


int
_xos_exists(const char *path, int flags)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return exists_file_or_dir(buf, flags);
}


		 /*******************************
		 *	    DIRECTORIES		*
		 *******************************/

DIR *
opendir(const char *path)
{ TCHAR buf[PATH_MAX];
  DIR *dp = malloc(sizeof(DIR));

  if ( !dp )
  { errno = ENOMEM;
    return NULL;
  }

  if ( !_xos_os_filenameW(path, buf, PATH_MAX-4) )
  { free(dp);
    return NULL;
  }
  _tcscat(buf, _T("\\*.*"));

  if ( !(dp->data = malloc(sizeof(WIN32_FIND_DATA))) )
  { free(dp);
    errno = ENOMEM;
    return NULL;
  }
  dp->first = 1;
  dp->handle = FindFirstFile(buf, dp->data);

  if ( dp->handle == INVALID_HANDLE_VALUE )
  { if ( _waccess(buf, 04) )		/* does not exist */
    { free(dp->data);
      free(dp);
      return NULL;
    }
  }

  return dp;
}


int
closedir(DIR *dp)
{ if ( dp )
  { if ( dp->handle )
      FindClose(dp->handle);
    free(dp->data);
    free(dp);

    return 0;
  }

  return -1;
}


static struct dirent *
translate_data(DIR *dp)
{ WIN32_FIND_DATA *data;

  if ( !dp->handle )
    return NULL;

  data = dp->data;
  if ( wcstoutf8(dp->d_name, data->cFileName, sizeof(dp->d_name)) )
    return dp;

  return NULL;
}


struct dirent *
readdir(DIR *dp)
{ for(;;)
  { struct dirent *de;

    if ( dp->first )
    { dp->first = 0;
    } else
    { if ( dp->handle )
      { if ( !FindNextFile(dp->handle, dp->data) )
	  return NULL;
      }
    }

    if ( (de = translate_data(dp)) )
      return de;
  }
}


int
_xos_chdir(const char *path)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  if ( buf[0] < 0x80 && isalpha(buf[0]) && buf[1] == ':' )
  { int drv = tolower(buf[0]) - 'a' + 1;

    if ( _getdrive() != drv )
    { if ( _chdrive(drv) < 0 )
	return -1;
    }
  }

  return _wchdir(buf);
}


int
_xos_mkdir(const char *path, int mode)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wmkdir(buf);
}


int
_xos_rmdir(const char *path)
{ TCHAR buf[PATH_MAX];

  if ( !_xos_os_filenameW(path, buf, PATH_MAX) )
    return -1;

  return _wrmdir(buf);
}


char *
_xos_getcwd(char *buf, size_t len)
{ TCHAR buf0[PATH_MAX];
  TCHAR buf1[PATH_MAX];

  if ( _wgetcwd(buf0, sizeof(buf0)/sizeof(TCHAR)) &&
       _xos_long_file_nameW(buf0, buf1, sizeof(buf0)/sizeof(TCHAR)) )
  { return _xos_canonical_filenameW(buf1, buf, len, 0);
  }

  return NULL;
}


		 /*******************************
		 *	    ENVIRONMENT		*
		 *******************************/

size_t
_xos_getenv(const char *name, char *buf, size_t buflen)
{ TCHAR nm[PATH_MAX];
  TCHAR val[PATH_MAX];
  TCHAR *valp = val;
  DWORD size;

  if ( !utf8towcs(nm, name, PATH_MAX) )
    return -1;
  size = GetEnvironmentVariable(nm, valp, PATH_MAX);

  if ( size > 0 )
  { size_t rc;

    if ( size >= PATH_MAX )
    { if ( (valp = malloc((size+1)*sizeof(TCHAR))) == NULL )
	return -1;
      size = GetEnvironmentVariable(nm, valp, size+1);
    }

    size = wcslen(valp);		/* return sometimes holds 0-bytes */
    if ( wcstoutf8(buf, valp, buflen) )
      rc = strlen(buf);
    else
      rc = wcutf8len(valp);

    if ( valp != val )
      free(valp);

    return rc;
  }

  return -1;
}


int
_xos_setenv(const char *name, char *value, int overwrite)
{ TCHAR nm[PATH_MAX];
  TCHAR buf[PATH_MAX];
  TCHAR *val = buf;
  int rc;

  if ( !utf8towcs(nm, name, PATH_MAX) )
    return -1;
  if ( !overwrite && GetEnvironmentVariable(nm, NULL, 0) > 0 )
    return 0;
  if ( !utf8towcs(val, value, PATH_MAX) )
  { size_t wlen = utf8_strlen(value, strlen(value)) + 1;

    if ( (val = malloc(wlen*sizeof(TCHAR))) == NULL )
      return -1;
    utf8towcs(val, value, wlen);
  }

  rc = SetEnvironmentVariable(nm, val);
  if ( val != buf )
    free(val);

  if ( rc )
    return 0;

  return -1;				/* TBD: convert error */
}
