/* $Id: dsk.c,v 1.4 2001/12/24 01:09:01 sybalsky Exp $ (C) Copyright Venue, All Rights Reserved  */

/************************************************************************/
/*									*/
/*	(C) Copyright 1989-1995 Venue. All Rights Reserved.		*/
/*	Manufactured in the United States of America.			*/
/*									*/
/************************************************************************/

#include "version.h"

#include <errno.h>
#include <fcntl.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef DOS
#include <dirent.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/statvfs.h>
#include <sys/time.h>
#else /* DOS */
#include <direct.h>
#include <dos.h>
#include <time.h>
#include <io.h>
#define MAXPATHLEN _MAX_PATH
#define MAXNAMLEM _MAX_PATH
#define alarm(x) 0
#endif /* DOS */

#include "lispemul.h"
#include "lispmap.h"
#include "adr68k.h"
#include "lsptypes.h"
#include "lspglob.h"
#include "arith.h"
#include "stream.h"
#include "timeout.h"
#include "locfile.h"
#include "osmsg.h"
#include "dbprint.h"

#include "dskdefs.h"
#include "byteswapdefs.h"
#include "car-cdrdefs.h"
#include "commondefs.h"
#include "ufsdefs.h"


extern int *Lisp_errno;
extern int Dummy_errno;

typedef struct filename_entry {
  char name[MAXPATHLEN]; /* With version, foo.~3~ or foo */
  int version_no;
} FileName;

typedef struct current_varray {
  char path[MAXPATHLEN]; /* pathname of directory */
  char file[MAXPATHLEN]; /* file name  (down cased name) */
  time_t mtime;
} CurrentVArray;

FileName VersionArray[VERSIONARRAYLENGTH];
CurrentVArray VArrayInfo;

static int locate_file(char *dir, char *name);
static int make_directory(register char *dir);
static int maintain_version(char *file, FileName *varray, int forcep);
static int get_versionless(FileName *varray, char *file, char *dir);
static int check_vless_link(char *vless, FileName *varray, char *to_file, int *highest_p);
static int get_old(char *dir, FileName *varray, char *afile, char *vfile);
static int get_oldest(char *dir, FileName *varray, char *afile, char *vfile);
static int get_new(char *dir, FileName *varray, char *afile, char *vfile);
static int get_old_new(char *dir, FileName *varray, char *afile, char *vfile);
static int get_version_array(char *dir, char *file, FileName *varray, CurrentVArray *cache);

#ifdef DOS
static void separate_drive(char *lfname, char *drive)
{
  register char *cp;

  cp = lfname;

  /* Check if there's a drive specified. */

  if (*(cp + 1) == DRIVESEP) {
    *drive = *cp; /* copy the drive letter, if there is one */
    cp++;
    cp++;       /* Move to the real `<`/ */
    while (*cp) /* Move the rest to the left to cover. */
    {
      *(cp - 2) = *cp;
      cp++;
    }
    *(cp - 2) = '\0';
  } else
    *drive = '\0'; /* no drive */
}
#endif /* DOS */

/*
 * Name:	separate_host
 *
 * Argument:	char	*lfname	Lisp full file name including host field.
 *		char	*host	The place where host field will be stored.
 *
 * Value:	void
 *
 * Side Effect:	lfname will be replaced with the file name except host field,
 *		and host will be replaced with the host field.
 *
 * Description:
 *
 * Accepts a Lisp full file name from Lisp code, and separate the host field
 * from other components.  The result will be appropriate form to pass to
 * unixpathname.  For convenience to unixpathname, the initial directory
 * delimiter will be removed from lfname except the case lfname specifies the
 * very root directory.  And if the lfname is regarded as other directory,
 * the trail directory delimiter is also removed.
 *
 */

#ifdef DOS
void separate_host(char *lfname, char *host, char *drive)
#else
void separate_host(char *lfname, char *host)
#endif /* DOS */
{
  register char *cp;
  register size_t diff;

  cp = lfname + 1; /* Skip the initial "{". */

  while (*cp != '}') *host++ = *cp++;
  *host = '\0';

  cp++; /* Now, *cp == '<' or drive letter. */
#ifdef DOS
  /* Check if there's a drive specified. */

  if (*(cp + 1) == DRIVESEP) {
    *drive = *cp; /* copy the drive letter, if there is one */
    cp++;
    cp++; /* Move to the real `<`/ */
  } else
    *drive = '\0'; /* no drive */
#endif             /* DOS */

  if (*(cp + 1) == '\0') {
    /* Root directory is specified. */
    *lfname = '<';
    *(lfname + 1) = '\0';
  } else {
    diff = cp - lfname;
    if (*cp == '<' || *cp == DIRSEP
#ifdef DOS
        || *cp == UNIXDIRSEP
#endif /* DOS */
        ) {
      /*
       * Skip the initial directory delimiter.
       */
      cp++;
      diff++;
    }
    while (*cp) {
      *(cp - diff) = *cp;
      cp++;
    }
    if (*(cp - 1) == '>' && *(cp - 2) != '\'') {
      /*
       * The last character is a not quoted directory
       * delimiter.  We have to remove it from the result
       * lfname for the convenience of unixpathname.
       */
      *(cp - diff - 1) = '\0';
    } else {
      *(cp - diff) = '\0';
    }
  }
}

/*
 * Name:	COM_openfile
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name which is following the Xerox
 *				 Lisp file naming convention.
 *				args[1] Recognition mode.  See IRM.
 *				args[2] Access mode.  See IRM.
 *				args[3]	The place where the creation date of the
 *				        opened file should be stored.
 *				args[4] The place where the size of the opened file
 *				        should be stored.
 *				args[5] The place where the error number should be
 *				        stored.
 *
 * Value:	If succeed, returns the Lisp smallp which represents the open
 *		file descriptor, otherwise Lisp NIL.
 *
 * Side Effect:	If succeed, cdate(args[3]) and size(args[4]) will hold the
 *		creation date and file size respectively.
 *
 * Description:
 *
 * The implementation of OPENFILE FDEV method for DSK and UNIX device.  Try to
 * open a specified file.
 */

LispPTR COM_openfile(register LispPTR *args)
{
  char lfname[MAXPATHLEN + 5], file[MAXPATHLEN], host[MAXNAMLEN];
  char dir[MAXPATHLEN], name[MAXNAMLEN], ver[VERSIONLEN];
  register int fatp, dskp, rval, fd, link_check_flg, flags, *bufp;
  struct stat sbuf;
#ifdef DOS
  char drive[1]; /* Drive designator */
  int extlen;    /* length of the raw file extension */
  char rawname[MAXNAMLEN];
#endif /* DOS */
  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[5]));

  LispStringLength(args[0], rval, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = fatp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add five for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);

#ifdef DOS
  separate_host(lfname, host, drive);
#else
  separate_host(lfname, host);
#endif /* DOS */
  UPCASE(host);

  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

/*
 * Convert a Lisp file name to UNIX one.  If host is DSK, we also have to
 * convert a version field.
 */
#ifdef DOS
  unixpathname(lfname, file, dskp, 0, drive, &extlen, rawname);
#else
  unixpathname(lfname, file, dskp, 0);
#endif

  /*
   * Set up the flags argument for open system call.
   * And we have to handle the non existing directory case if the device is
   * DSK.
   * link_check_flg is used to determine whether we have to check a hard-link
   * based version control after opening a file.
   */
  link_check_flg = 0;
  switch (args[1]) {
    case RECOG_OLD:
    case RECOG_OLDEST:
      switch (args[2]) {
        case ACCESS_INPUT: flags = O_RDONLY; break;

        case ACCESS_OUTPUT:
          /*
           * The current implementation of Lisp page mapped device requires
           * that the output stream being "readable"!
           */
          flags = O_RDWR | O_TRUNC;
          break;

        case ACCESS_BOTH: flags = O_RDWR; break;

        case ACCESS_APPEND:
          /*
           * Should be O_WRONLY | O_APPEND.  But Lisp needs it.
           */
          flags = O_RDWR;
          break;
      }
      break;
    case RECOG_NEW:
    case RECOG_OLD_NEW:
      /*
       * In DSK device, the not existing yet file can be recognized.  In this
       * case, if there is a chance to create a new file, we have to make
       * sure that all directory to reach the recognized file exists.
       * Also we have to check the versionless file is correctly maintained
       * or not when we have a chance to create a new file.
       */
      switch (args[2]) {
        case ACCESS_INPUT:
          if (args[1] == RECOG_NEW) {
            /*
             * Opening a input stream to a new, not yet
             * existing, file does not make sense.
             */
            return (NIL);
          } else {
            /*
             * Even if OLD/NEW recognition, opening a input
             * stream never try to create a new file.  Thus,
             * without O_CREAT.
             */
            flags = O_RDONLY;
          }
          break;

        case ACCESS_OUTPUT:
          flags = O_RDWR | O_TRUNC | O_CREAT;
          if (dskp) {
            unpack_filename(file, dir, name, ver, 1);
            if (make_directory(dir) == 0) return (NIL);
            link_check_flg = 1;
          }
          break;

        case ACCESS_BOTH:
          flags = O_RDWR | O_CREAT;
          if (dskp) {
            unpack_filename(file, dir, name, ver, 1);
            if (make_directory(dir) == 0) return (NIL);
            link_check_flg = 1;
          }
          break;

        case ACCESS_APPEND:
          flags = O_RDWR | O_CREAT;
          if (dskp) {
            unpack_filename(file, dir, name, ver, 1);
            if (make_directory(dir) == 0) return (NIL);
            link_check_flg = 1;
          }
          break;
      }
      break;

    default: return (NIL);
  }

  /*
   * The file name which has been passed from Lisp is sometimes different
   * from the actual file name on DSK, even after the Lisp name is converted
   * to UNIX form with unixpathname.  Lisp always recognize a file on DSK
   * with version.  If the versionless file exists and it is not correctly
   * maintained, that is it is not hard linked to the existing highest
   * versioned file, Lisp regards such link missing versionless file as
   * the highest versioned file, but the actual name on the file system
   * is still versionless.
   * get_old, get_oldest, get_new, get_old_new routines handle all of the
   * complicated cases correctly and let us know the "Lisp recognizing"
   * name and "Real" name.  Both of them are UNIX format.
   * At this point, we will use one of the four routines and get the
   * real name.  We can use it to open a file which is requested from Lisp
   * with the "Lisp recognizing" name.
   */

  if (dskp) {
    if (unpack_filename(file, dir, name, ver, 1) == 0) return (NIL);
    if (true_name(dir) != -1) return (0);
    if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);
    ConcNameAndVersion(name, ver, file);

    switch (args[1]) {
      case RECOG_OLD:
        if (get_old(dir, VersionArray, file, name) == 0) return (NIL);
        break;

      case RECOG_OLDEST:
        if (get_oldest(dir, VersionArray, file, name) == 0) return (NIL);
        break;

      case RECOG_NEW:
        if (get_new(dir, VersionArray, file, name) == 0) return (NIL);
        break;

      case RECOG_OLD_NEW:
        if (get_old_new(dir, VersionArray, file, name) == 0) return (NIL);
        break;

      default: return (NIL);
    }
  }

  /*
   * DSK device only allow to open a regular file.
   */
  if (dskp) {
    TIMEOUT(rval = stat(file, &sbuf));
    if (rval == 0) {
      if ((sbuf.st_mode & S_IFMT) != S_IFREG) {
        /*
         * The Lisp code handles this case as same as "file table
         * overflow" error.  Final error message is "File won't
         * open".
         */
        *Lisp_errno = ENFILE;
        return (NIL);
      }
    } else {
      /*
       * When stat failed, only if the reason is "file does not
       * exist" and we are trying to open a file with a mode we can
       * create a new file, we can proceed.
       */
      if (errno != ENOENT || !link_check_flg) {
        *Lisp_errno = errno;
        return (NIL);
      }
    }
  }
  if (dskp && link_check_flg) {
    /*
     * When we are opening a file with a mode we might create a new file,
     * we have to make sure that versionless file is maintained
     * correctly before we actually creating a new file, because a
     * created new file will change the status and the recognition on
     * the same file with the same recognition mode will return the
     * different result.
     * At this point, the third argument for maintain_version, forcep is
     * 1, because a lonly versionless file should be linked to version 1.
     * If we are opening a file recognized with new mode, version 2,
     * without pre-linking a versionless to version 1, the final
     * clean up maintain_version will link the versionless to version 3.
     */
    TIMEOUT(rval = access(file, F_OK));
    if (rval == -1) {
      if (errno == ENOENT) {
        /*
         * Actually we are creating a new file.  We have to
         * maintain a version status.
         */
        if (maintain_version(file, (FileName *)NULL, 1) == 0) {
          TIMEOUT(rval = close(fd));
          *Lisp_errno = errno;
          return (NIL);
        }
      } else {
        /*
         * Because of other reason, access call failed.
         */
        *Lisp_errno = errno;
        return (NIL);
      }
    } else {
/*
 * The subjective file has already existed.  We don't need
 * to maintain a version.
 */
#ifdef DOS
      if (args[1] == RECOG_NEW) {
        char old[MAXPATHLEN];
        make_old_version(old, file);
        unlink(old);
        rename(file, old); /* make old version */
      }
#endif /* DOS */
    }
  }

  /*
   * If a new file is created, its actual mode is computed from the
   * third argument for open and the process's umask.  I'm pretty sure
   * 0666 would be most appropriate mode to specify here.
   */
  TIMEOUT(fd = open(file, flags, 0666));
  if (fd == -1) {
    *Lisp_errno = errno;
    return (NIL);
  }

  if (dskp && link_check_flg) {
    /*
     * Again we have to maintain version to clean up the directory.
     * This time we invoke maintain_version with forcep off, because
     * the entirely newly created file, versionless file, should not
     * be linked to any file.
     */
    if (maintain_version(file, (FileName *)NULL, 0) == 0) {
      TIMEOUT(close(fd));
      *Lisp_errno = errno;
      return (NIL);
    }
  }

  TIMEOUT(rval = fstat(fd, &sbuf));
  if (rval == -1) {
    TIMEOUT(close(fd));
    *Lisp_errno = errno;
    return (NIL);
  }

  bufp = (int *)(Addr68k_from_LADDR(args[3]));
  *bufp = ToLispTime(sbuf.st_mtime);

  bufp = (int *)(Addr68k_from_LADDR(args[4]));
  if (!dskp && ((sbuf.st_mode & S_IFMT) != S_IFREG) && ((sbuf.st_mode & S_IFMT) != S_IFDIR)) {
    /*
     * Not a regular file or directory file.  Put on a marker.
     */
    *bufp = SPECIALFILEMARK;
  } else {
    *bufp = sbuf.st_size;
  }

  return (GetSmallp(fd));
}

/*
 * Name:	COM_closefile
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name which is following the Xerox
 *				 Lisp file naming convention.  Including a host
 *				 field.
 *				args[1]
 *				 The Lisp integer representing a file descriptor
 *				 of the file being closed.
 *				args[2]
 *				 The creation date of the file.
 *				args[3]
 *				 The place where the error number should be
 *				 stored.
 *
 * Value:	If succeed, returns the Lisp T otherwise, Lisp NIL.
 *
 * Side Effect:	None.
 *
 * Description:
 *
 * The implementation of CLOSEFILE FDEV method for DSK and UNIX device.  Try to
 * close a specified file.
 * The creation date file attribute in Lisp sense is kept in st_mdate field of
 * UNIX structure.  To keep the creation date of the file, it is maintained by
 * Lisp and it is passed to COM_closefile.
 */

