/* 
   Pcm: a PC eMulator
   Copyright (C) 1992 Electronetics, Inc.  All rights reserved.

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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * Code to deal with network redirection
 */

#include "sim.h"
#include <sys/types.h>
#include <errno.h>

#ifdef apollo
#include <sys/dir.h>
#include <sys/file.h>
#else
#include <dirent.h>
#include <fcntl.h>
#endif 
#include <sys/timeb.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <ctype.h>

char dos_root[256] = "./"; /* Must end in trailing slash */
int dos_root_len = 2;
int netdrive = -1;  /* DOS network drive number */
int hostok; /* okay to service int 2f calls */
char *strchr(), *strrchr();
unsigned short getshort();
unsigned long getlong();

extern int debug;

#define SET_CARRY { CARRY = 1; FLAGS_OK = 1; return 1; }
#define CLR_CARRY { AX = CARRY = 0; FLAGS_OK = 1; return 1; }

#define HIDDEN 0x1
#define VOLUME 0x8
#define DIRECTORY 0x10
#define ARCHIVE 0x20

#define FILE_NOT_FOUND	   0x2
#define PATH_NOT_FOUND     0x3
#define ACCESS_DENIED      0x5
#define NO_MORE_FILES      0x12


/*
 * Anything < 0x20 is illegal, as
 * well as ."/\[]:|<>+=;,
 * All other are okay with lowercase mapped to upper
 */

unsigned char dos_legal[256] = {
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 
 0 ,'!', 0 ,'#','$','%','&','\'',
'(',')','*', 0 , 0 ,'-', 0 , 0 ,
'0','1','2','3','4','5','6','7',
'8','9', 0 , 0 , 0 , 0 , 0 , 0 ,
'@', 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
 0 , 0 , 0 , 0 , 0 , 0 ,'^','_',
'`','A','B','C','D','E','F','G',
'H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W',
'X','Y','Z','{', 0 ,'}','~', 0 ,

0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};

static char cbase[9]; /* candidate base */
static char cext[4];  /* candidate extension */
static char tbase[9]; /* template base */
static char text[4];  /* template extension */
static char volname[11];

struct dos_entry {
  char base[9];
  char ext[4];
  long size;
  long mtime;
  long stmode;
  struct dos_entry *next;
} *dlist, *get_candidates(), *alloc_dos_entry(), *free_entry();

unsigned char attr;

int lol_cdsfarptr_off = 0x16;
int lol_last_drive_off = 0x21;
int cds_record_size = 0x51;  /* 0x58 */
int cds_flags_off = 0x43;

int sda_current_dta_off = 0xc;
int sda_filename1_off = 0x92; /* 0x9e */
int sda_filename2_off = 0x112; /* 0x11e */
int sda_sdb_off = 0x192;       /* 0x19e */
int sda_search_attribute_off = 0x23a; /* 0x24d */
int sda_open_mode_off = 0x23b;  /* 0x24e */

int sdb_drive_letter_off = 0x0;
int sdb_template_name_off = 0x1;
int sdb_attribute_off = 0xc;
int sdb_file_name_off = 0x15;
int sdb_file_attr_off = 0x20;
int sdb_file_time_off = 0x2b;
int sdb_file_size_off = 0x31;

int sft_open_mode_off        = 0x02;
int sft_attribute_byte_off   = 0x04;
int sft_device_info_off      = 0x05;
int sft_dev_drive_ptr_off    = 0x07;
int sft_fd_off               = 0x0b;
int sft_time_off             = 0x0d;
int sft_size_off             = 0x11;
int sft_position_off         = 0x15;
int sft_abs_cluster_off      = 0x1b;
int sft_directory_sector_off = 0x1d;
int sft_directory_entry_off  = 0x1f;
int sft_name_off             = 0x20;

unsigned char warned[256];
/*
 * An int 0x2f with AH == 0x11 has
 * been received at this point
 */
