/* File I/O function library module,
   Rick Smereka, Copyright (C) 1995-2005.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   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.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   Original DOS version Feb/95, Rick Smereka

   Port to QNX V4.23a Dec/97, Rick Smereka

   Changed the 'unique' function to create a file in the
   indicated directory. Also changed the return type to
   'int' to indicate the success or failure of the file
   name generation. Dec/97, Rick Smereka

   Added function 'qrename' specific to QNX. This function
   attempts to perform a normal rename. If that failes with
   a 'EXDEV' 'errno' (cannot move across devices), an OS
   copy is performed. Nov/98, Rick Smereka

   Ported to 32bit Windows under CodeWarrior V4.
   Ported to HP-UX under GNU C 2.8.1.
   Jan/99, Rick Smereka

   Ported to Red Hat Linux 5.2, Jul/99, Rick Smereka

   Changed function 'isdirectory' to compile on all
   platforms and use the POSIX system call 'opendir'.
   Feb/2001, Rick Smereka

   Changed function 'filecopy' to use standard stream I/O
   funtion calls ('fopen', 'fread', 'fwrite' and 'fclose').
   Mar/2001, Rick Smereka

   Corrected bug in 'zcreate' where an already existing file
   was truncated. Nov/2001, Rick Smereka

   Ported to Debian Linux. Nov/2002, Rick Smereka

   Added functions 'write_dos_line' and 'write_unix_line'.
   Dec/2002, Rick Smereka

   Changed function 'unique' to use a sequential number for the
   file name instead of a random number. Jan/2005, Rick Smereka

   Added a check for a proper directory name in function 'unique'.
   Fixed 'ifdef' in function 'qrename'. Mar/2005, Rick Smereka */

#include "stdhead.h"

int unique(char *dir, char *fname)
{
   /* Create a unique file name. Uniquness will be tested in the
      directory 'dir'. Do not end 'dir' with a path separator.
      'dir' can be a null string but not a NULL pointer.
      Name of the new file is returned in 'fname' minus the
      directory name. */

   static long fcount = 0L;
   int dir_len, total_len, ex = TRUE;
   char *total_f;
   char rname[13];

   if (dir == (char *)NULL)
      return(FALSE);

   dir_len = strlen(dir);

   if (dir_len && dir[dir_len - 1] == PATH_SEP)
      return(FALSE);

   if (dir_len && !isdirectory(dir))
      return(FALSE);

   if (fname == (char *)NULL)
      return(FALSE);

   /* calculate length of total string required */

   total_len = dir_len + 14;

   /* allocate total file name string */

   if ((total_f = (char *)malloc(total_len)) == (char *)NULL)
      return(FALSE);

   while(ex)
      {
      if (fcount > 99999999L)
         fcount = 0L;

      sprintf(rname, "%08ld.tmp", fcount++);

      if (dir_len)
         sprintf(total_f, "%s%c%s", dir, PATH_SEP, rname);
      else
         strcpy(total_f, rname);

      ex = exist(total_f);
      }

   free(total_f);
   strcpy(fname, rname);
   return(TRUE);
}

int exist(char *file)
{
   /* Test for file existance. Under Unix/QNX, the POSIX
      'access' function is used. Since CodeWarrior does not
      support this function and DOS directory functions are
      also not supported, we test for file existance by
      attempting to open the file. Note that the Unix/QNX
      version makes no assumption on the read/write status
      of the file whereas the Windows 32bit version specifically
      opens the file for read. Function returns 'TRUE' upon success,
      'FALSE' otherwise. */
#ifdef OS_DOS
struct ffblk dconst;
struct ffblk *dta = &dconst;
#endif

#ifdef OS_WIN32
   FILE *in;
#endif

   if (!strlen(file))
      return(FALSE);

#ifdef OS_WIN32
   if ((in = fopen(file, "r")) == (FILE *)NULL)
      return(FALSE);

   fclose(in);
   return(TRUE);
#endif

#ifdef OS_UNIX
   if (!access(file, F_OK))
      return(TRUE);

   return(FALSE);
#endif

#ifdef OS_DOS
   if (!findfirst(file, dta, FA_NORMAL))
      return(TRUE);

   return(FALSE);
#endif
}

int numrecs(FILE *fi)
{
   /* Count the number of records in a file. */

   char *buf;
   int numrec = 0;

   if ((buf = (char *)malloc(BUFSIZE + 1)) == NULL)
      return(0);

   rewind(fi);
   get_rec(fi, buf);

   while(!feof(fi))
      {
      numrec++;
      get_rec(fi, buf);
      }

   rewind(fi);
   free(buf);
   return(numrec);
}

void get_nrec(int recno, FILE *fi, char *buf)
{
   /* Read the 'recno' record from 'fi' into 'buf' and strip any
      line feeds. 'Buf' will contain zero bytes on failure.
      'Buf' must be statically allocated by the caller to an
      appropriate size. */

   int i;
   char *ptr;

   rewind(fi);

   for(i = 0; i < recno; i++)
      {
      fgets(buf, BUFSIZE, fi);

      if (feof(fi))
         {
         buf[0] = EOS;
         return;
         }
      }

   if ((ptr = strchr(buf, EOL)) != NULL)
      *ptr = EOS;
}