LispPTR COM_closefile(register LispPTR *args)
{
#ifdef DOS

  register int fd, dskp, rval;
  time_t cdate;
  char lfname[MAXPATHLEN + 5], host[MAXNAMLEN];
  char file[MAXPATHLEN], dir[MAXPATHLEN], name[MAXNAMLEN + 1];
  char ver[VERSIONLEN], drive[1];
  struct find_t dirp;
  int dp;
  struct stat sbuf;
  ino_t ino;
  int extlen;
  char rawname[MAXNAMLEN];

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  LispStringLength(args[0], rval, dskp);

  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = dskp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add five for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);

  separate_host(lfname, host, drive);

  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

  /*
   * Convert a Lisp file name to UNIX one.  If host is DSK, we also have to
   * convert a version field.
   */
  dskp ? unixpathname(lfname, file, 1, 0, drive, &extlen, rawname)
       : unixpathname(lfname, file, 0, 0, drive, &extlen, rawname);
  fd = LispNumToCInt(args[1]);
  cdate = (time_t)LispNumToCInt(args[2]);
  if (!dskp) {
    TIMEOUT(rval = fstat(fd, &sbuf));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (NIL);
    }
  }

  if (cdate == 0) {
    /* Just close. */
    TIMEOUT(rval = close(fd));
    if (rval == -1) {
      if (!dskp && errno == EPERM && (sbuf.st_mode & S_IFREG) == 0) {
        /*
         * On {UNIX} device, closing a special file we are not
         * the owner of it.  Although I don't think close fails
         * because of EPERM, in honor of Medley 1.1 code, I put
         * this segment here.
         */
        return (ATOM_T);
      } else {
        *Lisp_errno = errno;
        return (NIL);
      }
    } else {
      return (ATOM_T);
    }
  }

  if (!unpack_filename(file, dir, name, ver, 1)) return (NIL);

  if (dskp) {
    /*
     * On {DSK}, we have to make sure dir is case sensitively existing
     * directory.
     */
    if (true_name(dir) != -1) return (NIL);

    /*
     * There is a very troublesome problem here.  The file name Lisp
     * recognizes is not always the same as the name which COM_openfile
     * used to open the file.  Sometimes COM_openfile uses the versionless
     * file name to open a file, although Lisp always recognizes with
     * *versioned* file name.
     * Thus, we compare i-node number of the requested file with ones of all
     * of files on the directory.   This is time spending implementation.
     * More clean up work is needed.
     */
    TIMEOUT(rval = fstat(fd, &sbuf));
    if (rval != 0) {
      *Lisp_errno = errno;
      return (NIL);
    }
    ino = sbuf.st_ino;
    TIMEOUT(rval = _dos_findfirst(dir, _A_SUBDIR, &dirp));
    if (rval < 0) {
      *Lisp_errno = errno;
      return (NIL);
    }

    for (; rval == 0; S_TOUT(rval = _dos_findnext(&dirp))) {
      sprintf(file, "%s\\%s", dir, dirp.name);
    }
  }
#ifndef DOS /* effectively NEVER, since we're in an ifdef DOS */
  time[0].tv_sec = (long)sbuf.st_atime;
  time[0].tv_usec = 0L;
  time[1].tv_sec = (long)ToUnixTime(cdate);
  time[1].tv_usec = 0L;
#endif /* DOS */
  TIMEOUT(rval = close(fd));
  if (rval == -1) {
    *Lisp_errno = errno;
    return (NIL);
  }
#ifndef DOS
  TIMEOUT(rval = utimes(file, time));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (NIL);
  }
#endif /* DOS, internal */
#else  /* UNIX version of CLOSEFILE */
  register int fd, fatp, dskp, rval;
  time_t cdate;
  char lfname[MAXPATHLEN + 5], host[MAXNAMLEN];
  char file[MAXPATHLEN], dir[MAXPATHLEN], name[MAXNAMLEN + 1];
  char ver[VERSIONLEN];
  register DIR *dirp;
  register struct dirent *dp;
  struct stat sbuf;
  struct timeval time[2];
  ino_t ino;

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  LispStringLength(args[0], rval, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = fatp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add five for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);

  separate_host(lfname, host);
  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

  /*
   * Convert a Lisp file name to UNIX one.  If host is DSK, we also have to
   * convert a version field.
   */
  dskp ? unixpathname(lfname, file, 1, 0) : unixpathname(lfname, file, 0, 0);

  fd = LispNumToCInt(args[1]);
  cdate = (time_t)LispNumToCInt(args[2]);

  if (!dskp) {
    TIMEOUT(rval = fstat(fd, &sbuf));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (NIL);
    }
  }

  if (cdate == 0) {
    /* Just close. */
    TIMEOUT(rval = close(fd));
    if (rval == -1) {
      if (!dskp && errno == EPERM && (sbuf.st_mode & S_IFREG) == 0) {
        /*
         * On {UNIX} device, closing a special file we are not
         * the owner of it.  Although I don't think close fails
         * because of EPERM, in honor of Medley 1.1 code, I put
         * this segment here.
         */
        return (ATOM_T);
      } else {
        *Lisp_errno = errno;
        return (NIL);
      }
    } else {
      return (ATOM_T);
    }
  }

  if (!unpack_filename(file, dir, name, ver, 1)) return (NIL);

  if (dskp) {
    /*
     * On {DSK}, we have to make sure dir is case sensitively existing
     * directory.
     */
    if (true_name(dir) != -1) return (NIL);

    /*
     * There is a very troublesome problem here.  The file name Lisp
     * recognizes is not always the same as the name which COM_openfile
     * used to open the file.  Sometimes COM_openfile uses the versionless
     * file name to open a file, although Lisp always recognizes with
     * *versioned* file name.
     * Thus, we compare i-node number of the requested file with ones of all
     * of files on the directory.   This is time spending implementation.
     * More clean up work is needed.
     */
    TIMEOUT(rval = fstat(fd, &sbuf));
    if (rval != 0) {
      *Lisp_errno = errno;
      return (NIL);
    }
    ino = sbuf.st_ino;

    errno = 0;
    TIMEOUT0(dirp = opendir(dir));
    if (dirp == (DIR *)NULL) {
      *Lisp_errno = errno;
      return (NIL);
    }

    for (S_TOUT(dp = readdir(dirp)); dp != (struct dirent *)NULL || errno == EINTR;
         errno = 0, S_TOUT(dp = readdir(dirp)))
      if (dp) {
        if (ino == (ino_t)dp->d_ino) sprintf(file, "%s/%s", dir, dp->d_name);
      }
    TIMEOUT(closedir(dirp));
  }

  time[0].tv_sec = (long)sbuf.st_atime;
  time[0].tv_usec = 0L;
  time[1].tv_sec = (long)ToUnixTime(cdate);
  time[1].tv_usec = 0L;

  TIMEOUT(rval = close(fd));
  if (rval == -1) {
    *Lisp_errno = errno;
    return (NIL);
  }

  TIMEOUT(rval = utimes(file, time));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (NIL);
  }

#endif /* DOS */

  return (ATOM_T);
}

/*
 * Name:	DSK_getfilename
 *
 * Argument:	LispPTR	*args	args[0] Full file name in Lisp format.
 *				args[1] Recognition mode.  See IRM.
 *				args[2] Name area where the recognized full file name
 *				 will be stored.
 *				args[3] The place where the error number should be
 *				 stored.
 *
 * Value:	If succeed, returns the Lisp smallp which represents the length
 *		of the recognized full file name, otherwise Lisp NIL.
 *
 * Side Effect:	If succeed, name area (args[2]) will be replaced with the
 *		recognized full file name.
 *
 * Description:
 *
 * The implementation of GETFILENAME FDEV method for DSK device.  Performs the
 * recognition on the specified name.  Does not check if OPENFILE actually
 * can open the file with the specified mode or not.
 */

LispPTR DSK_getfilename(register LispPTR *args)
{
  register char *base;
  register int len, dirp, rval;
  int fatp;
  char lfname[MAXPATHLEN];
  char aname[MAXNAMLEN];
  char vname[MAXPATHLEN];
  char file[MAXPATHLEN];
  char dir[MAXPATHLEN];
  char name[MAXNAMLEN];
  char ver[VERSIONLEN];
#ifdef DOS
  char drive[1], rawname[MAXNAMLEN];
  int extlen; /* len of extension, for making backup filename */
#endif        /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  LispStringLength(args[0], len, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  len = fatp ? len + 4 + 1 : len + 2 + 1;
  if (len > MAXPATHLEN) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);

#ifdef DOS
  separate_drive(lfname, drive);
#endif

/*
 * Convert a Lisp file name to UNIX one.  This is a DSK device method.
 * Thus we have to convert a version field too.  Third argument for
 * unixpathname specifies it.
 */
#ifdef DOS
  if (unixpathname(lfname, file, 1, 0, drive, &extlen, rawname) == 0) return (NIL);
#else
  if (unixpathname(lfname, file, 1, 0) == 0) return (NIL);
#endif

  if (unpack_filename(file, dir, name, ver, 1) == 0) return (NIL);

  switch (args[1]) {
    case RECOG_OLD:
      /*
       * "Old" file means the "newest existing" file.  Thus, we have to
       * check dir is an existing directory or not.  The search has to
       * be done in case insensitive manner.  true_name does this work.
       */

      if (true_name(dir) != -1) {
        /* No such directory. */
        return (NIL);
      }
      /*
       * At this point, true_name has converted dir to the "true" name
       * of the directory.
       */
      if (strcmp(name, "") == 0) {
        /*
         * The file name is specified with a trail directory delimiter.
         * We should recognize it as a directory.
         */
        strcpy(aname, dir);
        strcpy(vname, dir);
        dirp = 1;
      } else {
        /*
         * Recognizing a file on DSK device needs the version information.
         * We gather version information in a version array first.
         */
        if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);

        ConcNameAndVersion(name, ver, aname);
        if (get_old(dir, VersionArray, aname, vname) == 0) return (NIL);

        if ((rval = true_name(aname)) == 0) return (NIL);
        if (rval == -1) {
          /*
           * The specified file is a directory file.
           */
          strcpy(vname, aname);
          dirp = 1;
        } else {
#ifdef DOS
          strcpy(vname, aname);
#endif
          dirp = 0;
        }
      }

      break;

    case RECOG_OLDEST:
      /*
       * "Oldest" file means the "oldest existing" file.  Thus, we have to
       * check dir is an existing directory or not.
       */
      if (true_name(dir) != -1) {
        /* No such directory. */
        return (NIL);
      }
      if (strcmp(name, "") == 0) {
        /*
         * The file name is specified with a trail directory delimiter.
         * We should recognize it as a directory.
         */
        strcpy(aname, dir);
        strcpy(vname, dir);
        dirp = 1;
      } else {
        if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);

        ConcNameAndVersion(name, ver, aname);
        if (get_oldest(dir, VersionArray, aname, vname) == 0) return (NIL);

        if ((rval = true_name(aname)) == 0) return (NIL);
        if (rval == -1) {
          /*
           * The specified file is a directory file.
           */
          strcpy(vname, aname);
          dirp = 1;
        } else {
#ifdef DOS
          strcpy(vname, aname);
#endif
          dirp = 0;
        }
      }
      break;

    case RECOG_NEW:
      /*
       * "New" file means the "not existing" file.  Thus it is not
       * necessary that dir is an existing directory.  If dir is not
       * an existing directory, we returns the specified file name
       * as if, the subsequent OPENFILE will find the truth.
       */
      if (true_name(dir) != -1) {
        strcpy(vname, file);
        dirp = 0;
      } else if (strcmp(name, "") == 0) {
        /*
         * The file name is specified with a trail directory delimiter.
         * We should recognize it as a directory.
         */
        strcpy(aname, dir);
        strcpy(vname, dir);
        dirp = 1;
      } else {
        ConcDirAndName(dir, name, aname);
        if ((rval = true_name(aname)) == -1) {
          strcpy(vname, aname);
          dirp = 1;
        } else {
          /*
           * Here, dir is an existing directory.  We have to perform
           * "new" recognition with the version information.
           */
          if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);

          ConcNameAndVersion(name, ver, aname);
          if (get_new(dir, VersionArray, aname, vname) == 0) return (NIL);
          dirp = 0;
        }
      }
      break;

    case RECOG_OLD_NEW:
      /*
       * "Old/new" file means the "newest existing" or "not existing" file.
       * Thus, if dir is not an existing directory, we can return the
       * specified file name.  If it is an existing one, we have to
       * try "old" recognition on the directory first.  If the recognition
       * fails, we try "new" recognition.
       */
      if (true_name(dir) != -1) {
        strcpy(vname, file);
        dirp = 0;
      } else {
        ConcDirAndName(dir, name, aname);
        if ((rval = true_name(aname)) == -1) {
          strcpy(vname, aname);
          dirp = 1;
        } else {
          if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);

          ConcNameAndVersion(name, ver, aname);
          if (get_old_new(dir, VersionArray, aname, vname) == 0) return (NIL);
          dirp = 0;
        }
      }
      break;

    case RECOG_NON:
      /*
       * "Non" recognition is used to recognize a sysout file.  The sysout
       * file is dealt with specially, it does not have any version, even
       * if it is on {DSK} device.  Only we have to do here is to make
       * sure the path to reach to the specified file is an existing
       * directories.  The file name itself is recognized as if.
       */
      if (true_name(dir) != -1) return (NIL);
      ConcDirAndName(dir, name, vname);
      strcpy(aname, vname);
      if (true_name(aname) == -1) {
        strcpy(vname, aname);
        dirp = 1;
      } else {
        dirp = 0;
      }
      if (lisppathname(vname, lfname, dirp, 0) == 0) return (NIL);
      STRING_BASE(args[2], base);
      len = strlen(lfname);

#ifndef BYTESWAP
      strncpy(base, lfname, len + 1);
#else
      StrNCpyFromCToLisp(base, lfname, len + 1);
#endif /* BYTESWAP */

      return (GetSmallp(len));
  }

  /*
   * DSK device does not recognize a directory file as a file.  Thus we should
   * return NIL when the recognized file is a directory.
   */
  if (dirp) return (NIL);

/*
 * Now, vname holds the "versioned" full name of the recognized file in UNIX
 * format.  We have to convert it back to Lisp format.  The version field
 * have to be converted.  The fourth argument for lisppathname specifies it.
 */
#ifdef DOS
  /* For DOS, have to assure we use the name asked for, not the */
  /* faked-up oversion-0 name, so reported names match. */
  {
    char dver[VERSIONLEN];
    separate_version(vname, dver, 0);
    ConcDirAndName(dir, name, aname);
    ConcNameAndVersion(aname, dver, vname);
  }
#endif /* DOS */

  if (lisppathname(vname, lfname, dirp, (dirp ? 0 : 1)) == 0) return (NIL);

  STRING_BASE(args[2], base);
  len = strlen(lfname);

#ifndef BYTESWAP
  strncpy(base, lfname, len + 1);
#else
  StrNCpyFromCToLisp(base, lfname, len + 1);
#endif /* BYTESWAP */

  return (GetSmallp(len));
}