sim_redirect() {
  char unix_path[256];
  char unix_path2[256];
  char *cp, *cp2, mode, *dta;
  struct stat sbuf;
  unsigned short seg, off;
  long pos;
  int len, unixfd, bcount, rval;
  if (!hostok) {
    return 0; /* not ready yet */
  }
  sdb = sda + sda_sdb_off;
  sft = ESPTR + DI;
  cp = (char *)(sda + sda_current_dta_off);
  LOADW2(off,cp++);
  LOADW2(seg,cp++);
  dta = (char *)(pc_mem + (((unsigned int)seg << 4) + off));
  switch(AL) {
  case 0: /* Installation check */
    CLR_CARRY;
  break;
  case 0x1:
  case 0x2: /* Remove directory */
    make_unix_path(cp = (char *)(sda+sda_filename1_off+3),unix_path, 1);
    if (rmdir(unix_path, 0755)) {
      AX = PATH_NOT_FOUND;
      SET_CARRY;
    }
    CLR_CARRY;
  break;
  case 0x3:
  case 0x4: /* Make directory */
    make_unix_path(cp = (char *)(sda+sda_filename1_off+3),unix_path, 1);
    if (!stat(unix_path, &sbuf)) {
      AX = ACCESS_DENIED;
      SET_CARRY;
    }
    if (mkdir(unix_path, 0755)) {
      AX = PATH_NOT_FOUND;
      SET_CARRY;
    }
    CLR_CARRY;
  break;
  case 0x5: /* Set current directory */
    make_unix_path(cp = (char *)(sda+sda_filename1_off+3),unix_path, 1);
    if (stat(unix_path, &sbuf) || !(sbuf.st_mode & S_IFDIR)) {
      AX = PATH_NOT_FOUND;
      SET_CARRY;
    }
    len = strlen(cp)-1;
    if (*(cp+len) == '\\') {
      *(cp+len) = '\0';
    }
    CLR_CARRY;
  break;
  case 0x6: /* Close file */
    unixfd = getshort(sft+sft_fd_off);
    off = getshort(sft) - 1;
    putshort(off, sft);
    if (off > 0) {
      CLR_CARRY;
    }
    if (close(unixfd)) {
      SET_CARRY;
    }
    CLR_CARRY;
  break;
  case 0x8: /* Read file */
    bcount = len = CX;
    unixfd = getshort(sft+sft_fd_off);
    pos = getlong(sft+sft_position_off);
    lseek(unixfd, pos, 0);
    bcount = read(unixfd, dta, len);
    if (bcount < 0 || errno) {
      SET_CARRY;
    }
    CX = bcount;
    pos += bcount;
    putlong(pos,sft+sft_position_off);
    CLR_CARRY;
  break;
  case 0x9: /* Write file */
    len = CX;
    unixfd = getshort(sft+sft_fd_off);
    pos = getlong(sft+sft_position_off);
    lseek(unixfd, pos, 0);
    bcount = write(unixfd, dta, len);
    if (bcount < 0 || errno) {
      SET_CARRY;
    }
    CX = bcount;
    pos += bcount;
    putlong(pos,sft+sft_position_off);
    CLR_CARRY;
  break;
  case 0xc: /* Disk space */
    make_unix_path(cds+3,unix_path,1);
    if (get_disk_space(unix_path)) {
      CLR_CARRY;
    }
    else {
      SET_CARRY;
    }
  break;
  case 0xf: /* Get file attributes */
    make_unix_path(sda+sda_filename1_off+3,unix_path, 1);
    if (stat(unix_path, &sbuf)) {
      AX = PATH_NOT_FOUND;
      SET_CARRY;
    }
    if (sbuf.st_mode & S_IFDIR) {
      AX = DIRECTORY; /* directory */
      CLR_CARRY;
    }
    if (sbuf.st_mode & S_IWRITE) {
      AX = ARCHIVE; /* archive needed, regular file */
      CLR_CARRY;
    }
    AX = ARCHIVE | HIDDEN; /* archive needed, read-only file */
    CLR_CARRY;
  break;
  case 0x11: /* Rename */
    make_unix_path(sda+sda_filename1_off+3,unix_path, 1);
    make_unix_path(sda+sda_filename2_off+3,unix_path2, 1);
    if (!stat(unix_path2, &sbuf) || rename(unix_path, unix_path2)) {
      AX = PATH_NOT_FOUND;
      SET_CARRY;
    }
    CLR_CARRY;
  break;
  case 0x13: /* Delete */
    make_unix_path(sda+sda_filename1_off+3,unix_path, 1);
    cp = strrchr(unix_path,'/');
    *cp = '\0';
    extract(cp+1, tbase, text);
    strncpy(sft+sft_name_off, tbase, 8);
    strncpy(sft+sft_name_off+0x8, text, 3);
    if (!(dlist = get_candidates(unix_path, 1))) {
      AX = FILE_NOT_FOUND;
      SET_CARRY;
    }
    strcpy(cp,"/");
    cp++;
    while (dlist) {
      cp2 = cp;
      if (!(dlist->stmode & S_IFDIR)) {
        strcpy(cp,dlist->base);
        if (*dlist->ext) {
          strcat(cp,".");
          strcat(cp,dlist->ext);
	}
        unlink(unix_path);
      }
      dlist = free_entry(dlist);
    }
    CLR_CARRY;
  break;
  case 0x16: /* Open existing file */
    mode = *(sda+sda_open_mode_off) & 0x3;
    attr = *(SSPTR+SP);
    make_unix_path(sda+sda_filename1_off+3,unix_path, 1);
    if (stat(unix_path, &sbuf) || (sbuf.st_mode & S_IFDIR)) {
      AX = FILE_NOT_FOUND;
      SET_CARRY;
    }
/*
 * DOS mode is assumed to map directly to unix mode
 */
    if ((unixfd = open(unix_path, mode)) < 0) {
      AX = ACCESS_DENIED;
      SET_CARRY;
    }
    if (!(cp = strrchr(unix_path,'/'))) {
      cp = unix_path;
    }
    extract(cp+1,sft+sft_name_off,sft+sft_name_off+0x8);
    *(sft+sft_open_mode_off) = *(sda+sda_open_mode_off) & 0x7f;
    putlong(0,sft+sft_dev_drive_ptr_off);
    *(sft+sft_directory_entry_off) = 0;
    putshort(0,sft+sft_directory_sector_off);
    *(sft+sft_attribute_byte_off) = attr;
    putshort(netdrive + 0x8040,sft+sft_device_info_off);
    puttime(sbuf.st_mtime, sft+sft_time_off);
    putlong(sbuf.st_size, sft+sft_size_off);
    putlong(0,sft+sft_position_off);
    putshort(unixfd, sft+sft_fd_off);
    CLR_CARRY;
  break;
  case 0x17:  /* Create/truncate file */
    attr = *(SSPTR+SP);
    make_unix_path(sda+sda_filename1_off+3,unix_path, 1);
    if ((!stat(unix_path, &sbuf) && !(sbuf.st_mode & S_IFREG))) {
      AX = ACCESS_DENIED;
      SET_CARRY;
    }
    if ((unixfd = open(unix_path, O_RDWR|O_CREAT|O_TRUNC, 0664)) < 0) {
      AX = ACCESS_DENIED;
      SET_CARRY;
    }
    cp = strrchr(unix_path,'/');
    extract(cp+1,sft+sft_name_off,sft+sft_name_off+0x8);
    putlong(0,sft+sft_dev_drive_ptr_off);
    *(sft+sft_open_mode_off) = 1;
    *(sft+sft_directory_entry_off) = 0;
    putshort(0,sft+sft_directory_sector_off);
    *(sft+sft_attribute_byte_off) = attr;
    putshort(netdrive + 0x8040,sft+sft_device_info_off);
    stat(unix_path, &sbuf);
    puttime(sbuf.st_mtime, sft+sft_time_off);
    putlong(0,sft+sft_size_off);
    putlong(0,sft+sft_position_off);
    putshort(unixfd, sft+sft_fd_off);
    CLR_CARRY;
  break;
  case 0x1b:  /* Findfirst */
    attr = *(sda+sda_search_attribute_off);
    make_unix_path(sda+sda_filename1_off+3,unix_path, 0);
    cp = strrchr(unix_path, '/');
    *cp = '\0';
    extract(cp+1,tbase, text);
    strncpy(sdb+sdb_template_name_off,tbase,8);
    strncpy(sdb+sdb_template_name_off+8,text,3);
    if (attr & VOLUME) { /* looking for a label */
      strncpy(volname,"NATIVE  FS ",11);
      strncpy(sdb+sdb_file_name_off,volname,11);
      *(sdb+sdb_file_attr_off) = 0x8;
      CLR_CARRY;
    }
    *(sdb+sdb_attribute_off) = attr;
    *(sdb+sdb_drive_letter_off) = 0x80; /* sb 0x80 + drive - 1? */
    dlist = get_candidates(unix_path, 0);
find_next:
    if (dlist != NULL) {
      if (dlist->stmode & S_IFDIR) { /* Directory */
        *(sdb+sdb_file_attr_off) = ARCHIVE | DIRECTORY;
        if (!(attr & DIRECTORY)) {
          dlist = free_entry(dlist);
          goto find_next;
        }
      }
      else {
        *(sdb+sdb_file_attr_off) = ARCHIVE; /* regular file */
      }
      putlong(dlist->size,sdb+sdb_file_size_off);
      puttime(dlist->mtime,sdb+sdb_file_time_off);
      strncpy(sdb+sdb_file_name_off, dlist->base, 8);
      strncpy(sdb+sdb_file_name_off+8,dlist->ext,3);
      dlist = free_entry(dlist);
      CLR_CARRY;
    }
    else {
      AX = NO_MORE_FILES;
      SET_CARRY;
    }
  break;
  case 0x1c: /* Findnext */
    attr = *(sdb+sdb_attribute_off);
    goto find_next;
  break;
  case 0x22: /* Process terminated */
  break;
  case 0xe: /* Set file attributes */
  case 0x1d: /* Close all */
  case 0x25: /* undocumented, used in dos4.0+ */
    return 0;
  break;
  default: 
    if (!warned[AL]) {
      printf("unknown redirect AL = 0x%x\n",AL);
      warned[AL] = 1;
    }
  return 0;
  }
  return 0;
}

