/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/*
 * This is implementation of getmntent() and friends for DJGPP v2.x.
 *
 * Copyright (c) 1995-96 Eli Zaretskii <eliz@is.elta.co.il>
 *
 * This software may be used freely so long as this copyright notice is
 * left intact.  There is no warranty on this software.
 *
 * ---------------------------------------------------------------------
 *
 * The primary motivation for these functions was the GNU df program,
 * which lists all the mounted filesystems with a summary of the disk
 * space available on each one of them.  However, they are also useful
 * on their own right.
 *
 * Unlike Unix, where all mountable filesystems can be found on special
 * file (and thus implementing these function boils down to reading that
 * file), with MS-DOS it's a mess.  Every type of drive has its own
 * interface; there are JOINed and SUBSTed pseudo-drives and RAM disks;
 * different network redirectors hook DOS in a plethora of incompatible
 * ways; a single drive A: can be mapped to either A: or B:, etc.  That
 * is why this implementation uses almost every trick in the book to get
 * at the intimate details of every drive.  Some places where you might
 * find these tricks are: ``Undocumented DOS, 2nd ed.'' by Schulman et al
 * and Ralf Brown's Interrupt List.
 *
 */
#include <libc/stubs.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include <mntent.h>
#include <dir.h>
#include <dos.h>
#include <bios.h>
#include <dpmi.h>
#include <go32.h>
#include <libc/farptrgs.h>
#include <sys/movedata.h>
#include <libc/unconst.h>

/* Macro to convert a segment and an offset to a "far offset" suitable
   for _farxxx() functions of DJGPP.  */
#ifndef MK_FOFF
#define MK_FOFF(s,o) ((int)((((unsigned long)(s)) << 4) + (unsigned short)(o)))
#endif

#define CDS_JOIN     0x2000
#define CDS_VALID    0xc000
#define REMOVABLE    0
#define FIXED        1

/* Static variables.  */

static char          drive_number = -1;
static char          skip_drive_b = 0;
static char          drive_a_mapping = 0;
static char          cds_drives   = 0;
static unsigned long cds_address;
static int           cds_elsize;
static unsigned short dos_mem_base, our_mem_base;
static struct mntent mntent;
static unsigned char drive_string[128];
static char          *mnt_type;
static unsigned char mnt_dir[128];
static unsigned char mnt_fsname[128];
static          char dev_opts[] = "r ,dev=  ";

static char NAME_dblsp[] = "dblsp";
static char NAME_stac[] = "stac";
static char NAME_ram[] = "ram";
static char NAME_cdrom[] = "cdrom";
static char NAME_net[] = "net";
static char NAME_fd[] = "fd";
static char NAME_hd[] = "hd";
static char NAME_subst[] = "subst";
static char NAME_join[] = "join";

int _is_remote_drive(int);

/* Static helper functions.  */

/*
 * Get the entry for this disk in the DOS Current Directory Structure
 * (CDS).  In case of success, return this drive's attribute word; or
 * 0 in case of failure.  Fill the buffer at CURRDIR with the current
 * directory on that drive.
 * The pointer to the CDS array and the size of the array element
 * (which are DOS version-dependent) are computed when setmntent() is
 * called.
 */
static int
get_cds_entry(int drive_num, char *currdir)
{
  unsigned long  cds_entry_address;
  if (!cds_address)
    {
      *currdir = '\0';
      return 0;
    }

  /* The address of the CDS element for this drive.  */
  cds_entry_address = cds_address + (drive_num - 1)*cds_elsize;
  
  /* The current directory: 67-byte ASCIIZ string at the beginning
     of the CDS structure for our drive.  */
  movedata(dos_mem_base, (cds_entry_address & 0xfffff),
           our_mem_base, (unsigned int)currdir, 0x43);

  /* The drive attribute word is at the offset 43h, right after the
     current directory string.  */
  return _farpeekw(dos_mem_base, cds_entry_address + 0x43);
}

/*
 * For a PC with a single floppy drive, that drive can be referenced
 * as both A: and B:.  This function returns the logical drive number
 * which was last used to reference a physical drive, or 0 if the
 * drive has only one logical drive assigned to it (which means there
 * are two floppies in this system).
 */