/*
 * Name:	DSK_deletefile
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name in Lisp format.
 *				args[1]
 *				 The place where the error number should be
 *				 stored.
 *
 * Value:	If succeed, returns the Lisp symbol T, otherwise Lisp NIL.
 *
 * Side Effect:	If succeed, the specified file is unlinked.
 *
 * Description:
 *
 * The implementation of DELETEFILE FDEV method for DSK device.  Try to delete
 * a specified file.
 */

LispPTR DSK_deletefile(register LispPTR *args)
{
  char file[MAXPATHLEN], fbuf[MAXPATHLEN], vless[MAXPATHLEN];
  char dir[MAXPATHLEN], ver[VERSIONLEN];
  int rval, fatp;
  register FileName *varray;
#ifdef DOS
  char drive[1], rawname[MAXNAMLEN];
  int extlen; /* len of extension, for making backup filename */
#endif        /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[1]));

  LispStringLength(args[0], rval, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = fatp ? rval + 4 + 1 : rval + 2 + 1;
  if (rval > MAXPATHLEN) FileNameTooLong(NIL);

  LispStringToCString(args[0], fbuf, MAXPATHLEN);
#ifdef DOS
  separate_drive(fbuf, drive);
  unixpathname(fbuf, file, 1, 0, drive, &extlen, rawname);
#else
  unixpathname(fbuf, file, 1, 0);
#endif

  if (unpack_filename(file, dir, fbuf, ver, 1) == 0) return (NIL);
  if (get_version_array(dir, fbuf, VersionArray, &VArrayInfo) == 0) return (NIL);
  varray = VersionArray;

  if (NoFileP(varray))
    return (NIL); /*
                   * If the specified file is deleted from
                   * outside of Lisp during the last time
                   * Lisp recognize it and now, this case
                   * will occur.
                   */

  /*
   * Although the file should have been recognized with "oldest" mode in Lisp
   * code, we have to recognize it again to know the "real" accessible name
   * of it.
   */

  ConcNameAndVersion(fbuf, ver, file);
  if (get_oldest(dir, varray, file, fbuf) == 0) return (NIL);

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file.  All we have to do is to simply
     * try to unlink the specified file.
     */
    TIMEOUT(rval = unlink(file));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (NIL);
    }
    return (ATOM_T);
  }

  /*
   * If a versionless file exists, we have to check the link status of it,
   * because deleting a versionless file or a file to which a versionless
   * file is linked will destroy the consistency of the version status.
   */

  if (check_vless_link(vless, varray, fbuf, &rval) == 0) return (NIL);

  if (strcmp(file, vless) == 0 || strcmp(file, fbuf) == 0) {
    if (*fbuf != '\0') {
      /*
       * Both of the versionless file and the file to which the
       * versionless file is linked have to be unlinked.
       */
      TIMEOUT(rval = unlink(vless));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (NIL);
      }
      TIMEOUT(rval = unlink(fbuf));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (NIL);
      }
      /*
       * Finally, we have to maintain the version status.
       */
      if (maintain_version(vless, (FileName *)NULL, 0) == 0) return (NIL);
      return (ATOM_T);
    } else {
      /*
       * Although the versionfile is specified, it is not linked
       * to any file in varray.  We should not maintain the version
       * status after deleting the versionless file, because
       * we cannot say whether the versionless file is actually under
       * control of the Medley DSK file system or not.
       */
      TIMEOUT(rval = unlink(vless));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (NIL);
      }
      return (ATOM_T);
    }
  } else {
    /*
     * Just unlink the specified file.
     */
    TIMEOUT(rval = unlink(file));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (NIL);
    }
    return (ATOM_T);
  }
}

/*
 * Name:	DSK_renamefile
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name in Lisp format.  The file which
 *				 is being renamed.
 *				args[1]
 *				 Full file name in Lisp format.  The file to which
 *				 args[0] is being renamed.
 *				args[2]
 *				 The place where the error number should be
 *				 stored.
 *
 * Value:	If succeed, returns the Lisp symbol T, otherwise Lisp NIL.
 *
 * Side Effect:	If succeed, the specified file is unlinked.
 *
 * Description:
 *
 * The implementation of RENAMEFILE FDEV method for DSK device.  Try to rename
 * a specified file.
 */

LispPTR DSK_renamefile(register LispPTR *args)
{
  char src[MAXPATHLEN], dst[MAXPATHLEN];
  char fbuf[MAXPATHLEN], vless[MAXPATHLEN], svless[MAXPATHLEN];
  char dir[MAXPATHLEN], ver[VERSIONLEN];
  int rval, fatp;
  register int need_maintain_flg;
  register FileName *varray;
#ifdef DOS
  char drive1[1], drive2[1];
  int extlen1, extlen2; /* len of extension */
  char rawname1[MAXNAMLEN], rawname2[MAXNAMLEN];
#endif /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[2]));

  LispStringLength(args[0], rval, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = fatp ? rval + 4 + 1 : rval + 2 + 1;
  if (rval > MAXPATHLEN) FileNameTooLong(NIL);

  LispStringLength(args[1], rval, fatp);
  rval = fatp ? rval + 4 + 1 : rval + 2 + 1;
  if (rval > MAXPATHLEN) FileNameTooLong(NIL);

  LispStringToCString(args[0], fbuf, MAXPATHLEN);
#ifdef DOS
  separate_drive(fbuf, drive1);
  unixpathname(fbuf, src, 1, 0, drive1, &extlen1, rawname1);
#else  /* DOS */
  unixpathname(fbuf, src, 1, 0);
#endif /* DOS */

  LispStringToCString(args[1], fbuf, MAXPATHLEN);
#ifdef DOS
  separate_drive(fbuf, drive2);
  unixpathname(fbuf, dst, 1, 0, drive2, &extlen2, rawname2);
#else  /* DOS */
  unixpathname(fbuf, dst, 1, 0);
#endif /* DOS */

  if (unpack_filename(dst, dir, fbuf, ver, 1) == 0) return (NIL);
  /*
   * The destination file has been recognized as new file.  Thus we have
   * to make sure that the directory exists.
   */
  if (make_directory(dir) == 0) return (NIL);

  /*
   * We maintain the destination to handle the link damaged case correctly.
   */
  ConcDirAndName(dir, fbuf, dst);
  if (maintain_version(dst, (FileName *)NULL, 0) == 0) return (NIL);

  if (get_version_array(dir, fbuf, VersionArray, &VArrayInfo) == 0) return (NIL);
  varray = VersionArray;

  /*
   * Although the file should have been recognized with "new" mode in Lisp
   * code, we have to recognize it again to know the "real" accessible name
   * of it.
   */

  ConcNameAndVersion(fbuf, ver, dst);
  if (get_new(dir, varray, dst, fbuf) == 0) return (NIL);

  /*
   * At this point, there are three cases for the destination.  If there is
   * no member of the destination family, there is nothing to do.  If there
   * is only a versionless file and, if the "real" destination is not the
   * versionless, we have to rename it to version 1.  And last case, if the
   * "real" destination file is the file to which the versionless file is linked,
   * we have to unlink the versionless file.
   */
  if (!NoFileP(varray)) {
    if (OnlyVersionlessP(varray)) {
      get_versionless(varray, vless, dir);
      if (strcmp(dst, vless) != 0) {
        ConcNameAndVersion(vless, "1", fbuf);
        TIMEOUT(rval = rename(vless, fbuf));
        if (rval == -1) {
          *Lisp_errno = errno;
          return (NIL);
        }
      }
    } else {
      /*
       * We are sure that the versionless file is linked to one of
       * the higher versioned file here.
       */
      get_versionless(varray, vless, dir);
      if (check_vless_link(vless, varray, fbuf, &rval) == 0) { return (NIL); }
      if (strcmp(dst, fbuf) == 0) {
        TIMEOUT(rval = unlink(vless));
        if (rval == -1) {
          *Lisp_errno = errno;
          return (NIL);
        }
      }
    }
  }

  if (unpack_filename(src, dir, fbuf, ver, 1) == 0) return (NIL);
  if (get_version_array(dir, fbuf, varray, &VArrayInfo) == 0) return (NIL);

  if (NoFileP(varray))
    return (NIL); /*
                   * If the specified file is deleted from
                   * outside of Lisp during the last time
                   * Lisp recognize it and now, this case
                   * will occur.
                   */

  /*
   * Although the file should have been recognized with "old" mode in Lisp
   * code, we have to recognize it again to know the "real" accessible name
   * of it.
   */
  ConcNameAndVersion(fbuf, ver, src);
  if (get_old(dir, varray, src, fbuf) == 0) return (NIL);

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file.  All we have to do is to simply
     * try to rename the specified file.
     */
    need_maintain_flg = 0;
  } else {
    /*
     * If a versionless file exists, we have to check the link status
     * of it, because renaming a versionless file or a file to which a
     * versionless file is linked will destroy the consistency of the
     * version status.
     */
    if (check_vless_link(vless, varray, fbuf, &rval) == 0) return (NIL);

    if (strcmp(src, vless) == 0 && *fbuf != '\0') {
      /*
       * At this point, we only unlink the file to which the
       * versionless is linked.  The versionless fill will be
       * renamed later.
       */
      TIMEOUT(rval = unlink(fbuf));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (NIL);
      }
      need_maintain_flg = 1;
    } else if (strcmp(src, fbuf) == 0) {
      TIMEOUT(rval = unlink(vless));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (NIL);
      }
      need_maintain_flg = 1;
    } else {
      need_maintain_flg = 0;
    }
    strcpy(svless, vless);
  }

  /*
   * At this point, src holds the full file name to be renamed, and dst holds
   * the full file name to which src will be renamed.
   */

  TIMEOUT(rval = rename(src, dst));
  if (rval == -1) {
    *Lisp_errno = errno;
    return (NIL);
  }

  /*
   * The destination directory always have to be maintained, because it is
   * now under control of DSK device.
   * The source directory have to be maintained only if need_maintain_flg
   * is on.
   */

  if (maintain_version(dst, (FileName *)NULL, 0) == 0) return (NIL);
  if (need_maintain_flg) {
    if (maintain_version(src, (FileName *)NULL, 0) == 0) return (NIL);
  }

  return (ATOM_T);
}

/*
 * Name:	DSK_directorynamep
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Directory name in Lispformat.  Both of the initial
 *				 and trail directory delimiter are stripped by Lisp
 *				 code.  Only one exception is a "root directory".
 *				 "Root directory is represented as ">".
 *				args[1]
 *				 The place where the "true" name of the directory
 *				 in Lisp format will be stored.
 *				args[2]
 *				 The place where the error number should be stored.
 *				 Not used in the current Lisp code implementation.
 *
 * Value:	If succeed, returns the Lisp smallp which represents the length
 *		of the "true" name of the directory, otherwise Lisp NIL.
 *
 * Side Effect:	If the directory is recognized as a valid directory representation,
 *		args[1] is replaced with the "true" directory name.
 *
 * Description:
 *
 * The implementation of the DIRECTORYNAMEP FDEV method for DSK device.
 * Performs the recognition as well. Accepts the directory representation which
 * obeys the Xerox Lisp file naming convention. The "true" name which is stored
 * on the area specified with the second argument also follows the Xerox Lisp
 * file naming convention, and it includes the initial and trail directory
 * delimiter. Thus the Lisp code does not have to worry about the conversion of
 * the directory name representation.
 */

LispPTR DSK_directorynamep(register LispPTR *args)
{
  char dirname[MAXPATHLEN];
  char fullname[MAXPATHLEN];
  register int len, fatp;
  register char *base;
#ifdef DOS
  char drive[1], rawname[MAXNAMLEN];
  int extlen; /* len of extension, for making backup filename */
#endif        /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[2]));

  LispStringLength(args[0], len, fatp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  len = fatp ? len + 4 + 1 : len + 2 + 1;
  /* -2 for the initial and trail directory delimiter. */
  if (len > MAXPATHLEN - 2) FileNameTooLong(NIL);

  LispStringToCString(args[0], dirname, MAXPATHLEN);

/* Convert Xerox Lisp file naming convention to Unix one. */
#ifdef DOS
  separate_drive(dirname, drive);
  if (unixpathname(dirname, fullname, 1, 0, drive, 0, 0) == 0) return (NIL);
#else  /* DOS*/
  if (unixpathname(dirname, fullname, 1, 0) == 0) return (NIL);
#endif /* DOS */

  if (true_name(fullname) != -1) return (NIL);

  /* Convert Unix file naming convention to Xerox Lisp one. */
  if (lisppathname(fullname, dirname, 1, 0) == 0) return (NIL);

  len = strlen(dirname);
  STRING_BASE(args[1], base);

#ifndef BYTESWAP
  strncpy(base, dirname, len + 1);
#else
  StrNCpyFromCToLisp(base, dirname, len + 1);
#endif /* BYTESWAP */

  return (GetSmallp(len));
}

/*
 * Name:	COM_getfileinfo
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name which is following the Xerox
 *				 Lisp file naming convention.  Including a host
 *				 field.
 *				args[1]
 *				 The Lisp pointer which represents the requested
 *				 file attribute.
 *				args[2]
 *				 The place where the requested value will be stored.
 *				args[3]
 *				 The place where the errno will be stored.
 *
 * Value:	If failed, returns Lisp NIL.  If succeed, returned value is
 *		different according to the attribute requested.
 *		In the case of LENGTH, WDATE, RDATE, and PROTECTION, returns Lisp T.
 *		In the case of AUTHOR and ALL, returns the length of the author name
 *		copied into the specified buffer.
 *
 * Side Effect:	The specified buffer will be replaced with the value of the requested
 *		attribute.
 *
 * Description:
 *
 * The implementation of GETFILEINFO FDEV method for DSK and UNIX device.  Try to
 * fetch a value of a specified file attribute.
 * The creation date file attribute in Lisp sense is kept in st_mdate field of
 * UNIX structure, and it is treated as same as the write date file attribute in
 * Lisp sense.
 */

LispPTR COM_getfileinfo(register LispPTR *args)
{
  register int dskp, rval;
  register unsigned *bufp;
#ifndef DOS
  register struct passwd *pwd;
#endif
  register char *base;
  char lfname[MAXPATHLEN + 5], file[MAXPATHLEN], host[MAXNAMLEN];
  char dir[MAXPATHLEN], name[MAXNAMLEN], ver[VERSIONLEN];
  struct stat sbuf;
  LispPTR laddr;
#ifdef DOS
  char drive[1], rawname[MAXNAMLEN];
  int extlen; /* len of extension, for making backup filename */
#endif        /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  LispStringLength(args[0], rval, dskp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = dskp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add 5 for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);
#ifdef DOS
  separate_host(lfname, host, drive);
#else
  separate_host(lfname, host);
#endif

  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

/*
 * Convert a Lisp file name to UNIX one.  If host is DSK, we also have to
 * convert a version field.
 */
#ifdef DOS
  unixpathname(lfname, file, dskp, 0, drive, &extlen, rawname);
#else  /* DOS */
  unixpathname(lfname, file, dskp, 0);
#endif /* DOS */

  /*
   * The file name which has been passed from Lisp is sometimes different
   * from the actual file name on DSK, even after the Lisp name is converted
   * to UNIX form with unixpathname.  The Lisp code for GETFILEINFO always
   * recognizes a file with old mode.  Thus, we recognize it again using
   * get_old routine.  It will let us know the "real accessible" name of
   * the file.
   */
  if (dskp) {
    if (unpack_filename(file, dir, name, ver, 1) == 0) return (NIL);
    if (true_name(dir) != -1) return (0);
    if (strcmp(name, "") == 0) {
      /*
       * The directory is specified.
       */
      strcpy(file, dir);
    } else {
      if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);
      ConcNameAndVersion(name, ver, file);
      if (get_old(dir, VersionArray, file, name) == 0) return (NIL);
    }
  }

  TIMEOUT(rval = stat(file, &sbuf));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (NIL);
  }

  switch (args[1]) {
    case LENGTH:
      bufp = (unsigned *)(Addr68k_from_LADDR(args[2]));
      *bufp = (unsigned)sbuf.st_size;
      return (ATOM_T);

    case WDATE:
      bufp = (unsigned *)(Addr68k_from_LADDR(args[2]));
      *bufp = (unsigned)ToLispTime(sbuf.st_mtime);
      return (ATOM_T);

    case RDATE:
      bufp = (unsigned *)(Addr68k_from_LADDR(args[2]));
      *bufp = (unsigned)ToLispTime(sbuf.st_atime);
      return (ATOM_T);

    case PROTECTION:
      bufp = (unsigned *)(Addr68k_from_LADDR(args[2]));
      *bufp = sbuf.st_mode;
      return (ATOM_T);

    case AUTHOR:
#ifndef DOS
      TIMEOUT(pwd = getpwuid(sbuf.st_uid));
      if (pwd == (struct passwd *)NULL) {
        /*
         * Returns Lisp 0.  Lisp code handles this case as author
         * unknown.  The returned value from Lisp GETFILEINFO
         * function would be "".
         */
        return (GetSmallp(0));
      }
      STRING_BASE(args[2], base);
      rval = strlen(pwd->pw_name);
#ifndef BYTESWAP
      strncpy(base, pwd->pw_name, rval);
#else
      StrNCpyFromCToLisp(base, pwd->pw_name, rval);
#endif /* BYTESWAP */
#endif /* DOS */
      return (GetSmallp(rval));

    case ALL:
      /*
       * The format of the buffer which has been allocated by Lisp
       * is as follows.
       * 	((LENGTH	.	fixp)
       *	 (WDATE		.	fixp)
       *	 (RDATE		.	fixp)
       *	 (PROTECTION	.	fixp)
       *	 (AUTHOR	.	string))
       */
      laddr = cdr(car(args[2]));
      bufp = (unsigned *)(Addr68k_from_LADDR(laddr));
      *bufp = sbuf.st_size;

      laddr = cdr(car(cdr(args[2])));
      bufp = (unsigned *)(Addr68k_from_LADDR(laddr));
      *bufp = ToLispTime(sbuf.st_mtime);

      laddr = cdr(car(cdr(cdr(args[2]))));
      bufp = (unsigned *)(Addr68k_from_LADDR(laddr));
      *bufp = ToLispTime(sbuf.st_atime);

      laddr = cdr(car(cdr(cdr(cdr(args[2])))));
      bufp = (unsigned *)(Addr68k_from_LADDR(laddr));
      *bufp = sbuf.st_mode;
#ifndef DOS
      TIMEOUT(pwd = getpwuid(sbuf.st_uid));
      if (pwd == (struct passwd *)NULL) { return (GetSmallp(0)); }
      laddr = cdr(car(cdr(cdr(cdr(cdr(args[2]))))));
      STRING_BASE(laddr, base);
      rval = strlen(pwd->pw_name);
#ifndef BYTESWAP
      strncpy(base, pwd->pw_name, rval);
#else
      StrNCpyFromCToLisp(base, pwd->pw_name, rval);
#endif /* BYTESWAP	 */
#endif /* DOS */
      return (GetSmallp(rval));

    default: return (NIL);
  }
}