putlong(amt, mptr)
unsigned int amt; 
unsigned char *mptr; {
  *mptr++ = amt;
  *mptr++ = amt >> 8;
  *mptr++ = amt >> 16;
  *mptr = amt >> 24;
}

putshort(amt, mptr)
unsigned int amt; 
unsigned char *mptr; {
  *mptr++ = amt;
  *mptr = amt >> 8;
}

unsigned short getshort(addr)
unsigned char *addr; {
  unsigned short rval;
  rval = *addr++;
  rval += ((unsigned int)*addr) << 8;
  return rval;
}

unsigned long getlong(addr)
unsigned char *addr; {
  unsigned long rval;
  rval = *addr++;
  rval += ((unsigned int)*addr++) << 8;
  rval += ((unsigned int)*addr++) << 16;
  rval += ((unsigned int)*addr) << 24;
  return rval;
}

puttime(t, addr) 
unsigned long t;
unsigned char *addr; {
  unsigned short time_word, date_word;
  struct tm *the_time;
  the_time = localtime(&t);
  time_word = (the_time->tm_hour << 11) |
              (the_time->tm_min << 5) |
              (the_time->tm_sec/2);
  date_word = ((the_time->tm_year - 80) << 9) |
              ((the_time->tm_mon + 1) << 5) |
              (the_time->tm_mday);
  *addr++ = time_word;
  *addr++ = time_word >> 8;
  *addr++ = date_word;
  *addr = date_word >> 8;
}