static int
assigned_to(int drive_num)
{
  __dpmi_regs r;

  /* Issue Int 21h/AX=440Eh to get logical drive last used to
     reference the physical drive DRIVE_NUM.  */
  r.x.ax = 0x440e;
  r.h.bl = drive_num;
  __dpmi_int(0x21, &r);
  if (r.x.flags & 1)
    return 0;
  return r.h.al;
}

/*
 * Check if the drive is compressed with DoubleSpace.  If it is,
 * get the host drive on which the Compressed Volume File (CVF)
 * resides, put the name of that CVF into MNT_FSNAME[] and return
 * non-zero.
 */
static int
get_doublespace_info(int drive_num)
{
  __dpmi_regs r;

  r.x.ax = 0x4a11;      /* DBLSPACE Int 2Fh API */
  r.x.bx = 1;
  r.h.dl = drive_num - 1; /* 0 = A: */
  __dpmi_int(0x2f, &r);

  if (r.x.ax == 0 && r.h.bl & 0x80)
    {
      int host = r.h.bl & 0x7f;

      /* Compressed drive, get the host and sequence number of the CVF.  */
      sprintf(mnt_fsname, "%c:\\DBLSPACE.%03u", 'A' + host, r.h.bh);
      mnt_type = NAME_dblsp;
      return 1;
    }
  else
    /* Error, or uncompressed drive (might be a host, but don't care).  */
    return 0;
}

/*
 * For a drive compressed with Stacker, get the name of the host
 * of its compressed volume file, fill MNT_FSNAME[] with its
 * name and return non-zero.  If this drive isn't controlled by
 * Stacker, return 0.
 */
static int
get_stacker_info(int drive_num)
{
  __dpmi_regs r;
  unsigned long stac_driver_ptr;

  /* Put a known DWORD into the Transfer Buffer.  If this drive
     isn't compressed with Stacker, it will remain unchanged.  */
  _farpokel(dos_mem_base, __tb, 0xbadabadaU);

  r.x.ax = 0x4404;      /* Stacker Get Driver Address function */
  r.h.bl = drive_num;
  r.x.cx = 4;
  r.x.ds = __tb >> 4;
  r.x.dx = __tb & 15;
  __dpmi_int(0x21, &r);

  if ((stac_driver_ptr = _farpeekl(dos_mem_base, __tb)) == 0xbadabadaU)
    return 0;

  /* This drive MIGHT be compressed with Stacker.  Construct a linear
     address of the far pointer into the Stacker device driver.  */
  stac_driver_ptr = ((stac_driver_ptr >> 12) & 0xffff0)
                    + (stac_driver_ptr & 0xffff);

  /* Sanity check: real-mode addresses are only 20 bit-long, so we can
     safely reject anything that's larger than FFFFFh, lest we get an
     illegal address abort when we try to peek at the signature below.  */
  if (stac_driver_ptr > 0xfffff)
    return 0;

  /* Stacker Anywhere returns pointer to 1 byte before the A55Ah
     signature (which is at offset 1Ah), while all other versions
     of Stacker point to the signature itself.  */
  if (_farpeekw(dos_mem_base,   stac_driver_ptr) == 0xa55a ||
      _farpeekw(dos_mem_base, ++stac_driver_ptr) == 0xa55a)
    {
      /* We have indeed Stacker device driver.  Get the volume
         number (at offset 58h) and host drive (from the 26-byte
         table beginning at offset 70h).  */
      unsigned char seq  = _farpeekb(dos_mem_base, stac_driver_ptr + 0x3e);
      unsigned char host = _farpeekb(dos_mem_base,
                                     stac_driver_ptr + 0x55 + drive_num);

      sprintf(mnt_fsname, "%c:\\STACVOL.%03u", 'A' + host, seq);
      mnt_type = NAME_stac;
      return 1;
    }
  return 0;
}

/*
 * Get the network name which corresponds to a drive DRIVE_NUM.
 * Ideally, _truename() (Int 21h/AH=60h) should return the same
 * string, but some network redirectors don't put a full UNC
 * name into the CDS, and others bypass the CDS altogether.
 */