/*
 * Name:	COM_setfileinfo
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file name which is following the Xerox
 *				 Lisp file naming convention.  Including a host
 *				 field.
 *				args[1]
 *				 The Lisp pointer which represents the requested
 *				 file attribute.
 *				args[2]
 *				 The value to be stored on the request attribute.
 *				args[3]
 *				 The place where the error number should be
 *				 stored.
 *
 * Value:	If succeed, returns Lisp T, otherwise Lisp NIL.
 *
 * Side Effect:	The specified attribute of a file will be replaced with the specified
 *		value.
 *
 * Description:
 *
 * The implementation of SETFILEINFO FDEV method for DSK and UNIX device.  Try to
 * replace a value of a specified file attribute.
 * In this implementation, only WDATE(as well as Creation Date) and PROTECTION
 * make sense.
 */

LispPTR COM_setfileinfo(register LispPTR *args)
{
  register int dskp, rval, date;
  char lfname[MAXPATHLEN + 5], file[MAXPATHLEN], host[MAXNAMLEN];
  char dir[MAXPATHLEN], name[MAXNAMLEN], ver[VERSIONLEN];
  struct stat sbuf;
#ifndef DOS
  struct timeval time[2];
#else
  char drive[1], rawname[MAXNAMLEN];
  int extlen;
#endif /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  LispStringLength(args[0], rval, dskp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = dskp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add 5 for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);

#ifdef DOS
  separate_host(lfname, host, drive);
#else
  separate_host(lfname, host);
#endif /* DOS */
  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

/*
 * Convert a Lisp file name to UNIX one.  If host is DSK, we also have to
 * convert a version field.
 */
#ifdef DOS
  unixpathname(lfname, file, dskp, 0, drive, &extlen, rawname);
#else  /* DOS */
  unixpathname(lfname, file, dskp, 0);
#endif /* DOS */

  /*
   * The file name which has been passed from Lisp is sometimes different
   * from the actual file name on DSK, even after the Lisp name is converted
   * to UNIX form with unixpathname.  The Lisp code for SETFILEINFO always
   * recognizes a file with old mode.  Thus, we recognize it again using
   * get_old routine.  It will let us know the "real accessible" name of
   * the file.
   */
  if (dskp) {
    if (unpack_filename(file, dir, name, ver, 1) == 0) return (NIL);
    if (true_name(dir) != -1) return (0);
    if (get_version_array(dir, name, VersionArray, &VArrayInfo) == 0) return (NIL);
    ConcNameAndVersion(name, ver, file);
    if (get_old(dir, VersionArray, file, name) == 0) return (NIL);
  }

  switch (args[1]) {
    case WDATE:
      TIMEOUT(rval = stat(file, &sbuf));
      if (rval != 0) {
        *Lisp_errno = errno;
        return (NIL);
      }
#ifndef DOS
      date = LispNumToCInt(args[2]);
      time[0].tv_sec = (long)sbuf.st_atime;
      time[0].tv_usec = 0L;
      time[1].tv_sec = (long)ToUnixTime(date);
      time[1].tv_usec = 0L;
      TIMEOUT(rval = utimes(file, time));
#endif /* DOS */
      if (rval != 0) {
        *Lisp_errno = errno;
        return (NIL);
      }
      return (ATOM_T);

    case PROTECTION:
      rval = LispNumToCInt(args[2]);
      TIMEOUT(rval = chmod(file, rval));
      if (rval != 0) {
        *Lisp_errno = errno;
        return (NIL);
      }
      return (ATOM_T);

    default: return (NIL);
  }
}

/*
 * Name:	COM_readpage
 *
 * Argument:	LispPTR	*args	args[0]
 *				 The Lisp integer representing a file descriptor
 *				 of the file being read.
 *				args[1]
 *				 The Lisp integer representing a page number of the
 *				 file being read.
 *				args[2]
 *				 The place where the contents of the file will be
 *				 stored.
 *				args[3]
 *				 The place where the error number should be stored.
 *
 * Value:	If succeed, returns a Lisp integer representing a total number of
 *		bytes read, otherwise Lisp NIL.
 *
 * Side Effect:	The specified buffer will be filled with the specified region of the
 *		contents of the file.
 *
 * Description:
 *
 * The implementation of READPAGES FDEV method for DSK and UNIX device.  Try to
 * read a page into a buffer.
 * If a page being read is a last page in a file, and it is not a full page, the
 * remaining region of a buffer will be zero outed.  The size of a page is 512 bytes.
 */

LispPTR COM_readpage(register LispPTR *args)
{
  register char *bufp;
  register char *bp;
  register int count, fd, npage, rval;
  struct stat sbuf;

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[3]));

  fd = LispNumToCInt(args[0]);
  npage = LispNumToCInt(args[1]);
  bufp = (char *)(Addr68k_from_LADDR(args[2]));

  TIMEOUT(rval = fstat(fd, &sbuf));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (NIL);
  }

  if ((sbuf.st_mode & S_IFREG) != 0) {
  /*
   * The request file is a regular file.  We have to make sure that
   * next byte read is at the beginning of the requested page of the
   * file.  If the request file is special file, lseek is not needed.
   */
  sklp:
    TIMEOUT(rval = lseek(fd, (npage * FDEV_PAGE_SIZE), SEEK_SET));
    if (rval == -1) {
      if (errno == EINTR) goto sklp; /* interrupted, retry */
      *Lisp_errno = errno;
      return (NIL);
    }
  }

rdlp:
  TIMEOUT(count = read(fd, bufp, FDEV_PAGE_SIZE));
  if (count == -1) {
    if (errno == EINTR) goto rdlp; /* interrupted; retry */
    *Lisp_errno = errno;
    return (NIL);
  }

  /* O out the remaining part of the buffer. */
  for (bp = &bufp[count], rval = count; rval < FDEV_PAGE_SIZE; ++rval) *bp++ = 0;

#ifdef BYTESWAP
  word_swap_page((DLword *)bufp, FDEV_PAGE_SIZE / 4);
#endif /* BYTESWAP */

  return (GetSmallp(count));
}

/*
 * Name:	COM_writepage
 *
 * Argument:	LispPTR	*args	args[0]
 *				 The Lisp integer representing a file descriptor
 *				 of the file being read.
 *				args[1]
 *				 The Lisp integer representing a page number of the
 *				 file being written.
 *				args[2]
 *				 The place in where the next date to be written is
 *				 hold.
 *				args[3]
 *				 The Lisp integer representing a number of bytes
 *				 of data to be written.
 *				args[4]
 *				 The place where the error number should be stored.
 *
 * Value:	If succeed, returns a Lisp T, otherwise Lisp NIL.
 *
 * Side Effect:	The specified page of the file will be replaced with the contents of
 *		the buffer.
 *
 * Description:
 *
 * The implementation of WRITEPAGES FDEV method for DSK and UNIX device.  Try to
 * write a page into a buffer.
 * The actual size of data written is specified with args[3].
 */

LispPTR COM_writepage(register LispPTR *args)
{
  register int fd;
  register int npage;
  register char *bufp;
  register int rval, count;

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[4]));

  fd = LispNumToCInt(args[0]);
  npage = LispNumToCInt(args[1]);
  bufp = (char *)(Addr68k_from_LADDR(args[2]));
  count = LispNumToCInt(args[3]);

sklp2:
  TIMEOUT(rval = lseek(fd, (npage * FDEV_PAGE_SIZE), SEEK_SET));
  if (rval == -1) {
    if (errno == EINTR) goto sklp2; /* interrupted; retry */
    *Lisp_errno = errno;
    return (NIL);
  }

/* OK to write the page. */

#ifdef BYTESWAP
  word_swap_page((DLword *)bufp, (count + 3) >> 2);
#endif /* BYTESWAP */

wlp:
  TIMEOUT(rval = write(fd, bufp, count));
  if (rval == -1) {
    if (errno == EINTR) goto wlp; /* interrupted; retry */
    *Lisp_errno = errno;
#ifdef BYTESWAP
    word_swap_page((DLword *)bufp, (count + 3) >> 2);
#endif /* BYTESWAP */
    return (NIL);
  }

#ifdef BYTESWAP
  word_swap_page((DLword *)bufp, (count + 3) >> 2);
#endif /* BYTESWAP */

  return (ATOM_T);
}

/*
 * Name:	COM_truncatefile
 *
 * Argument:	LispPTR	*args	args[0]
 *				 The Lisp integer representing a file descriptor
 *				 of the file being truncated.
 *				args[1]
 *				 The Lisp integer representing a requested length to be
 *				 truncated.
 *				args[2]
 *				 The place where the error number should be stored.
 *
 * Value:	If succeed, returns a Lisp T, otherwise Lisp NIL.
 *
 * Side Effect:	The length of the specified file will be the specified length.
 *
 * Description:
 *
 * The implementation of TRUNCATEFILE FDEV method for DSK and UNIX device.  Try to
 * truncate a file.
 */

LispPTR COM_truncatefile(register LispPTR *args)
{
  register int fd;
  register int length;
  register int rval;
  struct stat sbuf;

  ERRSETJMP(NIL);
  Lisp_errno = (int *)(Addr68k_from_LADDR(args[2]));

  fd = LispNumToCInt(args[0]);
  length = LispNumToCInt(args[1]);

  TIMEOUT(rval = fstat(fd, &sbuf));
  if (rval == -1) {
    *Lisp_errno = errno;
    return (NIL);
  }

  if ((sbuf.st_mode & S_IFREG) == 0) {
    /*
     * The request file is not a regular file.  We don't need to
     * truncate such file.
     */
    return (ATOM_T);
  }
  if ((off_t)length != sbuf.st_size) {
#ifdef DOS
    TIMEOUT(rval = chsize(fd, (off_t)length));
#else
    TIMEOUT(rval = ftruncate(fd, (off_t)length));
#endif /* DOS */
    if (rval != 0) {
      *Lisp_errno = errno;
      return (NIL);
    }

/*
 * TRUNCATEFILE FDEV method is invoked from FORCEOUTPUT Lisp function.
 * Thus we have to sync the file state here.
 */
#ifndef DOS
    TIMEOUT(rval = fsync(fd));
#endif

    if (rval != 0) {
      *Lisp_errno = errno;
      return (NIL);
    }
  }
  return (ATOM_T);
}

/*
 * Name:	COM_changedir
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full file directory name which is following the Xerox
 *				 Lisp file naming convention.  Including a host
 *				 field.
 *
 * Value:	If succeed, returns a Lisp T, otherwise Lisp NIL.
 *
 * Side Effect:	If succeed, the current directory of this Lisp process will be changed.
 *
 * Description:
 *
 * Change the current directory of the process to the specified directory.
 */

LispPTR COM_changedir(register LispPTR *args)
{
  register int dskp, rval;
  char lfname[MAXPATHLEN + 5], dir[MAXPATHLEN], host[MAXNAMLEN];
#ifdef DOS
  char drive[1], rawname[MAXNAMLEN];
  int extlen;
#endif /* DOS */

  ERRSETJMP(NIL);
  Lisp_errno = &Dummy_errno;

  LispStringLength(args[0], rval, dskp);
  /*
   * Because of the version number convention, Lisp pathname might
   * be shorter than UNIX one.  For THIN string, the difference
   * is 2 bytes, for FAT string, 4 bytes.  Add 1 byte for NULL
   * terminating character.
   */
  rval = dskp ? rval + 4 + 1 : rval + 2 + 1;
  /* Add 5 for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);
#ifdef DOS
  separate_host(lfname, host, drive);
#else
  separate_host(lfname, host);
#endif /* DOS */
  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

#ifdef DOS
  if (!unixpathname(lfname, dir, 0, 0, drive, 0, 0)) return (NIL);
#else  /* DOS */
  if (!unixpathname(lfname, dir, 0, 0)) return (NIL);
#endif /* DOS */

  if (dskp) {
    /*
     * If {DSK} device, the directory name can be specified in a case
     * insensitive manner.  We have to convert it into a right case.
     */
    if (true_name(dir) != -1) return (NIL);
  }

  TIMEOUT(rval = chdir(dir));
  if (rval != 0) return (NIL);
#ifdef DOS
  if (*drive) {
    if (*drive <= 'Z')
      rval = _chdrive(*drive - ('A' - 1));
    else
      rval = _chdrive(*drive - ('a' - 1));
    if (rval != 0) return (NIL);
  }
#endif /* DOS */
  return (ATOM_T);
}