/*
 * Expect DOS version number in CX, ES:BX -> DOS list of lists,
 *    DS:SI -> swappable data area (sda)
 */
init_redirect() {
  int category;
  lol = ESPTR + BX;
  sda = DSPTR + SI;
  dos_major = CL;
  dos_minor = CH;
  if ((dos_major == 3) && (dos_minor > 9) && (dos_minor < 31)) {
    category = 0;
  }
  else if ((dos_major == 4) && (dos_minor >= 0) && (dos_minor <= 1)) {
    category = 1;
  }
  else if ((dos_major == 5) && (dos_minor == 0)) {
    category = 2;
  }
  else return;
  init_dos_offsets(category);
  calculate_dos_pointers();
  hostok = 1;
}

init_dos_offsets(ver)
int ver; {
  if (ver) { /* 3.1 to 3.3 is default */
    cds_record_size          = 0x58;
    sda_filename1_off        = 0x9e;
    sda_filename2_off        = 0x11e;
    sda_sdb_off              = 0x19e;
    sda_search_attribute_off = 0x24d;
    sda_open_mode_off        = 0x24e;
  }
}

calculate_dos_pointers() {
  int drive_count, i;
  unsigned short seg, off;
  cptr = lol + lol_cdsfarptr_off;
  LOADW2(off, cptr++);
  LOADW2(seg, cptr++);
/*
 * Point to start of list of current directory structures
 */
  cptr = pc_mem + ((unsigned int)seg << 4) + off;

  drive_count = *(lol + lol_last_drive_off);
  for (i = 0; i < drive_count; i++) {
    if (*(cptr + cds_flags_off) == 0 &&
        *(cptr + cds_flags_off + 1) == 0) {
/*
 * Mark it as a network drive
 */
      *(cptr + cds_flags_off + 1) |= 0xc0;
      printf("marked %c: as network drive\n",
              i+'A');
      AX = netdrive = i;
      cds = cptr;
      break;
    }
    cptr += cds_record_size;
  }

}