static int
get_netredir_entry(int drive_num)
{
  __dpmi_regs r;
  unsigned long tb = __tb & 0xfffff;
  unsigned char devname[2];
  int i = -1;

  r.x.ds = r.x.es = (tb >> 4);
  r.x.si = tb & 15;
  r.x.di = r.x.si + 16;

  /* Loop for all the valid list indices, comparing the local device
     name with our disk drive letter, until we find one which matches.

     We could cache entries which we've seen, but (1) I never said
     this will be the fastest function ever; and (2) I can't be sure
     the network configuration won't change between two successive
     calls to getmntent().  */
  while (++i < 35)  /* at the maximum: 32 drives + 3 LPTn devices */
    {
      r.x.cx = 0;
      r.x.bx = i;
      r.x.ax = 0x5f02;
      __dpmi_int(0x21, &r);

      if (r.x.flags & 1)
        {
          if (r.x.ax == 0x12) /* end of list--bail out */
            return 0;

          else
            continue;
        }
      else if ((r.h.bh == 0 || r.h.bh == 2) && r.h.bl == 4)
        {
          /* We have a valid device which is a disk drive (BL = 4).
             Pull in the local device name and if that fits our
             drive number, get its network name.  */
          *(unsigned short *)devname = _farpeekw(dos_mem_base, tb);

          /* The local device name may or may not include the
             colon (Ralf Brown's Interrupt List).  */
          if (devname[0] == '@' + drive_num &&
              (devname[1] == ':' || devname[1] == '\0'))
            {
              movedata(dos_mem_base, tb + 16,
                       our_mem_base, (unsigned)mnt_fsname, 128);
              return 1; /* found! */
            }
        }
    }
  return 0;
}

/*
 * Return 1 if this drive is a CD-ROM drive, 0 otherwise.  Works
 * with MSCDEX 2.x, but what about other CD-ROM device drivers?
 */
static int
is_cdrom_drive(int drive_num)
{
  __dpmi_regs r;

  r.x.ax = 0x150b;      /* CD-ROM Drive Check function */
  r.x.cx = drive_num - 1; /* 0 = A: */
  __dpmi_int(0x2f, &r);

  /* If MSCDEX installed, BX will hold ADADh; AX will be non-zero
     if this drive is supported by MSCDEX.  */
  if (r.x.bx == 0xadad && r.x.ax != 0)
    return 1;
  return 0;
}

/*
 * Return 1 if a CD-ROM drive DRIVE_NUM is ready, i.e. there is a
 * disk in the drive and that disk is a data (not AUDIO) disk.
 */
static int
cdrom_drive_ready(int drive_num)
{
  __dpmi_regs r;
  int i = 2;

  /* Int 2Fh/AX=1505h (Read Volume Table Of Contents) will return
     with error for empty drives or if the disk is an AUDIO disk.  */
  r.x.es = __tb;
  r.x.bx = __tb & 15;
  r.x.cx = drive_num - 1;   /* 0 = A: */
  r.x.dx = 0;   /* get the 1st descriptor (usually, the only one) */

  /* First time after the door is closed the call might fail.
     Therefore try twice before giving up.  */
  do
    {
      r.x.ax = 0x1505;
      __dpmi_int(0x2f, &r);
    } while (--i && (r.x.flags & 1));

  if ((r.x.flags & 1) == 0 && (r.x.ax == 0 || r.x.ax == 1))
    return 1;
  return 0;
}

/*
 * Detect a RAM disk.  We do this by checking if the number of FAT
 * copies (in the Device Parameter Block) is 1, which is typical of
 * RAM disks.  [This doesn't _have_ to be so, but if it's good
 * enough for Andrew Schulman et al (Undocumented DOS, 2nd edition),
 * we can use this as well.]
 */
static int
is_ram_drive(int drive_num)
{
  __dpmi_regs r;

  r.h.ah = 0x32;        /* Get Device Parameter Block function */
  r.h.dl = drive_num;
  __dpmi_int(0x21, &r);

  if (r.h.al == 0)
    {
      /* The pointer to DPB is in DS:DX.  The number of FAT copies is at
         offset 8 in the DPB.  */
      char fat_copies = _farpeekb(dos_mem_base, MK_FOFF(r.x.ds, r.x.dx) + 8);

      return fat_copies == 1;
    }
  return 0;
}

/*
 * Check if the media in this disk drive is fixed or removable.
 * Should only be called after we're sure this ain't CD-ROM or
 * RAM disk, since these might fool you with this call.
 */