/************************************************************************/
/*									*/
/*		     C O M _ g e t f r e e b l o c k			*/
/*									*/
/*									*/
/*									*/
/************************************************************************/

/*
 * Name:	COM_getfreeblock
 *
 * Argument:	LispPTR	*args	args[0]
 *				 Full sysout in Lisp format.  Including a host field.
 *				args[1]
 *				 The place where the available free block size will
 *				 be stored.
 *
 * Value:	If succeed, returns a Lisp T, otherwise Lisp NIL.
 *
 * Side Effect:	None.
 *
 * Description:
 *
 * Examines the statistics of the file system on which the specified sysout file
 * resides, and returns the number of available free blocks through the specified
 * buffer.
 * This routine is invoked just before the Lisp flushes its image on a sysout file so
 * as to make sure the Lisp image is gracefully written on a sysout file.  See
 * \MAIKO.CHECKFREESPACE in MOD44IO.
 * The version control based on a file naming convention is not applicable to a
 * sysout file, even if the sysout file is specified on {DSK} device.  The Lisp code
 * strips the version field by invoking DSK_getfilename with NON recognition mode.
 */

LispPTR COM_getfreeblock(register LispPTR *args)
{
  register int dskp, rval, *buf;
  char lfname[MAXPATHLEN + 5], dir[MAXPATHLEN], host[MAXNAMLEN];
  char name[MAXNAMLEN + 1], file[MAXPATHLEN], ver[VERSIONLEN];
#ifdef DOS
  char drive[2];
  struct diskfree_t sfsbuf;
#else
  struct statvfs sfsbuf;
#endif

  ERRSETJMP(NIL);
  Lisp_errno = &Dummy_errno;

  LispStringLength(args[0], rval, dskp);
  /*
   * Add 1 byte for NULL terminating character.
   */
  rval += 1;
  /* Add 5 for the host name field in Lisp format. */
  if (rval > MAXPATHLEN + 5) FileNameTooLong(NIL);

  LispStringToCString(args[0], lfname, MAXPATHLEN);
  buf = (int *)(Addr68k_from_LADDR(args[1]));
#ifdef DOS
  separate_host(lfname, host, drive);
#else
  separate_host(lfname, host);
#endif /* DOS */
  UPCASE(host);
  if (strcmp(host, "DSK") == 0)
    dskp = 1;
  else if (strcmp(host, "UNIX") == 0)
    dskp = 0;
  else
    return (NIL);

#ifdef DOS
  if (!unixpathname(lfname, file, 0, 0, drive, 0, 0)) return (NIL);
#else  /* DOS */
  if (!unixpathname(lfname, file, 0, 0)) return (NIL);
#endif /* DOS */

  if (!unpack_filename(file, dir, name, ver, 0)) return (NIL);

  if (dskp) {
    /*
     * Although Lisp code guarantees the directory is an existing one,
     * by calling DSK_getfilename, we check it again for safety.
     */
    if (true_name(dir) != -1) return (NIL);
  }

/*
 * The specified file itself might be a file being created newly.  Thus,
 * we check the available block size, using the directory on which the file
 * will be exist.
 */
#ifdef DOS
  /* For DOS, we have to use either the disk drive the file
     will be on, or the default drive. */

  if (drive[0]) {
    drive[1] = 0;
    UPCASE(drive);
    if (_dos_getdiskfree((unsigned)drive[0] - (int)'@', &sfsbuf))
      return (NIL); /* call failed, so name is invalid */

    *buf = sfsbuf.avail_clusters * sfsbuf.sectors_per_cluster * sfsbuf.bytes_per_sector;
  } else {
    if (_dos_getdiskfree(0, &sfsbuf)) return (NIL); /* call failed, so name is invalid */

    *buf = sfsbuf.avail_clusters * sfsbuf.sectors_per_cluster * sfsbuf.bytes_per_sector;
  }
#else
  TIMEOUT(rval = statvfs(dir, &sfsbuf));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (NIL);
  }
  *buf = sfsbuf.f_bavail;
#endif /* DOS */
  return (ATOM_T);
}

/********************************************
        Subroutines
********************************************/

/*
 * Name:	separate_version
 *
 * Argument:	char	*name   UNIX file name.  It is recommended to pass only
 *				"root file name" (i.e. without directory) as
 *				name argument, but full file name is also
 *				acceptable.
 *		char	*ver	The place where extracted version will be stored.
 *		init	checkp	If 1, whether the version field contains only
 *				numbers or not is checked.  If 0, anything in the
 *				version field is stored in ver.  If GENERATEFILE
 *				want to contain a wild card character in a version
 *				field, checkp should be 0.
 *
 * Value:	void
 *
 * Side Effect:	If name contains a valid version field, name and version will
 *		be set to name and version respectively.
 *
 * Description:
 *
 * Check if name contains the valid version field and if so, separate version
 * field from name field.  After this operation, name contains only name and
 * version contains its version.  If name does not contain a valid version
 * field, version will be NULL string.
 * If checkp is 1, the version field which contains only numeric characters are
 * regarded valid.
 *
 */

void separate_version(char *name, char *ver, int checkp)
{
  register char *start, *end, *cp;
  register int ver_no;
  size_t len;
  char ver_buf[VERSIONLEN];

  if ((end = (char *)strchr(name, '~')) != (char *)NULL) {
    start = end;
    cp = end + 1;
    while (*cp) {
      if (*cp == '~') {
        start = end;
        end = cp;
      }
      cp++;
    }

    if (start != end && *(start - 1) == '.' && end == (cp - 1)) {
      /*
       * name ends in the form ".~###~". But we have to check
       * ### are all numbers or not, if checkp is 1.
       */
      len = (end - start) - 1;
      strncpy(ver_buf, start + 1, len);
      ver_buf[len] = '\0';
      if (checkp) {
        NumericStringP(ver_buf, YES, NO);
      YES:
        /*
         * name contains a valid version field.
         */
        *(start - 1) = '\0';
        *end = '\0';
        /*
         * Use atoi to eliminate leading 0s.
         */
        ver_no = atoi(start + 1);
        sprintf(ver_buf, "%d", ver_no);
        strcpy(ver, ver_buf);
        return;
      } else {
        *(start - 1) = '\0';
        strcpy(ver, ver_buf);
        return;
      }
    }
  } else if (strchr(name, '%')) {
    strcpy(ver, "0");
    return;
  }
NO:
  /* name does not contain a valid version field. */
  *ver = '\0';
}

/*
 * Name:	unpack_filename
 *
 * Argument:	char	*file   UNIX file name in the UNIX format.  It must be an
 *				absolute path.
 *		char	*dir	The place where unpacked directory will be stored.
 *		char	*name	The place where unpacked "root file name" will be
 *				stored. "Root file name" contains the file name
 *				and the extension.
 *		char	*ver	The place where unpacked version will be stored.
 *		int	checkp	If 1, whether the version field contains only the
 *				numeric characters or not will be checked.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	dir, name, and  ver will be replaced with the unpacked corresponding
 *		fields.
 *
 * Description:
 *
 * Unpack the specified UNIX full pathname to three components.  If the file does
 * not include a version field, NULL string will be stored as a version.
 * unpack_filename assumes the pathname passed is a file, not a directory.
 *
 */

int unpack_filename(char *file, char *dir, char *name, char *ver, int checkp)
{
  register char *cp;

#ifdef DOS
  if ((cp = (char *)max((UNSIGNED)strrchr(file, DIRSEP), (UNSIGNED)strrchr(file, UNIXDIRSEP))) == 0)
    return (0);

  if (file[1] == DRIVESEP) { /* There's a drive spec; copy it and ignore it from here on. */
    *dir++ = *file++;
    *dir++ = *file++;
  }
#else  /* DOS */
  if ((cp = (char *)strrchr(file, UNIXDIRSEP)) == NULL) return (0);
#endif /* DOS */

  if (cp == file) {
    /* File is on a root directory. */
    *dir = '/';
    *(dir + 1) = '\0';
  } else {
    while (file != cp) *dir++ = *file++;
    *dir = '\0';
  }

  strcpy(name, cp + 1);
  separate_version(name, ver, checkp);
  return (1);
}

/*
 * Name:	true_name
 *
 * Argument:	char	*path	The pathname which follows the UNIX file naming
 *				convention and does not include any meta character.
 *				Whether a tail directory delimiter is included
 *				in path or not is not a matter.  true_name handles
 *				both case correctly.
 *
 * Value:	If the pathname is recognized as an existing directory, returns
 *		-1, recognized as an existing file, returns 1, otherwise 0.
 *
 * Side Effect:	If succeed, the contents of path is replaced with the true name.
 *
 * Description:
 *
 * Try to find the file or directory specified with path.  The search is case
 * insensitive.
 *
 */

int true_name(register char *path)
{
  char dir[MAXPATHLEN];
  char name[MAXNAMLEN];
#ifdef DOS
  char drive[1];
#endif
  register char *sp, *cp;
  register int type, c;

  if (strcmp(path, "/") == 0) return (-1);

#ifdef DOS
  if (*(path + 1) == DRIVESEP) {
    drive[0] = *path;
    dir[0] = drive[0]; /* but copy it to the working dir string */
    dir[1] = DRIVESEP;
    dir[2] = '\0';
    cp = path + 3; /* skip the drive spec & 1st dir delimiter */
  } else {
#endif           /* DOS */
    *dir = '\0'; /*
                  * locate_file does not accept the directory with
                  * the trail delimiter.  Thus, for the root
                  * directory, we have to set the null character
                  * as directory.
                  */
    cp = path + 1;
#ifdef DOS
  }
#endif /* DOS */
       /* If all there was was the root /, succeed easily */
  if (strcmp((cp - 1), DIRSEPSTR) == 0) return (-1);

  while (*cp) {
    /*
     * Copy the next subdirectory to name.
     * And examine if it is an existing subdirectory or the file on the
     * dir.
     * At this point dir has been guaranteed to be exist.
     */
    sp = name;
    for (c = *cp++, *sp++ = c; c != '/' && c != '\0' && c != DIRSEP; c = *cp++, *sp++ = c) {}
    if (c == '/') {
      /* Remove the trail delimiter */
      *(sp - 1) = '\0';
    } else {
      /* Move back cp to '\0' character to finish the loop. */
      cp--;
    }

    /* Try to locate name on dir*/
    if ((type = locate_file(dir, name)) == 0) {
      /*
       * No directory or file named name has been found on
       * dir.
       */
      return (0);
    }
    /*
     * Now, the true name including the name has been set
     * to dir by locate_file.
     */
  }
  strcpy(path, dir);
  return (type);
}

/*
 * Name:	locate_file
 *
 * Argument:	char	*dir	The existing directory name.  Does not include
 *				the trail delimiter.
 *
 *		char	*name	The name which is searched on dir.
 *
 * Value:	If name is recognized as an existing directory, returns -1,
 *		recognized as an existing file, returns 1, otherwise 0.
 *
 * Side Effect:	If succeed, the contents of dir is replaced with the true name
 *		including name.
 *
 * Description:
 *
 * Try to find the file or directory specified with name on the directory
 * specified with dir.  The search is case insensitive.
 *
 */

static int locate_file(char *dir, char *name)
{
#ifdef DOS
  char path[MAXPATHLEN];
  char nb1[MAXNAMLEN], nb2[MAXNAMLEN];
  register int type, len;
  struct find_t dirp;
  struct direct *dp;

  /* First of all, recognize as if. */
  sprintf(path, "%s\\%s", dir, name);
  DIR_OR_FILE_P(path, type);
  if (type != 0) {
    strcpy(dir, path);
    return (type);
  }

  return (0);

#else  /* UNIX code follows */

  char path[MAXPATHLEN];
  char nb1[MAXNAMLEN], nb2[MAXNAMLEN];
  register int type;
  size_t len;
  DIR *dirp;
  struct dirent *dp;

  /* First of all, recognize as if. */
  sprintf(path, "%s/%s", dir, name);
  DIR_OR_FILE_P(path, type);
  if (type != 0) {
    strcpy(dir, path);
    return (type);
  }

  /* Next try with all lower case name. */
  strcpy(nb1, name);
  DOWNCASE(nb1);
  sprintf(path, "%s/%s", dir, nb1);
  DIR_OR_FILE_P(path, type);
  if (type != 0) {
    strcpy(dir, path);
    return (type);
  }

  /* Next try with all upper case name. */
  UPCASE(nb1);
  sprintf(path, "%s/%s", dir, nb1);
  DIR_OR_FILE_P(path, type);
  if (type != 0) {
    strcpy(dir, path);
    return (type);
  }

  /* No way.  Read dir and compare with name. */
  len = strlen(name);
  errno = 0;
  TIMEOUT0(dirp = opendir(dir));
  if (dirp == NULL) {
    *Lisp_errno = errno;
    return (0);
  }
  for (S_TOUT(dp = readdir(dirp)); dp != NULL || errno == EINTR;
       errno = 0, S_TOUT(dp = readdir(dirp)))
    if (dp) {
      if (strlen(dp->d_name) == len) {
        strcpy(nb2, dp->d_name);
        UPCASE(nb2);
        if (strcmp(nb1, nb2) == 0) {
          sprintf(path, "%s/%s", dir, dp->d_name);
          DIR_OR_FILE_P(path, type);
          if (type != 0) {
            strcpy(dir, path);
            TIMEOUT(closedir(dirp));
            return (type);
          }
        }
      }
    }
  TIMEOUT(closedir(dirp));
  return (0);
#endif /* DOS */
}

/*
 * Name:	make_directory
 *
 * Argument:	char	*dir	The full directory name in UNIX format.  It does
 *				not include a tail delimiter.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	The directory specified as dir will be created in the file system.
 *		If succeed, dir will be replaced with the true name of the directory.
 *
 * Description:
 *
 * Try to create a specified directory.
 *
 */