make_unix_path(src, dest, lowcasep)
char *src, *dest;
int lowcasep; {
  char c, *save_dest;
  
  strcpy(save_dest = dest, dos_root);
  dest += dos_root_len;
  while (c = *src) {
    if (c == '\\') {
      c = '/';
    }
    else if (c == ' ') {
      continue;
    }
    else if (lowcasep && isupper(c)) {
      c = tolower(c);
    }
  src++;
  *dest++ = c;
  }
  if (*(dest-1) == '.') {
    dest--;
  }
  *dest = '\0';
/* printf("make_unix_path: >%s<\n",save_dest); */
}

lowcase(name)
char *name; {
  while (*name) {
    if (isupper(*name)) *name = tolower(*name);
    name++;
  }
}

/*
 * extract a basename and extension, padding with spaces
 */
extract(src, base, ext)
char *src, *base, *ext; {
  int count = 0;
  while (*src && *src != '.' && count < 8) {
    *base = islower(*src) ? toupper(*src) : *src;
    base++;
    count++;
    src++;
  }
  for ( ; count < 8; count++) {
    *base++ = ' ';
  }
  count = 0;
  if (*src && *src++ == '.') {
    while (*src) {
      *ext = islower(*src) ? toupper(*src) : *src;
      ext++;
      src++;
      count++;
    }
  }
  for ( ; count < 3; count++) {
    *ext++ = ' ';
  }
}