static int
media_type(int drive_num)
{
  __dpmi_regs r;

  r.x.ax = 0x4408;
  r.h.bl = drive_num;
  __dpmi_int(0x21, &r);

  if (r.x.flags & 1)
    return -1;
  return r.x.ax;    /* returns 1 for fixed disks, 0 for removable */
}

/* Exported library functions.  */

FILE *
setmntent(char *filename, char *type)
{
  __dpmi_regs r;
  int cds_address_offset;
  /* Need TRUE DOS version. */
  unsigned short true_dos_version = _get_dos_version(1);

  dos_mem_base = _go32_info_block.selector_for_linear_memory;
  our_mem_base = _my_ds();

  drive_number = 0;
  skip_drive_b = 0;

  /* Get and save the pointer to the CDS array and the size of
     the array element.  This is version-dependent.  */
  if (true_dos_version < 0x0300 /* CDS not supported for v2.x */
      || true_dos_version > 0x1000) /* dos emulation (OS/2) ? */
    {
      cds_elsize  = -1;
      cds_address = 0;
      cds_address_offset = 0; /* pacify -Wall */
    }
  else if (true_dos_version == 0x0300)
    {
      cds_address_offset = 0x17;
      cds_elsize = 0x51;
    }
  else
    {
      cds_address_offset = 0x16;
      cds_elsize = (true_dos_version >= 0x0400) ? 0x58 : 0x51;
    }

  if (cds_elsize > 0)
    {
      unsigned long cds_address_ptr;
      
      r.h.ah = 0x52;    /* DOS Get List of Lists call */
      __dpmi_int(0x21, &r);

      /* The pointer to the List of Lists is in ES:BX.  Compute the
         linear address of the pointer to the CDS, which is at version-
         dependent offset from the LoL start.  */
      cds_address_ptr = MK_FOFF(r.x.es, r.x.bx + cds_address_offset);

      /* Get the pointer and compute the linear address of the CDS array.  */
      cds_address = MK_FOFF(_farpeekw(dos_mem_base, cds_address_ptr + 2),
                            _farpeekw(dos_mem_base, cds_address_ptr));
    }

  return (FILE *) 1;
}