static int make_directory(register char *dir)
{
  register char *cp, *dp;
  register int maked, rval;
  char dir_buf[MAXPATHLEN];

  maked = 0;

  dp = dir_buf;
  cp = dir;

#ifdef DOS
  if (DRIVESEP == *(cp + 1)) {
    *dp++ - *cp++; /* copy the drive letter and colon */
    *dp++ = *cp++;
  }
#endif /* DOS */

  *dp++ = DIRSEP; /* For a root directory. */
  cp++;           /* Skip a root directory in dir. */

  for (;;) {
    switch (*cp) {
#ifdef DOS
      case DIRSEP:
#endif
      case '/':
      case '\0':
        *dp = '\0';
        /*
         * Now, dir_buf contains the absolute path to the next
         * subdirectory or file.  If one of the parent directories
         * are created, we have to create a new subdirectory
         * anyway.  If all of the parent directories are existing
         * directories, we have to check this subdirectory is an
         * existing or not.
         */
        if (maked) {
#ifdef DOS
          TIMEOUT(rval = mkdir(dir_buf));
#else
          TIMEOUT(rval = mkdir(dir_buf, 0777));
#endif /* DOS */
          if (rval == -1) {
            *Lisp_errno = errno;
            return (0);
          }
          if (*cp == '\0') {
            strcpy(dir, dir_buf);
            return (1);
          }
          *dp++ = DIRSEP;
          cp++;
        } else {
          switch (true_name(dir_buf)) {
            case -1: /* Directory */
              if (*cp == '\0') {
                /* Every subdirectories are examined. */
                strcpy(dir, dir_buf);
                return (1);
              } else {
                dp = dir_buf;
                while (*dp) dp++;
                *dp++ = DIRSEP;
                cp++;
              }
              break;

            case 1: /* Regular File */
              /*
               * UNIX does not allow to make a directory
               * and a file in the same name on a directory.
               */
              return (0);

            default:
/*
 * Should handle other cases. (special file).
 */
#ifdef DOS
              TIMEOUT(rval = mkdir(dir_buf));
#else
              TIMEOUT(rval = mkdir(dir_buf, 0777));
#endif /* DOS */
              if (rval == -1) {
                *Lisp_errno = errno;
                return (0);
              }
              if (*cp == '\0') return (1);
              *dp++ = DIRSEP;
              cp++;
              maked = 1;
              break;
          }
        }
        break;

      default: *dp++ = *cp++; break;
    }
  }
}

/*
 * Name:	FindHighestVersion
 *
 * Argument:	FileName *varray
 *				The version array.  It has to include at
 *				least one versioned file.
 *		FileName *mentry
 *				The place where an entry in varray corresponding
 *				to the highest versioned file will be stored.
 *		int	max_no	The place where the version number of the highest
 *				versioned file will be stored.
 *
 * Value:	N/A
 *
 * Side Effect:	mentry and max_no will be replaced with the highest versioned entry
 *		and highest version number respectively.
 *
 * Description:
 *
 * Find the highest versioned entry in varray.  Varray has to include at least
 * one versioned file, that is varray has to satisfy (!NoFileP(varray) &&
 * !OnlyVersionlessP(varray)).
 *
 */
#ifdef DOS
#define FindHighestVersion(varray, mentry, max_no)                                         \
  {                                                                                        \
    register FileName *centry;                                                             \
    for (centry = varray, max_no = -1; centry->version_no != LASTVERSIONARRAY; centry++) { \
      if (centry->version_no > max_no) {                                                   \
        max_no = centry->version_no;                                                       \
        mentry = centry;                                                                   \
      }                                                                                    \
    }                                                                                      \
  }
#else
#define FindHighestVersion(varray, mentry, max_no)                                        \
  {                                                                                       \
    register FileName *centry;                                                            \
    for (centry = (varray), (max_no) = 0; centry->version_no != LASTVERSIONARRAY; centry++) { \
      if (centry->version_no > (max_no)) {                                                  \
        (max_no) = centry->version_no;                                                      \
        (mentry) = centry;                                                                  \
      }                                                                                   \
    }                                                                                     \
  }
#endif /* DOS */

/*
 * Name:	FindLowestVersion
 *
 * Argument:	FileName *varray
 *				The version array.  It has to include at
 *				least one versioned file.
 *		FileName *mentry
 *				The place where an entry in varray corresponding
 *				to the lowest versioned file will be stored.
 *		int	min_no	The place where the version number of the highest
 *				versioned file will be stored.
 *
 * Value:	N/A
 *
 * Side Effect:	mentry and min_no will be replaced with the lowest versioned entry
 *		and lowest version number respectively.
 *
 * Description:
 *
 * Find the lowest versioned entry in varray.  Varray has to include at least
 * one versioned file, that is varray has to satisfy (!NoFileP(varray) &&
 * !OnlyVersionlessP(varray)).
 *
 */
#ifdef DOS
#define FindLowestVersion(varray, mentry, min_no)                                                  \
  {                                                                                                \
    register FileName *centry;                                                                     \
    for (centry = varray, min_no = MAXVERSION; centry->version_no != LASTVERSIONARRAY; centry++) { \
      if (centry->version_no < min_no) {                                                           \
        min_no = centry->version_no;                                                               \
        mentry = centry;                                                                           \
      }                                                                                            \
    }                                                                                              \
  }
#else
#define FindLowestVersion(varray, mentry, min_no)                                                  \
  {                                                                                                \
    register FileName *centry;                                                                     \
    for (centry = (varray), (min_no) = MAXVERSION; centry->version_no != LASTVERSIONARRAY; centry++) { \
      if (centry->version_no < (min_no) && centry->version_no != 0) {                                \
        (min_no) = centry->version_no;                                                               \
        (mentry) = centry;                                                                           \
      }                                                                                            \
    }                                                                                              \
  }
#endif /* DOS */

/*
 * Name:	FindSpecifiedVersion
 *
 * Argument:	FileName *varray
 *				The version array.  It has to include at
 *				least one versioned file.
 *		FileName *sentry
 *				The place where an entry in varray corresponding
 *				to the file which has the specified version will
 *				be stored.
 *		int	ver_no	The version number to be found.
 *
 * Value:	N/A
 *
 * Side Effect:	sentry will be replaced with the specified versioned entry.
 *
 * Description:
 *
 * Find the specified versioned entry in varray. Varray has to include at least
 * one versioned file, that is varray has to satisfy (!NoFileP(varray) &&
 * !OnlyVersionlessP(varray)).
 *
 */

#define FindSpecifiedVersion(varray, sentry, ver_no)                        \
  {                                                                         \
    register FileName *centry;                                              \
                                                                            \
    (sentry) = (FileName *)NULL;                                              \
    for (centry = varray; centry->version_no != LASTVERSIONARRAY; centry++) \
      if (centry->version_no == (ver_no)) {                                   \
        (sentry) = centry;                                                    \
        break;                                                              \
      }                                                                     \
  }

/************************************************************************/
/*									*/
/*		    g e t _ v e r s i o n _ a r r a y			*/
/*									*/
/*	Given a file, create an array of file/version# pairs for all	*/
/*	versions of that file.  Returns 1 on success, 0 on failure.	*/
/*									*/
/*	dir	UNIX directory specified absolutely.  Caller must	*/
/*		guarantee that the directory exists.			*/
/*	file	File name, optionally including a (unix) version	*/
/*	varray	Place to put the version array entries.			*/
/*	cache	Place to hold info about the new version array		*/
/*									*/
/*	Read thru DIR and gather all files that match FILE into		*/
/*	VARRAY.  DIR's case must match existing directory's, but	*/
/*	FILE name matching is case-insensitive.  For UNIX, the 		*/
/*	versionless file is marked with a version# of 0; for DOS,	*/
/*	version 0 is the back-up copy of the file.			*/
/*									*/
/************************************************************************/

static int get_version_array(char *dir, char *file, FileName *varray, CurrentVArray *cache)
{
#ifdef DOS

  char lcased_file[MAXPATHLEN];
  char old_file[MAXPATHLEN];
  char name[MAXNAMLEN];
  char ver[VERSIONLEN];
  register FileName *svarray;
  struct find_t dirp;
  register struct direct *dp;
  register int rval, drive = 0, isslash = 0;
  struct stat sbuf;
  int res;

  /*
   * First of all, prepare a lower cased file name for the case insensitive
   * search.  Also we have to separate file name from its version field.
   */
  if (dir[1] == DRIVESEP) drive = dir[0];

  if (strcmp(dir, "\\") == 0)
    isslash = 1;
  else if (drive && (strcmp(dir + 2, "\\") == 0))
    isslash = 1;

  if (!isslash)
    strcpy(lcased_file, dir); /* Only add the dir if it's real */
  else if (drive) {
    lcased_file[0] = drive;
    lcased_file[1] = DRIVESEP;
    lcased_file[2] = '\0';
  } else
    *lcased_file = '\0';

  /*  strcpy(lcased_file, dir);   removed when above code added 3/4/93 */
  strcat(lcased_file, DIRSEPSTR);
  strcat(lcased_file, file);
  separate_version(lcased_file, ver, 1);
  DOWNCASE(lcased_file);

  /*************************************************/
  /* First, look up the backup version of the file */
  /*************************************************/

  /* First, make the "backup-file-name" for this file */

  make_old_version(old_file, lcased_file);

  svarray = varray;

  TIMEOUT(res = _dos_findfirst(old_file, _A_NORMAL | _A_SUBDIR, &dirp));
  if (res == 0) {
    strcpy(name, dirp.name);
    strcpy(svarray->name, name);
    svarray->version_no = 0;
    svarray++;
  }

  /*******************************/
  /* Now look up the file itself */
  /*******************************/

  TIMEOUT(res = _dos_findfirst(lcased_file, _A_NORMAL | _A_SUBDIR, &dirp));
  /*    if (res != 0)
        {
          *Lisp_errno = errno;
          return(0);
        }
  */
  for (; res == 0; S_TOUT(res = _dos_findnext(&dirp))) {
    strcpy(name, dirp.name);
    separate_version(name, ver, 1);
    DOWNCASE(name);

    strcpy(svarray->name, dirp.name);
    if (*ver == '\0') {
      /* Versionless file */
      svarray->version_no = 1;
    } else {
      /*
       * separator_version guarantees ver is a numeric
       * string.
       */
      svarray->version_no = atoi(ver);
    }
    svarray++;
  }

  /*
   * The last entry of varray is indicated by setting LASTVERSIONARRAY into
   * version_no field.
   */
  svarray->version_no = LASTVERSIONARRAY;

  /*
   * If more than one files have been stored in varray, we store the name
   * without version in the last marker entry.
   */
  if (!NoFileP(varray)) {
    strcpy(name, varray->name);
    separate_version(name, ver, 1);
    strcpy(svarray->name, name);
  }

  return (1);

#else
  /* UNIX version-array builder */
  char lcased_file[MAXNAMLEN];
  char name[MAXNAMLEN];
  char ver[VERSIONLEN];
  register FileName *svarray;
  register DIR *dirp;
  register struct dirent *dp;
  /* Used in commented out code below:
  register int rval;
  struct stat sbuf;
  */

  /*
   * First of all, prepare a lower cased file name for the case insensitive
   * search.  Also we have to separate file name from its version field.
   */
  strcpy(lcased_file, file);
  separate_version(lcased_file, ver, 1);
  DOWNCASE(lcased_file);

  /*
          TIMEOUT(rval = stat(dir, &sbuf));
          if (rval == -1) {
                  *Lisp_errno = errno;
                  return(0);
          }
  */

  /*
   * If the cached version array is still valid, we can return immediately.
   */

  /*
   * Cache mechanism is not used now, because of the bug of Sun OS.
   * Sometimes just after unlinking a file on a directory, the st_mtime
   * of the directory does not change.  This will make Maiko believe
   * cached version array is still valid, although it is already invalid.
   * sync(2) has no effect on such case.
   */

  /*
          if ((sbuf.st_mtime == cache->mtime) && strcmp(dir, cache->path) == 0
              && strcmp(lcased_file, cache->file) == 0) return(1);
  */
  errno = 0;
  TIMEOUT0(dirp = opendir(dir));
  if (dirp == NULL) {
    *Lisp_errno = errno;
    return (0);
  }

  for (S_TOUT(dp = readdir(dirp)), svarray = varray; dp != NULL || errno == EINTR;
       errno = 0, S_TOUT(dp = readdir(dirp)))
    if (dp) {
      strcpy(name, dp->d_name);
      separate_version(name, ver, 1);
      DOWNCASE(name);
      if (strcmp(name, lcased_file) == 0) {
        /*
         * This file can be regarded as a same file in Lisp sense.
         */
        strcpy(svarray->name, dp->d_name);
        if (*ver == '\0') {
          /* Versionless file */
          svarray->version_no = 0;
        } else {
          /*
           * separator_version guarantees ver is a numeric
           * string.
           */
          svarray->version_no = atoi(ver);
        }
        svarray++;
      }
    }
  /*
   * The last entry of varray is indicated by setting LASTVERSIONARRAY into
   * version_no field.
   */
  svarray->version_no = LASTVERSIONARRAY;

  /*
   * If more than one files have been stored in varray, we store the name
   * without version in the last marker entry.
   */
  if (!NoFileP(varray)) {
    strcpy(name, varray->name);
    separate_version(name, ver, 1);
    strcpy(svarray->name, name);
  }

  /*
   * Update cache information.
   */
  /*
          strcpy(cache->path, dir);
          strcpy(cache->file, lcased_file);
          cache->mtime = sbuf.st_mtime;
  */
  TIMEOUT(closedir(dirp));
  return (1);
#endif /* DOS */
}

/*
 * Name:	maintain_version
 *
 * Argument:	char	*file	The full file name in UNIX format.
 *		FileName *varray
 *				The version array.
 *		int	forcep	If 1, a versionless file will be linked to version
 *				1 file when there is no other files having the same
 *				name.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	None.
 *
 * Description:
 *
 * Try to make sure that the version control based on the name convention
 * is well maintained.  The versionless file corresponding to file will be
 * maintained.  The second argument varray is an optional.  If it is not
 * NULL pointer, maintain_version maintains the version control using the
 * version array.  If varray is NULL pointer, a version array is re collected
 * by this routine.
 * Currently, forcep is 1 only if maintain_version is called from COM_openfile
 * to maintain the directory on which a file is being created.
 */

static int maintain_version(char *file, FileName *varray, int forcep)
{
  char dir[MAXPATHLEN], fname[MAXNAMLEN], ver[VERSIONLEN];
  char old_file[MAXPATHLEN], vless[MAXPATHLEN];
  int highest_p;
  register int rval, max_no;
  register FileName *entry;

  if (varray == (FileName *)NULL) {
    if (unpack_filename(file, dir, fname, ver, 1) == 0) return (0);
    /*
     * We have to make sure that dir is the existing directory.
     */
    if (true_name(dir) != -1) return (0);
    if (get_version_array(dir, fname, VersionArray, &VArrayInfo) == 0) return (0);
    varray = VersionArray;
  }

  if (NoFileP(varray)) {
    /*
     * We don't need to care about such case that there is no such file
     * or an only versionless file exists.
     */
    return (1);
  }

  if (OnlyVersionlessP(varray)) {
    if (forcep) {
/*
 * If forcep, we link the versionless file to the version
 * 1 file.
 */
#ifndef DOS
      get_versionless(varray, vless, dir);
      ConcNameAndVersion(vless, "1", fname);
      TIMEOUT(rval = link(vless, fname));
      if (rval == -1) {
        *Lisp_errno = errno;
        return (0);
      }
#endif /* DOS */
    }
    return (1);
  }

  /*
   * At this point, we are sure that at least one file with version number
   * exists.  Thus, FindHighestVersion works fine from now on.
   */

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is not a versionless file, but at least one versioned file.
     * Thus, the thing we have to do is to link a versionless file
     * to the existing highest versioned file.
     */
    FindHighestVersion(varray, entry, max_no);
    ConcDirAndName(dir, entry->name, old_file);
/*
 * The versionless file should have the same case name as the old
 * file.
 */
#ifndef DOS
    strcpy(fname, entry->name);
    separate_version(fname, ver, 1);
    ConcDirAndName(dir, fname, vless);
    TIMEOUT(rval = link(old_file, vless));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (0);
    }
