// winport.c
// provide some GLUE code for Windows port

#include <config.h>
#include <system.h>
#include <common.h>
#include "winport.h"

char gen_tmp_buf[1024];

DIR * opendir(const char * dir )
{
	DIR * pdir = (DIR *)malloc( sizeof(DIR) );
	if( pdir ) {
		char * cp = &pdir->search[0];
      size_t len = strlen(dir);
		strcpy( cp, dir );
      if( len && ((dir[len-1] != '\\')&&(dir[len-1] != '/')) )
         strcat(cp, "\\");
		strcat( cp, "*.*" ); /* add wild card */
		pdir->hfind = FindFirstFile( cp, &pdir->fd );
		if(pdir->hfind && (pdir->hfind != INVALID_HANDLE_VALUE))
		{
			pdir->is_first = 1;
			return pdir;

		} else {
			free(pdir);
		}
	}



	return NULL;
}

struct dirent * readdir (DIR * pdir)
{
	if(pdir)
	{
		char * dst = &pdir->de.d_name[0];
		char * src = &pdir->fd.cFileName[0];
		if( pdir->is_first )
		{
			pdir->de.d_namlen = (int)strlen(src);
			strcpy( dst, src );
         pdir->de.d_type = 0;
         if( pdir->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
            pdir->de.d_type = DT_DIR;
			pdir->is_first = 0;
			return &pdir->de;
		}
		else
		{
			if(pdir->hfind && (pdir->hfind != INVALID_HANDLE_VALUE))
			{
				if( FindNextFile( pdir->hfind , &pdir->fd ) )
				{
					pdir->de.d_namlen = (int)strlen(src);
					strcpy( dst, src );
               pdir->de.d_type = 0;
               if( pdir->fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
                  pdir->de.d_type = DT_DIR;
					return &pdir->de;
				}
			}
		}
	}
	return NULL;
}

int closedir(DIR * pdir)
{
	if(pdir)
	{
		if(pdir->hfind && (pdir->hfind != INVALID_HANDLE_VALUE))
		{
			FindClose(pdir->hfind);
		}
		free( pdir );
	}
	return 0;
}

int geteuid( void )
{
   return 0;
}
int
chown (const char *file, uid_t uid, gid_t gid)
{
   // WIN32 NTFS does NOT have FILE OWNER,
   // so just return SUCCESS!!!
   return 0;
}

#include <time.h>

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif
 
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
  FILETIME ft;
  unsigned __int64 tmpres = 0;
  static int tzflag;
 
  if (NULL != tv)
  {
    GetSystemTimeAsFileTime(&ft);
 
    tmpres |= ft.dwHighDateTime;
    tmpres <<= 32;
    tmpres |= ft.dwLowDateTime;
 
    /*converting file time to unix epoch*/
    tmpres /= 10;  /*convert into microseconds*/
    tmpres -= DELTA_EPOCH_IN_MICROSECS; 
    tv->tv_sec = (long)(tmpres / 1000000UL);
    tv->tv_usec = (long)(tmpres % 1000000UL);
  }
 
  if (NULL != tz)
  {
    if (!tzflag)
    {
      _tzset();
      tzflag++;
    }
    tz->tz_minuteswest = _timezone / 60;
    tz->tz_dsttime = _daylight;
  }
 
  return 0;
}

/* ====================================================================
   Implement an input file, invoked by -@filename.txt
   It is a simple text file, with the command arguments on a new line.
   Lines beginning with ';' are comments.
   ===================================================================== */

static char ** inp_argv = 0;  /* allocated to hold the full argument list */
static char * inp_fbuf  = 0;  /* allocated to hold the file contents */

void
term_response_file( void )
{
   if( inp_argv )
      free(inp_argv);
   if( inp_fbuf )
      free(inp_fbuf);
   inp_argv = 0;
   inp_fbuf = 0;
}

#define  MY_EOL   (( c == '\r')||( c == '\n' ))
#define  GO_TO_END_OF_LINE for(; i < bytesread; i++) \
{ c = inp_fbuf[i]; \
   if(MY_EOL)break;\
}

void win_exit( int val )
{
   exit(val);
}

#if !defined(WIN64)
/* this will go away in WIN64 */
void too_large_for_win32_die (void)
{
  error (2, 0, "%s", _("too large for WIN32"));
  win_exit( 2 );
}
#endif


/* ============================================================
   Some arguments, like say --exclude can be
   --exclude='./*1'
   In unix/linux the shell will REMOVE these single quotes,
   so tehy must be removed here, to allow the single quotes
   to still be used ///

   ============================================================ */
void
input_response_file (int *pargc, char ***pargv)
{
   int argc = *pargc;
   char ** argv = *pargv;
   int i, c, new_argc, sz, bytesread, cnt, j, off, k;
   char * arg;
   struct_stat buf;
   int fd;
   size_t len;
   char * p;

   inp_argv = 0;  /* these MAY not be needed */
   inp_fbuf = 0;
   for(off = 1; off < argc; off++)
   {
      arg = argv[off];
      if( *arg == '@' ) {
         arg++;   /* past the leading '@' */
         if( *arg )
            break;
      }
      else if(( arg[0] == '-' )&&( arg[1] == '@' ))
      {
         arg++;
         arg++;   /* past the leading '@' */
         if( *arg )
            break;
      }
   }
   if( off < argc )
   {
      /* ok, must party with the options */
      if( FNSTAT(arg, &buf) == 0 )
      {
         if( buf.st_size == 0 )
            return;  /* silently ignore empty file */
         if( (buf.st_size + 1) > UINT_MAX )
         {
            fprintf(stderr,
               "ERROR: Input file TOO LARGE %I64u bytes. MAX is %u bytes! Aborting!!\n",
               buf.st_size, UINT_MAX );
            win_exit(TAREXIT_FAILURE);
         }
         sz = (int)buf.st_size;
         inp_fbuf = (char *)xmalloc( sz + 1 );
         if( inp_fbuf )
         {
            fd = _open (arg, _O_RDONLY);
            if( fd && (fd != -1) )
            {
               setmode(fd, O_BINARY);
               if ((bytesread = _read (fd, inp_fbuf, sz)) == sz)
               {
                  _close(fd);
                  new_argc = 0;
                  for(i = 0; i < bytesread; i++)
                  {
                     c = inp_fbuf[i];
                     if( c == ';' ) {
                        i++;
                        GO_TO_END_OF_LINE;
                     }
                     else if ( c > ' ' ) {
                        new_argc++; /* count a new argument */
                        arg = &inp_fbuf[i];  /* get START of arg */
                        j = i;   /* keep start of significant */
                        i++;     /* goto next char */
                        for(; i < bytesread; i++)
                        {
                           c = inp_fbuf[i];
                           /* terminate with space, eol, or comment char, ';' */
                           if((( c <= ' ' )&&( c != '\t' ))||( c == ';' ))
                           {
                              inp_fbuf[i] = 0;  /* kill this termination */
                              for( k = i; k > j; k-- )
                              {
                                 if( inp_fbuf[k] > ' ' )
                                    break;
                                 inp_fbuf[k] = 0;  /* back kill tailing space */
                              }
                              break;
                           }
                        }
                     }
                     if( !MY_EOL ) {
                        i++;
                        GO_TO_END_OF_LINE;
                     }
                  }
                  if( new_argc )
                  {
                     inp_argv = (char **)xmalloc( sizeof(void *) * (argc + new_argc + 1));
                     if(inp_argv)
                     {
                        /* read to read and add the arguments */
                        cnt = 0;
                        /* add any beginning arguments */
                        for(i = 0; i < off; i++)
                        {
                           arg = argv[i];
                           inp_argv[cnt++] = arg;
                        }
                        for(i = 0; i < bytesread; i++)
                        {
                           c = inp_fbuf[i];
                           if( c == ';' )
                           {
                              i++;
                              GO_TO_END_OF_LINE;
                           }
                           else if ( c > ' ' )
                           {
                              arg = &inp_fbuf[i];  /* get START of arg */
                              inp_argv[cnt++] = arg;
                              i++;
                              for(; i < bytesread; i++)
                              {
                                 c = inp_fbuf[i];
                                 if((( c <= ' ' )&&( c != '\t' ))||( c == ';' ))
                                    break;
                              }
                           }
                           if( !MY_EOL ) {
                              i++;
                              GO_TO_END_OF_LINE;
                           }
                        }
                        /* add back any trailing arguments */
                        for( i = (off + 1); i < argc; i++ )
                        {
                           arg = argv[i];
                           inp_argv[cnt++] = arg;
                        }
                        inp_argv[cnt] = 0;
                        *pargc = cnt;
                        *pargv = inp_argv;
                     }
                     else
                     {
                        fprintf(stderr, "ERROR: MEMORY FAILED! Aborting!!\n" );
                        win_exit(TAREXIT_FAILURE);
                     }
                  }
                  else
                  {
                     fprintf(stderr, "ERROR: FAILED to find argument in [%s]! Aborting!!\n", arg );
                     win_exit(TAREXIT_FAILURE);
                  }
               }
               else
               {
                  _close(fd);
                  fprintf(stderr, "ERROR: Read FAILED on [%s]! Aborting!!\n", arg );
                  win_exit(TAREXIT_FAILURE);
               }
            }
            else
            {
               fprintf(stderr, "ERROR: Failed to open [%s]! Aborting!!\n", arg );
               win_exit(TAREXIT_FAILURE);
            }
         }
         else
         {
            fprintf(stderr, "ERROR: MEMORY FAILED! Aborting!!\n" );
            win_exit(TAREXIT_FAILURE);
         }
      }
      else
      {
         char * cp = gen_tmp_buf;
         fprintf(stderr, "ERROR: Unable to stat file [%s]! Aborting!!\n", arg );
         _getcwd(cp, 264);
         fprintf(stderr, " Current work directory is [%s]\n", cp );
         win_exit(TAREXIT_FAILURE);
      }
   }

   /* after any RESPONSE file input, now check the arguments
      for potential adjustments
   */

   argc = *pargc; /* extract count */
   argv = *pargv; /* and argument pointer */
   k = 0;
   len = 0;
   for( off = 0; off < argc; off++ )
   {
      arg = argv[off];
      cnt = (int)strlen(arg);
      len += cnt + 1;   /* length, plus null char */
      if( off && ( _strnicmp( arg, "--exclude=", 10 ) == 0 ))
      {
         p = strchr( &arg[10], '\'' );
         if( p )
         {
            if( inp_fbuf )
            {  /* it is our buffer - it can be modified */
               k = 0;
               for( j = 0; j < cnt; j++ )
               {
                  if( arg[j] != '\'' )
                     arg[k++] = arg[j];
               }
               arg[k] = 0;
               k = 0;
            }
            else
               k = off + 1;  /* we must allocate a new buffer */
         }
      }
   }
   if(k)
   {
      inp_fbuf = xmalloc( len );
      inp_argv = xmalloc( sizeof(void *) * (argc + 1) );
      k = 0;
      len = 0;
      new_argc = 0;
      for( off = 0; off < argc; off++ )
      {
         arg = argv[off];
         cnt = (int)strlen(arg);
         strcpy( &inp_fbuf[len], arg ); /* copy in the argument */
         arg = &inp_fbuf[len];
         inp_argv[new_argc++] = arg;
         len += cnt + 1;   /* bump to next location */
         if( off && ( _strnicmp( arg, "--exclude=", 10 ) == 0 ))
         {
            p = strchr( &arg[10], '\'' );
            if( p )
            {  /* it is now our buffer - it can be modified */
               k = 0;
               for( j = 0; j < cnt; j++ )
               {
                  if( arg[j] != '\'' )
                     arg[k++] = arg[j];
               }
               arg[k] = 0;
            }
         }
      }
      inp_argv[new_argc] = 0; /* never sure if this ZERO required, but, in case */
      *pargv = inp_argv;   /* substitute our buffer pointers */
      *pargc = new_argc;   /* this should may be unchanged */
   }
}


/* ====================================================================
   END Implement an input file, invoked by -@filename.txt
   ==================================================================== */

int	BumpFileName( char * lps )
{
   int	i, j, k, iret;
	char	c;
   i = 0;

   iret = 0;
   if(lps)
      i = (int)strlen(lps);
   if(i)
	{
		j = i - 1;	// back up 1
		c = lps[j];	// get char
      if( strchr( lps, '.' ) )
      {
		   while(j)
		   {
			   c = lps[j];	// get char
			   if( c == '.' )
			   {
				   j--;		// back to BEFORE point
				   c = lps[j];	// get char
				   break;	// got the POINT
			   }
			   j--;	// continue backing up
		   }
      }
		k = j;	// keep file name end - ie before point, if any
		if(( k                             ) &&
			( ( c != '\\' ) || ( c != ':' ) ) )
		{
			// we appear to have some filename length
         // backup j until slash or colon
			while(j)
			{
				j--;
				c = lps[j];	// get char
				if( ( ISSLASH(c) ) || ( c == ':' ) )
				{
					j++;
					break;
				}
			}
			while( k >= j )
			{
				c = lps[k];	// get char
				if( ( c >= '0' ) && ( c <= '9' ) )
				{
					if( c == '9' )
					{
						lps[k] = '0';
					}
					else
					{
						c++;
                  if( lps[k] != c )
                  {
                     iret = 1;
                     lps[k] = c;
                  }
						break;
					}
				}
				else
				{
					// aint numeric
					lps[k] = '0';  // mkae it so
               iret = 1;
					break;
				}
				k--;	// back up another
			}
		}
	}
   return iret;
}

/* _MSC_VER - ensure the destination of a RENAME does NOT contain a SLASH
   or in fact any of \ / : * ? " < > |
   and that the new name does not presently exist ... */

#define  ISNOTFNC(a) (( a == '\\')||( a == '/' )||( a == '*' )||( a == '?' )||( a == '"')||\
   ( a == '<' )||( a == '>' ))