void get_rec(FILE *fi, char *buf)
{
   /* Read a record from 'fi' into 'buf' and strip any
      line feeds. */

   char *ptr;

   fgets(buf, BUFSIZE, fi);

   if ((ptr = strchr(buf, EOL)) != NULL)
      *ptr = EOS;
}

int zcreate(char *fname)
{
   /* Create a zero byte file called 'fname'. It is an
      error if the file already exists. Function
      returns TRUE on success, FALSE otherwise. */

   FILE *fi;

   if (fname == (char *)NULL || !strlen(fname))
      return(FALSE);

   if (exist(fname))
      return(FALSE);

   if ((fi = fopen(fname,"w")) == (FILE *)NULL)
      return(FALSE);

   fclose(fi);
   return(TRUE);
}

int isdirectory(char *path)
{
   /* Check for 'path' being a directory. Function uses the
      POSIX standard 'opendir' system call. Function returns
      'TRUE' if the 'path' resolves to a directory, 'FALSE'
      otherwise. */

   DIR *dir;
      
   if (path == NULL || !strlen(path))
      return(FALSE);
      
   if ((dir = opendir(path)) == NULL)
      return(FALSE);   

   closedir(dir);
   return(TRUE);
}

int filecopy(char *src, char *dest)
{
   /* Copy a file from 'src' to 'dest'. If 'dest' file
      exists, it will be overwritten. A 32kb buffer is
      used to copy the data. Function returns 'TRUE'
      upon success, 'FALSE' otherwise. */

   FILE *in, *out;
   int bufsiz, ret, done = FALSE;
   char *ptr;

   if (!strlen(src) || !strlen(dest))
      return(FALSE);

   bufsiz = 32760;

   if ((ptr = (char *)malloc(bufsiz)) == (char *)NULL)
      return(FALSE);

   if ((in = fopen(src, "rb")) == NULL)
      {
      free(ptr);
      return(FALSE);
      }

   if ((out = fopen(dest, "wb")) == NULL)
      {
      fclose(in);
      free(ptr);
      return(FALSE);
      }

   /* swap until end of file or error */

   do
      {
      ret = fread(ptr, 1, bufsiz, in);

      /* trap read errors */

      if (ferror(in))
         {
         fclose(in);
         fclose(out);
         free(ptr);
         return(FALSE);
         }

      if (fwrite(ptr, 1, ret, out) != ret)
         {
         fclose(in);
         fclose(out);
         free(ptr);
         return(FALSE);
         }

      /* if we read less than a buffer size, assume done */

      if (ret < bufsiz)
         done = TRUE;
      }
   while(!done);

   fclose(in);
   fclose(out);
   free(ptr);
   return(TRUE);
}

int qrename(char *src, char *dest)
{
   /* Rename a file. This version will rename a file
      anywhere (same directory, different directory on
      same volume, different directory on different
      disk/volume). Function returns 'TRUE' upon
      success, 'FALSE' otherwise. Dec/98,
      Rick Smereka */
      
   int lensrc, lendest;
#ifdef OS_UNIX
   int ret;
#endif
   
   // reasonableness checks
   
   if (src == (char *)NULL || dest == (char *)NULL)
      return(FALSE);
      
   lensrc = strlen(src);
   lendest = strlen(dest);
   
   if (!lensrc || !lendest)
      return(FALSE);
      
   // first, attempt a normal 'rename'
   
   if (!rename(src, dest))
      return(TRUE);

#ifdef OS_UNIX
   ret = errno;
   
   if (ret != EXDEV)
      return(FALSE);
#endif
      
   // normal rename failed, use copy
   
   if (!filecopy(src, dest))
      return(FALSE);
      
   // delete source file
   
   (void)unlink(src);
   return(TRUE);
}

int write_dos_line(FILE *out, char *buf)
{
   /* Write a text line with DOS/Windoze line delimiters (cr/lf).
      The file 'out' must have been opened for binary output.
      Function returns 'TRUE' if the line was successfully
      written, 'FALSE' otherwise. */

   char *fbuf;
   int len;

   if (out == (FILE *)NULL || buf == (char *)NULL)
      return(FALSE);
 
   len = strlen(buf);

   if ((fbuf = (char *)malloc(len + 3)) == (char *)NULL)
      return(FALSE);

   sprintf(fbuf, "%s%c%c", buf, (unsigned char)EOLL,
           (unsigned char)EOL);

   if (fwrite(fbuf, 1, len + 2, out) != len + 2)
      {
      free(fbuf);
      return(FALSE);
      }

   free(fbuf);
   return(TRUE);
}

int write_unix_line(FILE *out, char *buf)
{
   /* Write a text line with a Unix line delimiter (lf).
      The file 'out' must have been opened for binary output.
      Function returns 'TRUE' if the line was successfully
      written, 'FALSE' otherwise. */

   char *fbuf;
   int len;

   if (out == (FILE *)NULL || buf == (char *)NULL)
      return(FALSE);
 
   len = strlen(buf);

   if ((fbuf = (char *)malloc(len + 2)) == (char *)NULL)
      return(FALSE);

   sprintf(fbuf, "%s%c", buf, (unsigned char)EOL);

   if (fwrite(fbuf, 1, len + 1, out) != len + 1)
      {
      free(fbuf);
      return(FALSE);
      }

   free(fbuf);
   return(TRUE);
}