#endif /* DOS */
    return (1);
  }

  if (check_vless_link(vless, varray, old_file, &highest_p) == 0) return (0);

  if (*old_file == '\0') {
    /*
     * The versionless file is not linked to any file in varray.
     * Thus, we have to link the versionless file to the file which
     * is versioned one higher than the existing highest version.
     */
    FindHighestVersion(varray, entry, max_no);
    sprintf(ver, "%d", max_no + 1);
/*
 * The old file should have the same case name as the versionless
 * file.
 */
#ifndef DOS
    ConcNameAndVersion(vless, ver, old_file);
    TIMEOUT(rval = link(vless, old_file));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (0);
    }
#endif /* DOS */
    return (1);
  }

  if (highest_p) {
    /*
     * The versionless file has been already linked to the highest
     * versioned file.  Thus, there is nothing to do.
     */
    return (1);
  } else {
    /*
     * Although the  versionless file is linked to a file in varray,
     * the file is not the highest versioned file.  We have to unlink
     * the wrongly linked versionless file, and link the highest versioned
     * file to a versionless file.
     */
    TIMEOUT(rval = unlink(vless));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (0);
    }
    FindHighestVersion(varray, entry, max_no);
    ConcDirAndName(dir, entry->name, old_file);
/*
 * The versionless file should have the same case name as the old
 * file.
 */
#ifndef DOS
    strcpy(fname, entry->name);
    separate_version(fname, ver, 1);
    ConcDirAndName(dir, fname, vless);
    TIMEOUT(rval = link(old_file, vless));
    if (rval == -1) {
      *Lisp_errno = errno;
      return (0);
    }
#endif /* DOS */
    return (1);
  }
}

/*
 * Name:	get_versionless
 *
 * Argument:	FileName *varray
 *				The version array already filled by
 *				get_version_array routine.
 *		char	*file	The place where the full file name of the found
 *				versionless file will be stored.
 *		char	*dir	Directory absolute path following the UNIX
 *				file naming convention on which varray
 *				has been gathered.
 *
 * Value:	If a versionless file found, returns 1, otherwise 0.
 *
 * Side Effect:	file will be replaced with the full name of the found versionless
 *		file when it is found.
 *
 * Description:
 *
 * Accepts a version array and try to find a versionless file within it.
 *
 */

static int get_versionless(FileName *varray, char *file, char *dir)
{
#ifdef DOS
  return (0);
#endif /* DOS */
  if (NoFileP(varray)) return (0);

  while (varray->version_no != LASTVERSIONARRAY) {
    if (varray->version_no == 0) {
      ConcDirAndName(dir, varray->name, file);
      return (1);
    } else
      varray++;
  }
  return (0);
}

/*
 * Name:	check_vless_link
 *
 * Argument:	char	*vless	The full file name of the versionless file in
 *				UNIX format.
 *		FileName *varray
 *				The version array already filled by get_version_array.
 *		char	*to_file
 *				The place where the full file name of the file
 *				to which the versionless file is hard linked will
 *				be stored.
 *		int	*highest_p
 *				If to_file is the highest versioned file in varray,
 *				highest_p will point to 1, otherwise, 0.
 *
 * Value:	If succeed, returns 1, otherwise, 0.
 *
 * Side Effect:	to_file will be replaced with the full file name to which the
 *		versionless file is hard linked.  highest_p will be replaced with
 *		1 or 0.
 *
 * Description:
 *
 * Examines the link status of a specified versionless file.  If there is a file
 * to which the versionless file is hard linked in a version array, it will be
 * stored in to_file.  If there is no such file, to_file will be NULL string.
 * When hard linked file is found, if highest_p is 1, the file is the highest
 * versioned file in the version array.  This is the such case that a versionless
 * file is well maintained by DSK code.
 * Notice that even if to_file is NULL, it does not mean that the versionless file
 * is not hard linked to any file.  It only means that there is no file to which the
 * versionless file is hard linked in the version array.  The versionless file
 * might be hard linked to the other file entirely differently named.
 *
 */

static int check_vless_link(char *vless, FileName *varray, char *to_file, int *highest_p)
{
  register int rval, max_no, found;
  ino_t vless_ino;
  struct stat sbuf;
  char dir[MAXPATHLEN], name[MAXNAMLEN], ver[VERSIONLEN];
  register FileName *max_entry, *linked_entry;

  TIMEOUT(rval = stat(vless, &sbuf));
  if (rval != 0) {
    *Lisp_errno = errno;
    return (0);
  }

  if (sbuf.st_nlink == 1) {
    /* There is no file to which vless is hard linked. */
    *to_file = '\0';
    return (1);
  }
  vless_ino = sbuf.st_ino;

  if (unpack_filename(vless, dir, name, ver, 1) == 0) return (0);

  max_no = 0;
  found = 0;
  max_entry = NULL;
  linked_entry = NULL;

  while (varray->version_no != LASTVERSIONARRAY) {
    if (varray->version_no > max_no) {
      max_no = varray->version_no;
      max_entry = varray;
    }
    if (!found && varray->version_no != 0) {
      ConcDirAndName(dir, varray->name, name);
      TIMEOUT(rval = stat(name, &sbuf));
      if (rval != 0) {
        *Lisp_errno = errno;
        return (0);
      }
      if (sbuf.st_ino == vless_ino) {
        found = 1;
        linked_entry = varray;
      }
    }
    varray++;
  }

  if (linked_entry != NULL) {
    if (linked_entry == max_entry) {
      *highest_p = 1;
    } else {
      *highest_p = 0;
    }
    strcpy(to_file, name);
  } else {
    *to_file = '\0';
  }
  return (1);
}

/*
 * Name:	get_old
 *
 * Argument:	char	*dir	Directory absolute path following the UNIX
 *				file naming convention on which varray
 *				has been gathered.
 *		FileName *varray
 *				The version array already filled by
 *				get_version_array routine.
 *		char	*afile	File name.  It might include a version field.
 *				The version field have to be following the
 *				UNIX convention, that is "name.~##~", not
 *				Xerox Lisp one.  The versionless file is
 *				also acceptable.
 *				afile is also used as a place where the true
 *				name of the  recognized file will be stored.
 *		char	*vfile	The place where the versioned name of the
 *				recognized file will be stored.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	afile will be replaced with the full name of the recognized file
 *		when the recognition succeed.  vfile will be replaced with the
 *		versioned full name of the recognized file when the recognition
 *		succeed.
 *
 * Description:
 *
 * Accepts a version array and file name, and recognize a file in the version
 * array with the old mode.
 * "Old" file means the "newest existing" file.
 * Notice that, afile and vfile are not necessary same name, because the versionless
 * file is recognized as an old file, afile will hold the true versionless file name,
 * although vfile will hold the versioned file name anyway.
 * afile could be used as the real UNIX pathname to access the recognized file, on
 * the other hand, vfile could be used as file name from which the file name
 * which should be returned to Lisp could be produced.
 *
 */

static int get_old(char *dir, FileName *varray, char *afile, char *vfile)
{
  char name[MAXPATHLEN], vless[MAXPATHLEN], to_file[MAXPATHLEN];
  char ver[VERSIONLEN], vbuf[VERSIONLEN];
  register int ver_no, max_no;
  int highest_p;
  register FileName *entry;

  /* "Old" file have to be existing, thus varray should not be empty. */
  if (NoFileP(varray)) return (0);

  strcpy(name, afile);
  separate_version(name, ver, 1);

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file, but at least one versioned
     * file exists.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The highest versioned file
       * is an old file.
       */
      FindHighestVersion(varray, entry, max_no);
      ConcDirAndName(dir, entry->name, afile);
      strcpy(vfile, afile);
      return (1);
    } else {
      /*
       * A version is specified.  We have to walk thorough
       * varray and try to find the file with the specified
       * version.
       */
      ver_no = atoi(ver);
      FindSpecifiedVersion(varray, entry, ver_no);
      if (entry != NULL) {
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else
        return (0);
    }
  } else if (OnlyVersionlessP(varray)) {
    /*
     * There is only versionless file in varray.
     * If the specified version is 1 the versionless file is regarded
     * as version 1 file.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The versionless file is dealt
       * with as version 1.
       */
      ConcNameAndVersion(vless, "1", vfile);
      strcpy(afile, vless);
      return (1);
    } else {
      ver_no = atoi(ver);
      if (ver_no == 1) {
        /*
         * Version 1 is specified.  The versionless file is
         * dealt with as a version 1 file.
         */
        ConcNameAndVersion(name, "1", afile);
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /*
         * Other versions than 1 are not recognized as an old
         * file.
         */
        return (0);
      }
    }
  } else {
    if (check_vless_link(vless, varray, to_file, &highest_p) == 0) return (0);
    if (*to_file == '\0') {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is not linked
       * to any file in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The one higher than an
         * existing maximum version is dealt with as the
         * old version, and it should be a version of the
         * link missing versionless file.
         */
        FindHighestVersion(varray, entry, max_no);
        sprintf(vbuf, "%d", max_no + 1);
        ConcNameAndVersion(vless, vbuf, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /* A version is specified. */
        ver_no = atoi(ver);
        FindHighestVersion(varray, entry, max_no);
        if (ver_no == max_no + 1) {
          /*
           * If the version is one higher than the
           * existing highest version is specified, it
           * is dealt with as a version of the link
           * missing versionless file.
           */
          sprintf(vbuf, "%d", ver_no);
          ConcNameAndVersion(vless, vbuf, vfile);
          strcpy(afile, vless);
          return (1);
        } else {
          /*
           * We have to walk through varray and try
           * to find the file with the specified version.
           */
          FindSpecifiedVersion(varray, entry, ver_no);
          if (entry != NULL) {
            ConcDirAndName(dir, entry->name, afile);
            strcpy(vfile, afile);
            return (1);
          } else
            return (0);
        }
      }
    } else {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is linked to
       * one of files in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The highest versioned file
         * in varray is an old file.
         */
        FindHighestVersion(varray, entry, max_no);
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else {
        /*
         * A version is specified.  We have to walk thorough
         * varray and try to find the file with the specified
         * version.
         */
        ver_no = atoi(ver);
        FindSpecifiedVersion(varray, entry, ver_no);
        if (entry != NULL) {
          ConcDirAndName(dir, entry->name, afile);
          strcpy(vfile, afile);
          return (1);
        } else
          return (0);
      }
    }
  }
}

/*
 * Name:	get_oldeset
 *
 * Argument:	char	*dir	Directory absolute path following the UNIX
 *				file naming convention on which varray
 *				has been gathered.
 *		FileName *varray
 *				The version array already filled by
 *				get_version_array routine.
 *		char	*afile	File name.  It might include a version field.
 *				The version field have to be following the
 *				UNIX convention, that is "name.~##~", not
 *				Xerox Lisp one.  The versionless file is
 *				also acceptable.
 *				afile is also used as a place where the true
 *				name of the  recognized file will be stored.
 *		char	*vfile	The place where the versioned name of the
 *				recognized file will be stored.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	afile will be replaced with the full name of the recognized file
 *		when the recognition succeed.  vfile will be replaced with the
 *		versioned full name of the recognized file when the recognition
 *		succeed.
 *
 * Description:
 *
 * Accepts a version array and filename, and recognize a file in the version
 * array with oldest mode.
 * "Oldest" file means the "oldest existing" file.
 * Notice that, afile and vfile are not necessary same name, because the versionless
 * file is recognized as an old file, afile will hold the true versionless file name,
 * although vfile will hold the versioned file name anyway.
 * afile could be used as the real UNIX pathname to access the recognized file, on
 * the other hand, vfile could be used as file name from which the file name
 * which should be returned to Lisp could be produced.
 *
 */

static int get_oldest(char *dir, FileName *varray, char *afile, char *vfile)
{
  char name[MAXPATHLEN], vless[MAXPATHLEN], to_file[MAXPATHLEN];
  char ver[VERSIONLEN], vbuf[VERSIONLEN];
  register int ver_no, min_no;
  int highest_p;
  register FileName *entry;

  /* "Oldest" file have to be existing, thus varray should not be empty. */
  if (NoFileP(varray)) return (0);

  strcpy(name, afile);
  separate_version(name, ver, 1);

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file, but at least one versioned
     * file exists.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The lowest versioned file
       * is an oldest file.
       */
      FindLowestVersion(varray, entry, min_no);
      ConcDirAndName(dir, entry->name, afile);
      strcpy(vfile, afile);
      return (1);
    } else {
      /*
       * A version is specified.  We have to walk thorough
       * varray and try to find the file with the specified
       * version.
       */
      ver_no = atoi(ver);
      FindSpecifiedVersion(varray, entry, ver_no);
      if (entry != NULL) {
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else
        return (0);
    }
  } else if (OnlyVersionlessP(varray)) {
    /*
     * There is only versionless file in varray.
     * If the specified version is 1 the versionless file is regarded
     * as version 1 file.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The versionless file is dealt
       * with as version 1.
       */
      ConcNameAndVersion(vless, "1", vfile);
      strcpy(afile, vless);
      return (1);
    } else {
      ver_no = atoi(ver);
      if (ver_no == 1) {
        /*
         * Version 1 is specified.  The versionless file is
         * dealt with as a version 1 file.
         */
        ConcNameAndVersion(name, "1", afile);
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /*
         * Other versions than 1 are not recognized as an
         * oldest file.
         */
        return (0);
      }
    }
  } else {
    if (check_vless_link(vless, varray, to_file, &highest_p) == 0) return (0);
    if (*to_file == '\0') {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is not linked
       * to any file in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The lowest version is
         * dealt with as the oldest version.
         */
        FindLowestVersion(varray, entry, min_no);
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else {
        /* A version is specified. */
        ver_no = atoi(ver);
        FindHighestVersion(varray, entry, min_no);
        if (ver_no == min_no + 1) {
          /*
           * If the version is one higher than the
           * existing highest version is specified, it
           * is dealt with as a version of the link
           * missing versionless file.
           */
          sprintf(vbuf, "%d", ver_no);
          ConcNameAndVersion(vless, vbuf, vfile);
          strcpy(afile, vless);
          return (1);
        } else {
          /*
           * We have to walk through varray and try
           * to find the file with the specified version.
           */
          FindSpecifiedVersion(varray, entry, ver_no);
          if (entry != NULL) {
            ConcDirAndName(dir, entry->name, afile);
            strcpy(vfile, afile);
            return (1);
          } else
            return (0);
        }
      }
    } else {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is linked to
       * one of files in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The lowest versioned file
         * in varray is an old file.
         */
        FindLowestVersion(varray, entry, min_no);
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else {
        /*
         * A version is specified.  We have to walk thorough
         * varray and try to find the file with the specified
         * version.
         */
        ver_no = atoi(ver);
        FindSpecifiedVersion(varray, entry, ver_no);
        if (entry != NULL) {
          ConcDirAndName(dir, entry->name, afile);
          strcpy(vfile, afile);
          return (1);
        } else
          return (0);
      }
    }
  }
}