int win_copy_file_name( char * new_dst, char * dst )
{
   int len = (int)strlen(dst);
   int i;
   char c;
   for( i = 0; i < len; i++ )
   {
      c = dst[i];
      if( ISNOTFNC(c) )
         new_dst[i] = '_';
      else
         new_dst[i] = c;
   }
   new_dst[i] = 0;
   return len;
}

char * win_get_unique_destination( char * dst )
{
   static char _s_new_dst[MAX_PATH];
   char * new_dst = _s_new_dst;
   ssize_t len, i;
   char c, d;
   struct stat buf;

   len = win_copy_file_name( new_dst, dst );
   if( stat( new_dst, &buf ) )
      return new_dst;
   len--;
   while(len >= 0)
   {
      c = new_dst[len];
      if(( c < 'z' )&&( c != '_' ))
      {
         c++;
         new_dst[len] = c;
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
      else
      {
         len--;
      }
   }
   // wowee, still no unique file name found. try NUMBERS
   len = win_copy_file_name( new_dst, dst );
   len--;
   new_dst[len] = '0';  /* start with '0' */
   if( stat( new_dst, &buf ) )
      return new_dst;
   while(len >= 0)
   {
      c = new_dst[len];
      if(( c < '9' )&&( c != '_' ))
      {
         c++;
         new_dst[len] = c;
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
      else
      {
         /* put the previous character back */
         c = dst[len];
         if( ISNOTFNC(c) )
            new_dst[len] = '_';
         else
            new_dst[len] = c;
         len--;
         if( len >= 0 )
            new_dst[len] = '0'; /* start another range */
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
   }

   // WHAT - STILL HERE!!!
   // OK, ANOTHER SEQUENCE COMING UP
   len = win_copy_file_name( new_dst, dst );
   len--;
   new_dst[len] = '0';  /* start with '0' */
   if( stat( new_dst, &buf ) )
      return new_dst;
   while(len >= 0)
   {
      c = new_dst[len];
      if(( c < '9' )&&( c != '_' ))
      {
         c++;
         new_dst[len] = c;
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
      else
      {
         /* put this back to ZERO */
         new_dst[len] = '0';
         len--;   /* back up one more */
         if( len >= 0 )
            new_dst[len] = '0'; /* start another range */
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
   }
   // THIS IS IMPOSSIBLE - STILL NOT FOUND A FREE SLOT!!!
   len = win_copy_file_name( new_dst, dst );
   len--;
   new_dst[len] = '0';  /* start with '0' */
   if( stat( new_dst, &buf ) )
      return new_dst;
   while(len >= 0)
   {
      c = new_dst[len];
      if(( c < '9' )&&( c != '_' ))
      {
         c++;
         new_dst[len] = c;
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
      else
      {
         /* leave current at '9' */
         len--;   /* back up one more */
         if( len >= 0 )
            new_dst[len] = '0'; /* start another range */
         if( stat( new_dst, &buf ) )
            return new_dst;
      }
   }
   // this could go on FOREVER
   d = '9';
   while( d > '0' )
   {
      d--;  /* back up one */
      len = strlen(dst);
      for( i = 0; i < len; i++ )
      {
         c = dst[i];
         if( ISNOTFNC(c) )
            new_dst[i] = '_';
         else
            new_dst[i] = c;
      }
      new_dst[i] = 0;
      len--;
      new_dst[len] = '0';  /* start with '0' */
      if( stat( new_dst, &buf ) )
         return new_dst;
      while(len >= 0)
      {
         c = new_dst[len];
         if(( c < '9' )&&( c != '_' ))
         {
            c++;
            new_dst[len] = c;
            if( stat( new_dst, &buf ) )
               return new_dst;
         }
         else
         {
            /* back up current to '8', '7' ... '0' */
            new_dst[len] = d;
            len--;   /* back up one more */
            if( len >= 0 )
               new_dst[len] = '0'; /* start another range */
            if( stat( new_dst, &buf ) )
               return new_dst;
         }
      }
   }
   // WE ARE HERE WITH ALL ZEROS
   // but start again with the desired file name,
   // and bump it numerically ...
   len = win_copy_file_name( new_dst, dst );
   while( BumpFileName( new_dst ) )
   {
      if( stat( new_dst, &buf ) )
         return new_dst;
   }
   return new_dst;
}
// #endif /* _MSC_VER */

int win_Drive_Supports_Sparse( char * drive )
{
   char szVolName[MAX_PATH], szFSName[MAX_PATH];
   DWORD dwSN, dwMaxLen, dwVolFlags;
   char * path = drive;
   if( path && (*path == 0) )
      path = NULL;
   if( GetVolumeInformation( path,
       szVolName, MAX_PATH, &dwSN,
       &dwMaxLen, &dwVolFlags, szFSName, MAX_PATH) )
   {
      if (dwVolFlags & FILE_SUPPORTS_SPARSE_FILES) {
         // File system supports sparse streams
         return 1;
      } // Sparse streams are not supported
      return 0;
   }
   return 2;   // undetermined!!!
}

int win_drive_supports_sparse( char * filename )
{
   char szDrive[_MAX_DRIVE + 2];
   char szFile[_MAX_FNAME];
   size_t len;
   int   i;

   szDrive[0] = 0;
   szFile[0]  = 0;

   _splitpath( filename, szDrive, NULL, szFile, NULL );
   len = strlen(szDrive);
   if(len && !(szDrive[0] == '\\') && (szDrive[len - 1] != '\\'))
      strcat(szDrive,"\\");
   i = win_Drive_Supports_Sparse(szDrive);
   return i;
}

int win_is_sparse_file( char * file )
{
   int iret = 0;
   HANDLE hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   BY_HANDLE_FILE_INFORMATION bhfi;
   memset( &bhfi, 0, sizeof(BY_HANDLE_FILE_INFORMATION) );
   if( VFH(hFile) )
   {
      if( GetFileInformationByHandle(hFile, &bhfi) )
      {
         if( bhfi.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE )
            iret = 1;
      }
      CloseHandle(hFile);
   }
   return iret;
}

__int64 win_get_sparse_file_size( char * file_name )
{
   LARGE_INTEGER li;
   _int64 i64;
   li.LowPart = GetCompressedFileSize( file_name, // LPCTSTR lpFileName,
      (LPDWORD)&li.HighPart );  // LPDWORD lpFileSizeHigh
   i64 = li.QuadPart;
   if( li.LowPart == INVALID_FILE_SIZE )
   {
      /* potential error */
      if( GetLastError() != NO_ERROR )
         return -1;
   }
   return i64;
}

char * win_truncate_file( char * name, __int64 size )
{
   static char _s_trunc_err[1024];
   char * perr = NULL;
   LARGE_INTEGER lifrom, lito;
   DWORD dwerr;
   HANDLE h = CreateFile( name, GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL, OPEN_EXISTING, 0, NULL);
   if( !VFH(h) )
   {
      perr = _s_trunc_err;
      sprintf( perr, "cannot open '%s'", name);
      return perr;
   }
   lifrom.QuadPart = size;
   if( !SetFilePointerEx( h,   // HANDLE hFile,
      lifrom,
      &lito,
      FILE_CURRENT ) )   // DWORD dwMoveMethod
   {
      dwerr = GetLastError();
      CloseHandle(h);
      perr = _s_trunc_err;
      strcpy( perr, "truncate:SetFilePointer: FAILED! " );
      GetWinErrorText( &perr[strlen(perr)], 264, dwerr );
      return perr;
   }
   if( !SetEndOfFile( h ) )
   {
      dwerr = GetLastError();
      perr = _s_trunc_err;
      strcpy( perr, "truncate:SetEndOfFile: FAILED! " );
      GetWinErrorText( &perr[strlen(perr)], 264, dwerr );
   }
   CloseHandle(h);
   return perr;   // NULL, if no error
}

int win_verify_sparse( char * file, struct __stat64 * pst )
{
   __int64 fs = win_get_sparse_file_size( file );
   int isp = win_is_sparse_file(file);
   __int64 fblks = (fs / ST_NBLOCKSIZE + (fs % ST_NBLOCKSIZE != 0));
   __int64 sblks = ((pst->st_size / ST_NBLOCKSIZE) + ((pst->st_size % ST_NBLOCKSIZE) != 0));
   if( !isp || !(fblks < sblks) )
   {
#if !defined(NDEBUG)
      printf( "verify_file: FAILED [%s] NOT SPARSE!\n", file );
      if( isp )
      {
         printf( "file size = %I64d, stat size = %d\n", fs, pst->st_size );
         printf( "ST_NBLOCKSIZE = %d", ST_NBLOCKSIZE );
         printf( ", file blocks = %d", fblks);
         printf( ", stat blocks = %d (%d)\n", sblks, ST_NBLOCKS(*pst) );
      }
#endif
      return 0;
   }
   return 1;
}

int win_set_sparse_attribute( char * name, long h )
{
   DWORD dwatts;
   if ( !VFH((HANDLE)h) ||
      !DeviceIoControl((HANDLE)h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwatts, NULL) )
   {
      error (0, 0, _("cannot set sparse attribute on '%s'"), name);
      return 1;
   }
   return 0;
}

int win_set_sparse_attribute_on_descriptor( char * name, int fd )
{
   long h = _get_osfhandle (fd);
   if( VFH((HANDLE)h) )
      return win_set_sparse_attribute( name, h );
   return 1;
}


// eof - winport.c