static char NAME_unknown[] = "???";
struct mntent *
getmntent(FILE *filep)
{
  if (drive_number == -1)
    return NULL;
  if (filep != (FILE *)1)
    {
      errno = EBADF;    /* fake errno for invalid handle */
      return NULL;
    }

  /* The number of drives known to DOS is returned by the DOS
     SetDisk (Int 21h/AH=0Eh) call.  Any drive beyond that
     number can only come from some device driver which bypasses
     the DOS network redirector interface and instead hooks Int 21h
     directly (Novell NetWare 3.x is a good example).  Such drives
     won't appear in the DOS Current Directory Structure (CDS).  */
  cds_drives = setdisk(0xffff);

  /* There may be a maximum of 32 block devices.  Novell Netware indeed
     allows for 32 disks (A-Z plus 6 more characters from [ to ' */
  while (drive_number < 32)
    {
      unsigned char *p, *q;
      char *truename_result;
      struct ffblk   mnt_ff;
      unsigned char  cds_path[128];
      unsigned short cds_flags = 0;
      int            drvstr_len;
      int            got_fsname = 0;

      drive_number++;
      mnt_dir[0] = '\0';

      /* On PCs with only one floppy drive, it can be mapped to either
         A: or B:, depending on how it was last referenced.  We will
         skip and not report the drive if it is actually mapped to a
         physical drive which we already reported.  This method both
         insures more accurate report and avoids the annoying message
         ``Insert disk...'' from the OS when _truename() below hits
         the disk.

         I assume there is only one such drive in the system, and that
         it is drive A:, otherwise the logic below will fail miserably.  */
      if (drive_number == 2 && skip_drive_b)
        continue;

      /* Work around a possible bug in QDPMI: _truename() would sometimes
         crash the calling program when there is no disk in a (floppy)
         drive.  To avoid this, we have to check if the drive is empty
         with a BIOS call.  */
      if (drive_number <= 2)
        {
          unsigned char buf[512];
          int bios_status, count = 0;

          /* Int 13h/AH=02h returns 6 for disk changed, even if the
             disk isn't readable (e.g., unformatted).  Retry the
             operation after disk change, each time resetting the 
             drive, until something other than 6 is returned or we
             run out of our patience.  */
          while (++count < 10 && (bios_status =
                 biosdisk(2, drive_number - 1, 0, 0, 1, 1, buf)) == 6)
            biosdisk(0, drive_number - 1, 0, 0, 0, 0, NULL);

          /* If the loop ends with nonzero status, fail.  */
          if (bios_status != 0)
            continue;
        }

      /* See whether drive A: is mapped to B: and if it is, change
         DRIVE_NUMBER on the fly and raise the flag to skip drive
         number 2 next time we are called (meaning there is only
         one floppy drive).  */
      if (drive_number == 1 && (drive_a_mapping = assigned_to(1)) > 0)
        {
          skip_drive_b = 1;
          drive_number = drive_a_mapping;
        }

      drvstr_len = sprintf(drive_string, "%c:\\", '@' + drive_number);
      mnt_type = NAME_unknown;

      /* For the ``File System Name'' we use one of the following:

         * X:\DBLSPACE.NNN or X:\STACVOL.NNN for compressed drives,
           where X: is the host drive of the compressed volume and NNN
           is the volume sequence number;
         * What _truename() returns for the root directory, in case
           it isn't the usual ``X:\'';
         * The name of the volume label;
         * The string ``Drive X:'' (where X is the drive letter).

         These are used in the above order.  */

      /* See what 2160 can tell us.  If it fails, there ain't no such
         drive, as far as DOS is concerned.  */
      truename_result = _truename(drive_string, mnt_fsname);

      /* Get some info from the DOS Current Directory Structure (CDS).
         We've already hit the disk with _truename(), so CDS now
         contains valid and up to date data.  */
      if (drive_number <= cds_drives)
        cds_flags = get_cds_entry(drive_number, cds_path);

      if (truename_result != NULL)
        {

          /* Determine the type of this drive, if not known already.

             We use one of the following types:

             fd     for floppy disks
             hd     for hard disks
             dblsp  for disks compressed with DoubleSpace
             stac   for disks compressed with Stacker
             cdrom  for CD-ROM drives
             ram    for RAM disks
             subst  for SUBSTed directories
             join   for JOINed disks
             net    for networked drives
          */
          if (mnt_type[0] == '?')
            {
              int disk_type = media_type(drive_number);

              if (is_ram_drive(drive_number))
                mnt_type = NAME_ram;
              else if (is_cdrom_drive(drive_number))
                {
                  /* Empty CD-ROM drives do NOT fail _truename(),
                     so we must see if there is a disk in the drive.  */
                  if (cdrom_drive_ready(drive_number))
                    mnt_type = NAME_cdrom;
                  else
                    continue;           /* don't report this drive */
                }
              /* _is_remote_drive() needs zero-based disk number  */
              else if (_is_remote_drive(drive_number - 1) == 1)
                mnt_type = NAME_net;
              else if (disk_type == REMOVABLE)
                mnt_type = NAME_fd;
              else if (disk_type == FIXED)
                mnt_type = NAME_hd;
            }

          /* If the canonicalized name isn't the boring ``X:\'' with X
             the original drive letter, then it is either in the UNC
             form (``\\MACHINE\PATH\'') or it's SUBSTed or JOINed drive,
             and the canonicalized name might actually say something
             interesting about this drive, so use it.  */
          if (mnt_fsname[1] != ':')
            got_fsname = 1;
          else if (mnt_fsname[0] != drive_string[0])
            {
              /* Detect SUBSTed drives.  This could also be found by
                 looking in the CDS, but we try to keep usage of
                 undocumented features at the bare minimum.
                 For SUBSTed drives, the root directory will be returned
                 by _truename() as some directory on another drive.  */
              mnt_type = NAME_subst;
              got_fsname = 1;
            }
          else
            {
              /* Check for DoubleSpace- or Stacker- compressed drive.  */
              if ((got_fsname = get_doublespace_info(drive_number)) == 0)
                got_fsname = get_stacker_info(drive_number);
            }
        }
      /* JOINed drives fail _truename().  I don't know how to check
         for them without using the CDS.  We will only trust the CDS
         data if bits 14 and 15 aren't both zero, and if the current
         directory path in the CDS isn't empty (this might happen
         after ``JOIN D: /d'').  */
      else if ((cds_flags & CDS_VALID) &&
               (cds_flags & CDS_JOIN)  &&
               cds_path[0] != '\0')
        {
          mnt_type = NAME_join;
          strcpy(drive_string, cds_path);
          drvstr_len = strlen(drive_string);
          strcat(drive_string, "\\");

          /* Don't set got_fsname, so that we get a chance to look for
             a volume label below.  */
        }
      else
        continue;   /* illegal (non-present) drive */

      /* Some network redirectors don't set a UNC path to be
         returned by _truename().  See if 215F02 can help.  */
      if ((!got_fsname || mnt_fsname[0] != '\\') &&
           strcmp(mnt_type, "net") == 0)
        if (get_netredir_entry(drive_number))
          got_fsname = 1;
      if (!got_fsname || strcmp(mnt_type, "cdrom") == 0)
        {
          /* Look for the volume label.  */
          int e = errno;

          strcat(drive_string, "*.*");
          errno = 0;

          if (!findfirst(drive_string, &mnt_ff, FA_LABEL))
            {
              errno = e;

              /* Got label.  Strip out extraneous '.' separator, if present. */
              strcpy(mnt_fsname, mnt_ff.ff_name);
              if (strlen(mnt_fsname) > 8 && mnt_fsname[8] == '.')
                {
                  /* Overlapping copy, don't use strcpy() */
                  p = mnt_fsname + 8;
                  q = p + 1;
                  while ((*p++ = *q++) != '\0');
                }

              got_fsname = 1;
            }

          else if (errno == ENMFILE || errno == ENOENT)
            {
              /* Valid drive, but no label.  Construct default
                 filesystem name.  If drive A: is mapped to B:,
                 call it ``Drive A:''.  */
              (void) strcpy(mnt_fsname, "Drive  :");
              mnt_fsname[6] = (drive_number == 2 && drive_a_mapping == 2)
                              ? 'A'
                              : '@' + drive_number;
              got_fsname = 1;
            }
        }

      if (got_fsname)
        {
          char xdrive[3];

          /* Remove the '*.*' from the drive string for use as mnt_dir */
          drive_string[drvstr_len] = '\0';
          if (mnt_dir[0] == '\0')
            strcpy(mnt_dir, drive_string);

          /* Format mnt_dir[] for beauty.  */
          for (p = mnt_dir; *p; p++)
            {
              if (*p == '\\')
                *p = '/';
              else
                *p = tolower(*p);
            }

          /* Should we convert ``\\HOST\PATH'' into ``HOST:PATH''?  */
          mntent.mnt_fsname = mnt_fsname;
          mntent.mnt_dir    = mnt_dir;
          mntent.mnt_type   = mnt_type;
          mntent.mnt_opts   = dev_opts;

          /* Make CD-ROM drives read-only, others read-write.  */
          if (strcmp(mnt_type, "cdrom") == 0)
            dev_opts[1] = 'o';
          else
            dev_opts[1] = 'w';

          /* Include "dev=XX" in the mnt_opts field, where XX should
             be consistent with what stat() returns in st_dev.  */
          sprintf(xdrive, "%02x", strcmp(mnt_type, "subst")
                                  ? drive_number - 1
                                  : mnt_fsname[0] - 'A');
          dev_opts[7] = xdrive[0];
          dev_opts[8] = xdrive[1];
          mntent.mnt_freq   = -1;
          mntent.mnt_passno = -1;
          mntent.mnt_time   = -1;

          return &mntent;
        }

      /* Go try next drive, if any left.  */
    }
  
  return NULL;
}

int
addmntent(FILE *filep, struct mntent *mnt)
{
  return 1;
}

char *
hasmntopt(struct mntent *mnt, char *opt)
{
  return strstr(mnt->mnt_opts, opt);
}

int
endmntent(FILE *filep)
{
  if (filep != (FILE *)1)
    {
      errno = EBADF;    /* fake errno for invalid handle */
      return NULL;
    }
  drive_number = 0;
  skip_drive_b = 0;
  return 1;
}

#ifdef  TEST

int main()
{
  FILE *mp = setmntent("", "r");
  struct mntent *me;
  int i = 0;

  if (!mp)
    {
      fprintf(stderr, "setmntent() failed\n");
      return 1;
    }

  while ((me = getmntent(mp)) != NULL)
    printf("%d:  %s\t\t%s\t%s\t%s\n", i++,
           me->mnt_fsname, me->mnt_type, me->mnt_opts, me->mnt_dir);
  return 0;
}

#endif