/*
 * Name:	get_new
 *
 * Argument:	char	*dir	Directory absolute path following the UNIX
 *				file naming convention on which varray
 *				has been gathered.
 *		FileName *varray
 *				The version array already filled by
 *				get_version_array routine.
 *		char	*afile	File name.  It might include a version field.
 *				The version field have to be following the
 *				UNIX convention, that is "name.~##~", not
 *				Xerox Lisp one.  The versionless file is
 *				also acceptable.
 *				afile is also used as a place where the true
 *				name of the  recognized file will be stored.
 *		char	*vfile	The place where the versioned name of the
 *				recognized file will be stored.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	afile will be replaced with the full name of the recognized file
 *		when the recognition succeed.  vfile will be replaced with the
 *		versioned full name of the recognized file when the recognition
 *		succeed.
 *
 * Description:
 *
 * Accepts a version array and filename, and recognize a file in the version
 * array with new mode.
 * "New" file means the "not yet existing" file with the version which is one
 * higher than the current highest version.
 * Notice that, afile and vfile are not necessary same name, because the versionless
 * file is recognized as an old file, afile will hold the true versionless file name,
 * although vfile will hold the versioned file name anyway.
 * afile could be used as the real UNIX pathname to access the recognized file, on
 * the other hand, vfile could be used as file name from which the file name
 * which should be returned to Lisp could be produced.
 *
 */

static int get_new(char *dir, FileName *varray, char *afile, char *vfile)
{
  char name[MAXPATHLEN], vless[MAXPATHLEN], to_file[MAXPATHLEN];
  char ver[VERSIONLEN], vbuf[VERSIONLEN];
  register int ver_no, max_no;
  int highest_p;
  register FileName *entry;

  strcpy(name, afile);
  separate_version(name, ver, 1);

#ifndef DOS
  if (NoFileP(varray)) {
    /*
     * If there is no file with such name, "new" file is always
     * recognized.
     */
    if (*ver == '\0' || strcmp(ver, "1") == 0)
#endif /* DOS */
    {
      /*
       * If version is not specified or 1 is specified,
       * we can return versionless file as afile.
       */
      ConcNameAndVersion(name, "1", afile);
      ConcDirAndName(dir, afile, vfile);
      ConcDirAndName(dir, name, afile);
      return (1);
    }
#ifndef DOS
    else {
      /*
       * A version other than 1 is specified.  "New" file
       * is recognized as if.
       */
      ConcDirAndName(dir, afile, vfile);
      strcpy(afile, vfile);
      return (1);
    }
  }

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file, but at least one versioned
     * file exists.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The new file is one higher than
       * the existing highest version.
       */
      FindHighestVersion(varray, entry, max_no);
      sprintf(vbuf, "%d", max_no + 1);
      /*
       * We will use the file name of the existing highest
       * versioned file as the name of the new file, so that
       * new file is as the same case as old.
       */
      strcpy(name, entry->name);
      separate_version(name, ver, 1);
      ConcDirAndName(dir, name, afile);
      ConcNameAndVersion(afile, vbuf, vfile);
      strcpy(afile, vfile);
      return (1);
    } else {
      /*
       * A version is specified.  We have to walk thorough
       * varray and try to find the file with the specified
       * version.
       */
      ver_no = atoi(ver);
      FindSpecifiedVersion(varray, entry, ver_no);
      if (entry != NULL) {
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      }
      /*
       * There is not a file with the specified version in varray.
       * The specified file can be recognized as if.
       * Most user will hope to create a new file in same case as
       * old.   One of case sensitive names in the files are stored
       * in the trail marker entry in varray by get_version_array
       * routine.
       * We will use it, although we cannot say all of the gathered
       * files has the name in same case.
       */
      while (varray->version_no != LASTVERSIONARRAY) varray++;
      ConcNameAndVersion(varray->name, ver, afile);
      ConcDirAndName(dir, afile, vfile);
      strcpy(afile, vfile);
      return (1);
    }
  } else if (OnlyVersionlessP(varray)) {
    /*
     * There is only versionless file in varray.
     * If the specified version is 1 the versionless file is regarded
     * as version 1 file.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The versionless file is dealt
       * with as version 1.  Thus new version is 2.
       */
      ConcNameAndVersion(vless, "2", vfile);
      strcpy(afile, vfile);
      return (1);
    } else {
      ver_no = atoi(ver);
      if (ver_no == 1) {
        /*
         * Version 1 is specified.  The versionless file is
         * dealt with as a version 1 file.
         */
        ConcNameAndVersion(name, "1", afile);
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /*
         * Other versions than 1 are recognized as if.
         */
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vfile);
        return (1);
      }
    }
  } else {
    if (check_vless_link(vless, varray, to_file, &highest_p) == 0) return (0);
    if (*to_file == '\0') {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is not linked
       * to any file in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The two higher than an
         * existing maximum version is dealt with as the
         * new version, because the one higher version is
         * dealt with as the actual version of the link
         * missing versionless file.
         */
        FindHighestVersion(varray, entry, max_no);
        sprintf(vbuf, "%d", max_no + 2);
        ConcNameAndVersion(vless, vbuf, vfile);
        strcpy(afile, vfile);
        return (1);
      } else {
        /* A version is specified. */
        ver_no = atoi(ver);
        FindHighestVersion(varray, entry, max_no);
        if (ver_no == max_no + 1) {
          /*
           * If the version is one higher than the
           * existing highest version is specified, it
           * is dealt with as a version of the link
           * missing versionless file.
           */
          sprintf(vbuf, "%d", ver_no);
          ConcNameAndVersion(vless, vbuf, vfile);
          strcpy(afile, vless);
          return (1);
        } else {
          /*
           * We have to walk through varray and try
           * to find the file with the specified version.
           */
          FindSpecifiedVersion(varray, entry, ver_no);
          if (entry != NULL) {
            ConcDirAndName(dir, entry->name, afile);
            strcpy(vfile, afile);
            return (1);
          }
          /*
           * There is not a file with the specified
           * version in varray.  The specified file can
           * be recognized as if.
           * Most user will hope to create a new file in
           * same case as old.   One of case sensitive
           * names in the files are stored in the trail
           * marker entry in varray by get_version_array
           * routine.
           * We will use it, although we cannot say all
           * of the gathered files has the name in same
           * case.
           */
          while (varray->version_no != LASTVERSIONARRAY) varray++;
          ConcNameAndVersion(varray->name, ver, afile);
          ConcDirAndName(dir, afile, vfile);
          strcpy(afile, vfile);
          return (1);
        }
      }
    } else {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is linked to
       * one of files in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The one higher than the
         * existing highest versioned file in varray is a
         * new file.
         */
        FindHighestVersion(varray, entry, max_no);
        sprintf(vbuf, "%d", max_no + 1);
        /*
         * We will use the name of the highest versioned file
         * as the name of the new file.
         */
        strcpy(vless, entry->name);
        separate_version(vless, ver, 1);
        ConcDirAndName(dir, vless, afile);
        ConcNameAndVersion(afile, vbuf, vfile);
        strcpy(afile, vfile);
        return (1);
      } else {
        /*
         * A version is specified.  We have to walk thorough
         * varray and try to find the file with the specified
         * version.
         */
        ver_no = atoi(ver);
        FindSpecifiedVersion(varray, entry, ver_no);
        if (entry != NULL) {
          ConcDirAndName(dir, entry->name, afile);
          strcpy(vfile, afile);
          return (1);
        }
        /*
         * There is not a file with the specified
         * version in varray.  The specified file can
         * be recognized as if.
         * Most user will hope to create a new file in
         * same case as old.   We will use the name of
         * the highest versioned file as the name of the
         * new file.
         */
        FindHighestVersion(varray, entry, max_no);
        strcpy(vless, entry->name);
        separate_version(vless, vbuf, 1);
        ConcDirAndName(dir, vless, afile);
        ConcNameAndVersion(afile, ver, vfile);
        strcpy(afile, vfile);
        return (1);
      }
    }
  }
#endif /* DOS */
}

/*
 * Name:	get_old_new
 *
 * Argument:	char	*dir	Directory absolute path following the UNIX
 *				file naming convention on which varray
 *				has been gathered.
 *		FileName *varray
 *				The version array already filled by
 *				get_version_array routine.
 *		char	*afile	File name.  It might include a version field.
 *				The version field have to be following the
 *				UNIX convention, that is "name.~##~", not
 *				Xerox Lisp one.  The versionless file is
 *				also acceptable.
 *				afile is also used as a place where the true
 *				name of the  recognized file will be stored.
 *		char	*vfile	The place where the versioned name of the
 *				recognized file will be stored.
 *
 * Value:	If succeed, returns 1, otherwise 0.
 *
 * Side Effect:	afile will be replaced with the full name of the recognized file
 *		when the recognition succeed.  vfile will be replaced with the
 *		versioned full name of the recognized file when the recognition
 *		succeed.
 *
 * Description:
 *
 * Accepts a version array and filename, and recognize a file in the version
 * array with old/new mode.
 * "Old/new" file means the "newest existing" or "not yet existing" file with the
 * version which is one higher than the current highest version.
 * Notice that, afile and vfile are not necessary same name, because the versionless
 * file is recognized as an old file, afile will hold the true versionless file name,
 * although vfile will hold the versioned file name anyway.
 * afile could be used as the real UNIX pathname to access the recognized file, on
 * the other hand, vfile could be used as file name from which the file name
 * which should be returned to Lisp could be produced.
 *
 */

static int get_old_new(char *dir, FileName *varray, char *afile, char *vfile)
{
  char name[MAXPATHLEN], vless[MAXPATHLEN], to_file[MAXPATHLEN];
  char ver[VERSIONLEN], vbuf[VERSIONLEN];
  register int ver_no, max_no;
  int highest_p;
  register FileName *entry;

  strcpy(name, afile);
  separate_version(name, ver, 1);

  if (NoFileP(varray)) {
    /*
     * If there is no file with such name, "old/new" file is always
     * recognized.
     */
    if (*ver == '\0' || strcmp(ver, "1") == 0) {
      /*
       * If version is not specified or 1 is specified,
       * we can return versionless file as afile.
       */
      ConcNameAndVersion(name, "1", afile);
      ConcDirAndName(dir, afile, vfile);
      ConcDirAndName(dir, name, afile);
      return (1);
    } else {
      /*
       * A version other than 1 is specified.  "New" file
       * is recognized as if.
       */
      ConcDirAndName(dir, afile, vfile);
      strcpy(afile, vfile);
      return (1);
    }
  }

  if (get_versionless(varray, vless, dir) == 0) {
    /*
     * There is no versionless file, but at least one versioned
     * file exists.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The highest versioned file
       * is an old file.
       */
      FindHighestVersion(varray, entry, max_no);
      ConcDirAndName(dir, entry->name, afile);
      strcpy(vfile, afile);
      return (1);
    } else {
      /*
       * A version is specified.  We have to walk thorough
       * varray and try to find the file with the specified
       * version.
       */
      ver_no = atoi(ver);
      FindSpecifiedVersion(varray, entry, ver_no);
      if (entry != NULL) {
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      }
      /*
       * There is not a file with the specified version in varray.
       * The specified file can be recognized as if.
       * Most user will hope to create a new file in same case as
       * old.   One of case sensitive names in the files are stored
       * in the trail marker entry in varray by get_version_array
       * routine.
       * We will use it, although we cannot say all of the gathered
       * files has the name in same case.
       */
      while (varray->version_no != LASTVERSIONARRAY) varray++;
      ConcNameAndVersion(varray->name, ver, afile);
      ConcDirAndName(dir, afile, vfile);
      strcpy(afile, vfile);
      return (1);
    }
  } else if (OnlyVersionlessP(varray)) {
    /*
     * There is only versionless file in varray.
     * If the specified version is 1 the versionless file is regarded
     * as version 1 file.
     */
    if (*ver == '\0') {
      /*
       * No version is specified.  The versionless file is dealt
       * with as version 1.
       */
      ConcNameAndVersion(vless, "1", vfile);
      strcpy(afile, vless);
      return (1);
    } else {
      ver_no = atoi(ver);
      if (ver_no == 1) {
        /*
         * Version 1 is specified.  The versionless file is
         * dealt with as a version 1 file.
         */
        ConcNameAndVersion(name, "1", afile);
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /*
         * Other versions than 1 are recognized as if.
         */
        ConcDirAndName(dir, afile, vfile);
        strcpy(afile, vfile);
        return (1);
      }
    }
  } else {
    if (check_vless_link(vless, varray, to_file, &highest_p) == 0) return (0);
    if (*to_file == '\0') {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is not linked
       * to any file in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The one higher than an
         * existing maximum version is dealt with as the
         * old version, and it should be a version of the
         * link missing versionless file.
         */
        FindHighestVersion(varray, entry, max_no);
        sprintf(vbuf, "%d", max_no + 1);
        ConcNameAndVersion(vless, vbuf, vfile);
        strcpy(afile, vless);
        return (1);
      } else {
        /* A version is specified. */
        ver_no = atoi(ver);
        FindHighestVersion(varray, entry, max_no);
        if (ver_no == max_no + 1) {
          /*
           * If the version is one higher than the
           * existing highest version is specified, it
           * is dealt with as a version of the link
           * missing versionless file.
           */
          sprintf(vbuf, "%d", ver_no);
          ConcNameAndVersion(vless, vbuf, vfile);
          strcpy(afile, vless);
          return (1);
        } else {
          /*
           * We have to walk through varray and try
           * to find the file with the specified version.
           */
          FindSpecifiedVersion(varray, entry, ver_no);
          if (entry != NULL) {
            ConcDirAndName(dir, entry->name, afile);
            strcpy(vfile, afile);
            return (1);
          }
          /*
           * There is not a file with the specified
           * version in varray.  The specified file can
           * be recognized as if.
           * Most user will hope to create a new file in
           * same case as old.   One of case sensitive
           * names in the files are stored in the trail
           * marker entry in varray by get_version_array
           * routine.
           * We will use it, although we cannot say all
           * of the gathered files has the name in same
           * case.
           */
          while (varray->version_no != LASTVERSIONARRAY) varray++;
          ConcNameAndVersion(varray->name, ver, afile);
          ConcDirAndName(dir, afile, vfile);
          strcpy(afile, vfile);
          return (1);
        }
      }
    } else {
      /*
       * There is a versionless file in varray and at least one
       * versioned file exists.  The versionless file is linked to
       * one of files in varray.
       */
      if (*ver == '\0') {
        /*
         * No version is specified.  The highest versioned file
         * in varray is an old file.
         */
        FindHighestVersion(varray, entry, max_no);
        ConcDirAndName(dir, entry->name, afile);
        strcpy(vfile, afile);
        return (1);
      } else {
        /*
         * A version is specified.  We have to walk thorough
         * varray and try to find the file with the specified
         * version.
         */
        ver_no = atoi(ver);
        FindSpecifiedVersion(varray, entry, ver_no);
        if (entry != NULL) {
          ConcDirAndName(dir, entry->name, afile);
          strcpy(vfile, afile);
          return (1);
        }
        /*
         * There is not a file with the specified
         * version in varray.  The specified file can
         * be recognized as if.
         * Most user will hope to create a new file in
         * same case as old.   We will use the name of
         * the highest versioned file as the name of the
         * new file.
         */
        FindHighestVersion(varray, entry, max_no);
        strcpy(vless, entry->name);
        separate_version(vless, vbuf, 1);
        ConcDirAndName(dir, vless, afile);
        ConcNameAndVersion(afile, ver, vfile);
        strcpy(afile, vfile);
        return (1);
      }
    }
  }
}