struct dos_entry *get_candidates(path, nullp) 
char *path; 
int nullp; {
  struct dos_entry *list = NULL, *ltmp;
  struct stat sbuf;
  char has_ext, *cp, *cp2, *end_path;
  int len, i;
  struct dirent *candidate;
  DIR *cd;

  lowcase(path);
  if (!(cd = opendir(path))) {
    return NULL;
  }
  end_path = path + strlen(path);
  *end_path++ = '/';
  while (candidate = readdir(cd)) {
    if ((len = candidate->d_namlen) > 12) continue; /* too long */
    if (cp = strchr(cp2 = candidate->d_name,'.')) {
      has_ext = 1;
    }
    else {
      cp = candidate->d_name + len;
      has_ext = 0;
    }
    if (cp - cp2 > 8) continue; /* base too long */
/*
 * do base
 */
    for (i = 0; cp2 < cp; cp2++) {
      if (!(cbase[i++] = dos_legal[*cp2])) {
        break;
      }
    }
    if (cp2 != cp) continue; /* illegal character */
    for ( ; i < 8; i++) {
      cbase[i] = ' ';
    }
    i = 0;
/*
 * do ext
 */
    if (has_ext) {
      cp++;
      cp2 = candidate->d_name + len;
      if (cp2 == cp) { 
	if (len == 1) { /* "." */
	  cbase[0] = '.';
	}
	else {
	  continue; /* '.' is last character in name */
	}
      }
      else if (*cp == '.' && len == 2) { /* ".." */
	cbase[0] = cbase[1] = '.';
	cp2--;
      }
      else if (cp2 - cp > 3) { /* extension too long */
	continue;
      }
      for (i = 0; cp < cp2; cp++) {
        if (!(cext[i++] = dos_legal[*cp])) {
          break;
        }
      }
      if (cp != cp2) continue; /* illegal character */
    }
    for ( ; i < 3; i++) {
      cext[i] = ' ';
    }
/*    printf("trying candidate %s (%s,%s) with (%s,%s)\n",
       candidate->d_name,cbase,cext,tbase,text);  */
/*
 * match bases
 */
    for (i = 0; i < 8; i++) {
      if (!tbase[i]) {
        if (cbase[i] == ' ') i = 3;
        break;
      }
      if (cbase[i] != tbase[i] && tbase[i] != '?') break;
    }
    if (i < 8) continue;
/*
 * match extensions
 */
    for (i = 0; i < 3; i++) {
      if (!text[i]) {
        if (cext[i] == ' ') i = 3;
        break;
      }
      if (cext[i] != text[i] && text[i] != '?') break;
    }
    if (i < 3) continue;
    strcpy(end_path,candidate->d_name);
    if (stat(path, &sbuf)) { 
      continue;
    }
    i = sbuf.st_mode & S_IFMT;
    if (i != S_IFDIR && i != S_IFREG) {
      continue;
    }
/*    printf("matched candidate %s (%s,%s) with (%s,%s)\n",
       candidate->d_name,cbase,cext,tbase,text);   */
    dlist = alloc_dos_entry();
    strncpy(dlist->base,cbase,8);
    strncpy(dlist->ext,cext,3);
    if (nullp) {
      dotruncase(dlist->base,8);
      dotruncase(dlist->ext,3);
    }
    dlist->size = sbuf.st_size;
    dlist->stmode = sbuf.st_mode;
    dlist->mtime =  sbuf.st_mtime;
    dlist->next = list;
    list = dlist;
  }
/*
 * reverse list
 */
  dlist = NULL;
  while (list != NULL) {
    ltmp = list->next;
    list->next = dlist;
    dlist = list;
    list = ltmp;
  }
  closedir(cd);
  return list = dlist;  
}

dotruncase(cp, len)
char *cp;
int len; {
  int i;
  for (i = 0; i < len; i++, cp++) {
    if (*cp == ' ') {
      break;
    }
    if (isupper(*cp)) {
      *cp = tolower(*cp);
    }
  }
  *cp = '\0';
}

struct dos_entry *dfree = NULL;
#define DBLOCK 100
struct dos_entry *alloc_dos_entry() {
  int i;
  struct dos_entry *rval;
  if (!dfree) {
    dfree = (struct dos_entry *)xmalloc(DBLOCK * sizeof(struct dos_entry));
    for (i = 0; i < DBLOCK-1; i++) {
      (dfree+i)->next = dfree+i+1;
    }
  }
  rval = dfree;
  dfree = dfree->next;
  return rval;
}

struct dos_entry *free_entry(list)
struct dos_entry *list; {
  struct dos_entry *rval;
  rval = list->next;
  list->next = dfree;
  dfree = list;
  return rval;
}

free_list(list)
struct dos_entry *list; {
  while (list) {
    list = free_entry(list);
  }
}

#ifdef sun
#include <sys/vfs.h>
#endif

get_disk_space(path)
char *path; {
#ifdef sun
  struct statfs fsbuf;
  unsigned long freebytes, totalbytes;
  if (statfs(path, &fsbuf)) return 0;
  freebytes = fsbuf.f_bsize * fsbuf.f_bavail;
  totalbytes = fsbuf.f_blocks * fsbuf.f_bsize;
  AX = 8;
  CX = 1024;
  DX = freebytes >> 13;
  BX = totalbytes >> 13;
  return 1;
#else
  return 0;
#endif /* sun */
}


