
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <malloc.h>
#include <stdarg.h>
#include <mntent.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <linux/fd.h>
#include <linux/fs.h>
#include <linux/minix_fs.h>
#include <linux/ext_fs.h>
#include <linux/ext2_fs.h>
#include <linux/xia_fs.h>

#define USE_2M
#include "msdos_fs.h"

/* if set to 1, fdmount will chown the mount point to the user who
   did the mount, and set permissions according to his umask.
*/
#define SETOWNER 1

/* if set to 1, the mount point for fdmount must be owned by the user 
   unless it is the default. Leave this on if you are concerned about
   security.
*/
#define CHKOWNER_MOUNT 1

/* if set to 1, the mount point for fdumount must be owned by the user
   _even if_ it is the default. Should be used only with SETOWNER.
*/
#define CHKOWNER_UMOUNT 1

/* by default, drive names are fd0..3, device names /dev/fd0../dev/fd3,
   and default mountpoints /fd0../fd3
*/
#define NAME_PATTERN               "fd%d"
#define DEFAULT_DIR_PATTERN        "/fd%d"
#define DEVICE_PATTERN             "/dev/fd%d"

#define DEBUG 0

/***********************************************************************/


#define UNKNOWN -1

#define INRANGE(x,lo,hi) ((unsigned)((x)-(lo))<=(hi))

typedef unsigned short word;
typedef unsigned char byte;

typedef struct floppy_struct format;

typedef struct mnt_node {
   struct mnt_node *next;
   struct mntent ms;
} mnt_node;

typedef struct {
   int sect;
   int tracks;
   int heads;
   int totsect;
   int _2m;
   int sectsizec;
} fmt_descr;
   
int locked=0;
char curdev[40] = "";
char *progname = NULL;

enum { T_NULL, T_MINIX, T_DOS, T_EXT2, T_EXT, T_XIA };
char *fsnames[6] = {"???","minix","msdos","ext2","ext","xia"};

void die(const char *text,...) {
   va_list va;
   va_start(va,text);
   vfprintf(stderr,text,va);
   fputc('\n',stderr);
   va_end(va);
   exit(1);
}

void msg(char *text,...) {
   char buff[80];
   va_list p;
   va_start(p,text);
   vsprintf(buff,text,p);
   va_end(p);
   printf("%s (%s): %s\n",progname,curdev,buff);
}

#define errmsg msg

#if 0
void print_format(format *F) {
   static int rates[4] = { 500,300,250,1000 };
   printf("%d sectors\n"
          "%d sectors per track\n"
          "%d heads\n"
          "%d tracks\n"
          "double stepping: %s\n"
	  "gap1: %d, gap2: %d, rate: %d Kbps, cod: %s, perp: %d\n"
	  "sectsize: %d, 2M: %s, steprate: %d, hut: %d\n"
          "format name: %s\n",
          F->size, F->sect, F->head, F->track,
          (F->stretch==UNKNOWN)?"?":(F->stretch?"yes":"no"),
	  F->gap, F->fmt_gap, rates[F->rate&0x03], (F->rate&0x80)?"FM":"MFM",
	  F->rate & FD_PERP, 128<<((((F->rate&0x38)>>3)+2)%8), 
          (F->rate&FD_2M)?"yes":"no",F->spec1>>4, F->spec1%0x0F,
          F->name?"<none>":F->name);
}
#else
#define print_format(f)
#endif

void *xmalloc(int size) {
   void *p = malloc(size);
   if (!p) die("out of memory");
   return p;
}

/* Make a canonical pathname from PATH.  Returns a freshly malloced string.
   It is up the *caller* to ensure that the PATH is sensible.  i.e.
   canonicalize ("/dev/fd0/.") returns "/dev/fd0" even though ``/dev/fd0/.''
   is not a legal pathname for ``/dev/fd0.''  */
/* shamelessly copied from mount sourcecode */
char *
canonicalize (const char *path)
{
  char *canonical = xmalloc (PATH_MAX + 1);
  char *p = canonical;
  
  if (path == NULL)
    return NULL;

#if 0  
  if (streq (path, "none"))
    {
      strcpy (canonical, path);
      return canonical;
    }
  if (strchr (path, ':') != NULL)
    {
      strcpy(canonical, path);
      return canonical;
    }
#endif

  if (*path == '/')
    {
      /* We've already got an absolute path in PATH, but we need at
	 least one char in canonical[] on entry to the loop below.  */
      *p = *path++;
    }
  else
    {
      getcwd (canonical, PATH_MAX);
      p = canonical + strlen (canonical) - 1;
      if (*p != '/')
	*++p = '/';
    }
  
  /* There is at least one character in canonical[],
     and the last char in canonical[], *p, is '/'.  */
  while ((*path != '\0') && (p < canonical + PATH_MAX))
    if (*p != '/')
      {
	*++p = *path++;
      }
    else
      {
	if (path[0] == '/')
	  {
	    path++;		/* eliminate duplicate slashes (``//'') */
	  }
	else if ((path[0] == '.') && ((path[1] == '\0') || (path[1] == '/')))
	{
	  path++;		/* eliminate ``/.'' */
	}
	else if ((path[0] == '.') && (path[1] == '.')
		 && ((path[2] == '\0') || (path[2] == '/')))
	  {
	    while ((p > canonical) && (*--p != '/'))
	      /* ascend on ``/../'' */
	      ;
	    path += 2;
	  }
	else
	  {
	    *++p = *path++;	/* not a special case, just copy char */
	  }
      }
  if (p >= (canonical + PATH_MAX))
    die ("path too long");

  if (*p == '/')
    --p;			/* eliminate trailing slash */

  *++p = '\0';
  
  return canonical;
}

/**********************************************************************/

mnt_node *mounted_list = NULL, *mounted_list_end = NULL;
int mtab_changed;

const char *mtab_filename = "/etc/mtab";
const char *lock_filename = "/etc/mtab~";

mnt_node *get_mounted(const char *devname) {
   mnt_node *mnt;
   for(mnt=mounted_list;mnt;mnt=mnt->next)
      if (strcmp(mnt->ms.mnt_fsname,devname)==0) return mnt;
   return NULL;
}

void _dup(char **s) {
   *s = *s?strdup(*s):NULL;
}

void _free(char *s) {
   if (s) free(s);
}

void free_mtab_node(mnt_node *ent) {
   _free(ent->ms.mnt_fsname);
   _free(ent->ms.mnt_dir);
   _free(ent->ms.mnt_type);
   _free(ent->ms.mnt_opts);
   free(ent);
}

void add_mtab(const struct mntent *ent) {
   mnt_node *mm;
   mm=xmalloc(sizeof(mnt_node));
   if (!mounted_list)
      mounted_list=mounted_list_end=mm;
   else
      mounted_list_end->next=mm;
   mounted_list_end=mm;
   memcpy(&mm->ms,ent,sizeof(struct mntent));
   _dup(&mm->ms.mnt_fsname);
   _dup(&mm->ms.mnt_dir);
   _dup(&mm->ms.mnt_type);
   _dup(&mm->ms.mnt_opts);
   mm->next=NULL;
   mtab_changed=1;
}

/* remove all mtab entries with special entry 'devname'. */

void remove_mtab(const char *devname) {
   mnt_node *mm,*prev,*nxt;
   mm=mounted_list;
   prev=NULL;
   while(mm) {
     nxt=mm->next;
     if (strcmp(mm->ms.mnt_fsname,devname)==0) {
        if (!prev)
           mounted_list=mm->next;
        else
           prev->next=mm->next;
	free_mtab_node(mm);
     }
     else {
        prev=mm;
     }
     mm=nxt;
   }
   mounted_list_end=prev;
   mtab_changed=1;
}

/* read table of mounted drives from /etc/mtab and put it in mounted_list.
*/
void read_mtab() {
   FILE *mtab;
   struct mntent *ent;
   mounted_list=mounted_list_end=NULL;
   mtab=setmntent(mtab_filename,"r");
   while((ent=getmntent(mtab)))
      add_mtab(ent);
   endmntent(mtab);
   mtab_changed=0;
}

void save_mtab() {
   FILE *mtab;
   mnt_node *mnt,*next;
   mtab=setmntent(mtab_filename,"w");
   for(mnt=mounted_list;mnt;mnt=next) {
      addmntent(mtab,&mnt->ms);
      next=mnt->next;
      free_mtab_node(mnt);
   }
   mounted_list=mounted_list_end=NULL;
   endmntent(mtab);
   mtab_changed=0;
}

inline void lock_mtab() {
    int fd;
    if((fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0744)) < 0) {
       die("Cannot create lock file %s: %s",lock_filename,strerror(errno));
    }
    close(fd);
    locked=1;
}

inline void unlock_mtab() {
    unlink(lock_filename);
    locked=0;
}

void lock_read_mtab() {
   lock_mtab();
   read_mtab();
   mtab_changed=0;
}

void save_unlock_mtab() {
   if (mtab_changed) save_mtab();
   unlock_mtab();
}

/*********************************************************************/

int probe_drive(char *devname) {
   char drive_name[17];
   int e,fd,type;
   
   drive_name[16]=0;
   fd=open(devname,O_RDONLY | O_NDELAY);
   if (fd<=0) return 0;

   e=ioctl(fd,FDGETDRVTYP,(void*)drive_name);
   if (e) {
      printf("ioctl(FDGETDRVTYP) failed on %s: %s",devname,strerror(errno));
      close(fd);
      return 0;
   }

   close(fd);

   if (strchr("DHQdhq",drive_name[0])) {
      type=atoi(drive_name+1);
      if (type==360 || type==720 || type==1200 || 
          type==1440 || type==2880)
      {
         return type;
      }
   }
   printf("unknown drive type '%s' for %s",drive_name,devname);
   return 0;
}



/* identify the type of file system from the given boot sector/
   super block and read out the available format parameters
   into *fmt.
   super = first 2K of the disk.

   return the type if identified filesystem (T_DOS,T_MINIX,...)
   or 0 for error.
*/

int id_fstype(byte *super, fmt_descr *fmt) {

#define minix ((struct minix_super_block*)(super+1024))
#define dos   ((struct msdos_boot_sector*)super)
#define ext2  ((struct ext2_super_block*)(super+1024))
#define ext   ((struct ext_super_block*)(super+1024))
#define xia   ((struct xiafs_super_block*)(super+1024))

   memset(fmt,0,sizeof(*fmt));

   /* we look for Unix-type filesystems first because mkfs doesn't
      overwrite the first 1K of the disk, so a DOS filesystem might
      be detected even though the data area is overwritten.
   */

   /* look for Minix filesystem */

   if (minix->s_magic==MINIX_SUPER_MAGIC ||
       minix->s_magic==MINIX_SUPER_MAGIC2 ||
       minix->s_magic==NEW_MINIX_SUPER_MAGIC)
   {
      fmt->totsect=minix->s_nzones * (2<<minix->s_log_zone_size);
      return T_MINIX;
   }

   /* look for ext2 filesystem */

   if (ext2->s_magic==EXT2_SUPER_MAGIC ||
       ext2->s_magic==EXT2_PRE_02B_MAGIC)
   {
      fmt->totsect=ext2->s_blocks_count * (2<<ext2->s_log_block_size);
      return T_EXT2;
   }

   /* look for ext filesystem */

   if (ext->s_magic==EXT_SUPER_MAGIC)
   {
      fmt->totsect=ext->s_nzones * (2<<ext->s_log_zone_size);
      return T_EXT;
   }

   /* look for xia filesystem */

   if (xia->s_magic==_XIAFS_SUPER_MAGIC)
   {
      fmt->totsect=xia->s_nzones * xia->s_zone_size/512;
      return T_XIA;
   }

   /* add more format types here ... */

   /* look for MS-DOG filesystem 
      this is more a looking for hints than checking a 
      well-defined identification (which doesn't exist), and 
      I don't expect it to be 100% reliable. 
   */
   
   if (dos->bootid==0xAA55 ||             /* check boot sector id */
       (*(byte*)(super+512)>=0xF0 &&      /* check media descriptor */
        *(word*)(super+513)==0xFFFF) ||
        strncmp(dos->fat_type,"FAT",3)==0) /* check FAT id string */ 
   {
      int sect,heads,tracks,totsect,s_cyl;

      totsect=dos->sectors;
      if (totsect==0) totsect=dos->total_sect;
      sect=dos->secs_track;
      heads=dos->heads;
      s_cyl=sect*heads;

      /* sanity checks */
      if (!INRANGE(heads,1,2) || 
          !INRANGE(sect,3,60) ||
          !INRANGE(totsect,100,10000) ||
          totsect % s_cyl != 0)
      {
         /* try media descriptor (very old DOS disks) */
         switch(super[512]) {
          case 0xfe:             /* 160k */
                     tracks=40; 
                     sect=8; 
                     heads=1;
                     break;
          case 0xfc:             /* 180k */
                     tracks=40;
                     sect=9;
                     heads=1;
                     break;
          case 0xff:             /* 320k */
                     tracks=40;
                     sect=8;
                     heads=2;
                     break;
          case 0xfd:             /* 360k */
                     tracks=40;
                     sect=9;
                     heads=2;
                     break;
          case 0xf9:             /* 1.2M */
                     tracks=80;
                     sect=15;
                     heads=2;
                     break;
          default:
                     goto no_dos;
         }
         totsect=tracks*sect*heads;
      }
      else {
         tracks=totsect/s_cyl;
      }

      fmt->sect    = sect;
      fmt->tracks  = tracks;
      fmt->heads   = heads;
      fmt->totsect = totsect;
      fmt->_2m=0;
      fmt->sectsizec=2;  /* 512 bytes */
      
      /* check for 2M format */
      if (strncmp(dos->banner,"2M",2)==0) {
         int inftm=dos->InfTm;
         if (INRANGE(inftm,76,510)) {
            fmt->sectsizec=super[inftm];
            if (!INRANGE(fmt->sectsizec,0,7)) fmt->sectsizec=2;
         }
         fmt->_2m=1;
      }
      else {
         fmt->sectsizec=2;
      }
      return T_DOS;
   }

no_dos:

   return 0;  /* disk format not identified. */
   
}

int chk_mountpoint(char *dir,int is_default) {
   int must_own;
   struct stat st;

   if (stat(dir,&st)!=0) {
      errmsg("Can't access %s: %s\n",dir,strerror(errno));
      return 0;
   }

   if (!S_ISDIR(st.st_mode)) {
      errmsg("%s is not a directory\n",dir);
      return 0;
   }

   if (!is_default && access(dir,W_OK)!=0) {
      errmsg("No write permission to %s\n",dir);
      return 0;
   }

#if CHKOWNER_MOUNT
   /* user specified mount points must be owned by the user
      unless the user is root
   */
   must_own=(getuid()!=0) && !is_default;
   if (must_own && st.st_uid!=getuid()) {
      errmsg("Not owner of %s\n",dir);
      return 0;
   }
#endif

   return 1;
}

/* open the given device (which oughta be a floppy disk), figure out
   the format, and mount it.
   All message output here should be done via msg() and errmsg().
*/

#define DO_IOCTL(fd,code,parm)                               \
        { if (ioctl(fd,code,parm)!=0) {                      \
	     errmsg("ioctl(code) failed: %s",strerror(errno));  \
	     goto err_close;                                 \
          }                                                  \
        }

int do_mount(char *devname,char *_mountpoint,
             int flags,int force,int is_default,int drivetype) 
{
   int fd,e,fstype,_umask;
   format F;
   char *fsname;
   fmt_descr fmt;
   struct mntent ms;
   struct floppy_drive_struct drivstat;
   char options[80]; 
   char super[2048];
   char *mountpoint;
   
   strcpy(curdev,devname);
   
   if (access(devname,R_OK)!=0)
      die("no access to %s",devname);
   
   if (!(flags&MS_RDONLY) && access(devname,W_OK)!=0)
      die("no write access to %s",devname);
      
   lock_read_mtab();
   
   if(get_mounted(devname)) {
      if (!force) {
         errmsg("already mounted");
	 goto err_unlock;
      }
      else {
	 msg("already in /etc/mtab, trying to mount anyway!");
      }
   }

   mountpoint=canonicalize(_mountpoint);
   
   if (!chk_mountpoint(mountpoint,is_default)) goto err_unlock;

   /* all right, it's ok to mount. Now try to figure out
      the details.
   */

   fd=open(devname,O_RDONLY);
   if (fd<0) {
      errmsg("error opening device: %s",strerror(errno));
      goto err_unlock;
   }

   errno=0;   
   lseek(fd,0,SEEK_SET);
   read(fd,super,sizeof(super));
   if (errno) {
      errmsg("error reading boot/super block: %s",strerror(errno));
      goto err_close;
   }

   /* check if disk is write protected
      note: we don't need to poll (FDPOLLDRVSTAT) here because 
      the previous superblock read updated the state.
   */
   DO_IOCTL(fd,FDGETDRVSTAT, &drivstat);
   if (!(drivstat.flags & FD_DISK_WRITABLE)) flags|=MS_RDONLY;

   /* get the auto-detected floppy parameters */
   DO_IOCTL(fd,FDGETPRM,&F);

#if DEBUG
   printf("autodetected format:\n\n");
   print_format(&F);
#endif
   
   fstype=id_fstype(super,&fmt);
   if (fstype==0) {
      errmsg("unknown filesystem type");
      goto err_close;
   }
   fsname=fsnames[fstype];

   if (fstype==T_DOS) {
      F.sect  = fmt.sect;
      F.track = fmt.tracks;
      F.head  = fmt.heads;
      F.size  = fmt.totsect;
      F.rate &= ~FD_2M;
      if (fmt._2m) {
         F.rate &= ~0x38;
         F.rate |= (((fmt.sectsizec+6)%8)<<3) | FD_2M;
      }
   }
   else {
      /* hope that the track layout was detected correctly and figure
         out the number of tracks from the fs size.
      */
      int s_cyl = F.sect*F.head;
      int tr;

      if (!s_cyl) goto err_close;

      tr=fmt.totsect/s_cyl;
      if (fmt.totsect%s_cyl==0 && INRANGE(tr,30,83)) {
         /* was detected ok! */
         F.track=tr;
         F.size=fmt.totsect;
      }
      else {
         errmsg("sorry, can't figure out format (%s filesystem)", fsname);
         goto err_close;
      }
   }
   F.stretch = (drivetype!=360 && F.track<43);

#if DEBUG
   printf("setting format:\n\n");
   print_format(&F);
#endif
   
   DO_IOCTL(fd,FDSETPRM,&F);

   close(fd);  


   /* prepare the /etc/mtab entry and mount the floppy.
   */

   if (fstype==T_DOS) flags &= ~(MS_NOEXEC|MS_NODEV);
   
   *options=0;
   strcat(options,(flags&MS_RDONLY) ? "ro"      : "rw");
   strcat(options,(flags&MS_NOSUID) ? ",nosuid" : "");
   strcat(options,(flags&MS_NODEV)  ? ",nodev"  : "");
   strcat(options,(flags&MS_NOEXEC) ? ",noexec" : "");
   strcat(options,(flags&MS_SYNCHRONOUS) ? ",sync" : "");

   e=mount(devname,mountpoint,fsname,flags|MS_MGC_VAL,NULL);
   if (e) {
      errmsg("failed to mount %s %dK-disk: %s",
	  fsname,F.size/2,strerror(errno));
      goto err_unlock;
   }

#if SETOWNER
#if 0
    _umask = getumask();
#else
    _umask = umask(0);
    umask(_umask);
#endif
    e=chown(mountpoint,getuid(),getgid());
    if (e) errmsg("warning: chown failed");
    e=chmod(mountpoint,~_umask & 0777);
    if (e) errmsg("warning: chmod failed");
#endif
   
   msg("mounted %s %dK-disk (%s) on %s",fsname,F.size/2,
       (flags&MS_RDONLY) ? "readonly":"read/write", mountpoint);

   /* add entry to /etc/mtab */
   ms.mnt_fsname=devname;
   ms.mnt_dir=mountpoint;
   ms.mnt_type=fsname;
   ms.mnt_opts=options;
   ms.mnt_freq=0;
   ms.mnt_passno=0;
   add_mtab(&ms);

   save_unlock_mtab();
   return 0;

err_close:
   close(fd);
err_unlock:
   save_unlock_mtab();
   return -1;
}

int do_umount(const char *devname,int force) {
   int e,uid;
   mnt_node *mnt;
   char *mountpoint;
   struct stat st;

   lock_read_mtab();
   strcpy(curdev,devname);

   mnt=get_mounted(devname);
   if (!mnt) {
      if (!force) {
	 errmsg("not mounted");
	 save_unlock_mtab();
	 return -1;
      }
      else {
         msg("not in /etc/mtab, trying to unmount anyway!");
      }
   }
   else {
      mountpoint=mnt->ms.mnt_dir;

#if CHKOWNER_UMOUNT
      e=stat(mountpoint,&st);
      if (e) {
         errmsg("Cannot access %s: %s\n",mountpoint,strerror(errno));
         goto err;
      }
      uid=getuid();
      if (uid!=0 && st.st_uid!=uid) {
         errmsg("Not owner of mounted directory: UID=%d\n",st.st_uid);
         goto err;
      }
   }
#endif
   
   e=umount(devname);
   if (e) {
      errmsg("failed to unmount: %s\n",strerror(errno));
      goto err;
   }
   remove_mtab(devname);

#if 0
   /* have to check whether user's own for this. */
   chown(mountpoint,0,0);   /* back to root */
   chmod(mountpoint,0700);  /* no public permissions */
#endif

   msg("disk unmounted");

   save_unlock_mtab();
   return 0;

err:
   save_unlock_mtab();
   return -1;
}

void list_drives() {
   mnt_node *mnt;
   int i,type;
   char devname[10];

   read_mtab();
/*
   printf("NAME  DEVICE     TYPE MOUNTPOINT STATUS\n");
*/
   printf("NAME   TYPE  STATUS\n");

   for(i=0;i<4;i++) {
      sprintf(devname,DEVICE_PATTERN,i);   
      type=probe_drive(devname);
      if (type) {
         mnt=get_mounted(devname);
         if (mnt) {
            printf(" " NAME_PATTERN "  %4dK  mounted on %s (%s %s)\n",
                   i,type,
                   mnt->ms.mnt_dir,
                   mnt->ms.mnt_type,
                   mnt->ms.mnt_opts);
         }
         else {
            printf(" " NAME_PATTERN "  %4dK  not mounted\n",
                   i,type);
         }
      }
   }
}

int daemon_mode(char *devname,char *mountpoint,int mountflags,
                int interval,int drivetype) 
{
   int e,fd,disk_in,prev_disk_in,first;
   struct floppy_drive_struct state;
   mnt_node *mnt;

   strcpy(curdev,devname);
   fd=open(devname,O_RDONLY|O_NDELAY);
   if (fd<=0) {
      errmsg("error opening device: %s",strerror(errno));
      return -1;
   }

   prev_disk_in=0;
   first=1;

   ioctl(fd,FDFLUSH);
   while(1) {
      usleep(interval*100000);
      e=ioctl(fd,FDPOLLDRVSTAT,&state);
      if (e) {
         msg("ioctl(FDPOLLDRVSTAT) failed: %s",strerror(errno));
         return -1;
      }
      disk_in=!(state.flags & FD_VERIFY);
      if (disk_in && !prev_disk_in && !first) {
	 msg("disk inserted");
	 ioctl(fd,FDFLUSH);
         do_mount(devname,mountpoint,mountflags,0,1,drivetype);
      }
      if (!disk_in && prev_disk_in) {
	 msg("disk removed");
	 read_mtab();
	 mnt=get_mounted(devname);
	 if (mnt) {
            if (!hasmntopt(&mnt->ms,"sync"))
               msg("arg!! wasn't mounted sync");
	    /* should check for dirty buffers here! */
	    do_umount(devname,0);
	 }
      }
      prev_disk_in=disk_in;
      first=0;
   }
}

void syntax() {
   printf("usage: fdmount [options] drive_name [mount_dir]\n"
          "       fdumount [options] drive_name\n"
          "       fdlist\n"
          "       fdmountd [options] drive_name [mount_dir]\n"
          "\n"
          "    -r --readonly    mount read-only\n"
          "    -s --sync        synchronize writes\n"
          "       --nodev       ignore device flags\n"
          "       --nosuid      ignore suid flags\n"
          "       --noexec      ignore executable flags\n"
          "    -f --force       force mount/unmount\n"
          "    -l --list        list known drives\n"
          "    -d --daemon      fork and run as daemon\n"
          "       -i n\n"
          "       --interval n  set probing interval for -d [0.1sec]\n" 
          "    -h --help        this message\n\n");
   exit(1);
}

int main(int argc, char **argv) {
   int i,e,c,is_default,optidx=0;
   char *drivename,*mountpoint=NULL,def_mountpoint[40],devname[40];
   int drivetype;
   static int opt_force=0, opt_list=0, opt_daemon=0,
          opt_interval=10,opt_help=0,opt_umount=0;
   enum { _NOEXEC=10, _NODEV, _NOSUID };
   int mountflags=0;

   static struct option longopt[] = {
   { "readonly", 0, NULL, 'r' },
   { "noexec",   0, NULL, _NOEXEC },
   { "nodev",    0, NULL, _NODEV },
   { "nosuid",   0, NULL, _NOSUID },
   { "sync",     0, NULL, 's' },
   { "force",    0, &opt_force,    1 },
   { "list",     0, &opt_list,     1 },
   { "daemon",   0, &opt_daemon,   1 },
   { "interval", 1, &opt_interval, 'i' },
   { "help",     0, &opt_help,     1 },
   {0}
   };

   if (geteuid()!=0) 
      die("Must run with EUID=root");
   
   opt_umount=(strcmp(argv[0],"fdumount")==0);
   opt_daemon=(strcmp(argv[0],"fdmountd")==0);
   opt_list  =(strcmp(argv[0],"fdlist")==0);

   while(1) {
      c=getopt_long(argc,argv,"rsfldi:h",longopt,&optidx);
      if (c==-1) break;
      switch(c) {
       case 'r': mountflags |= MS_RDONLY;
                 break;
       case 's': mountflags |= MS_SYNCHRONOUS;
                 break;
       case _NOEXEC:
                 mountflags |= MS_NOEXEC;
                 break;
       case _NODEV:
                 mountflags |= MS_NODEV;
                 break;
       case _NOSUID:
                 mountflags |= MS_NOSUID;
                 break;
       case 'l': opt_list=1;
                 break;
       case 'f': opt_force=1;
		 break;
       case 'd': opt_daemon=1;
                 break;
       case 'i': opt_interval=atoi(optarg);
                 break;
       case 'h': opt_help=1;
                 break;
       case 0:   break;
       default:  syntax();
      }
   }

   drivename=argv[optind++];
   if (drivename) mountpoint=argv[optind++];
   if (optind<argc) syntax();
   
   if (!drivename) drivename="fd0";

   progname=argv[0];
   
   for(i=0;i<4;i++) {
      char dr[4];
      sprintf(dr,NAME_PATTERN,i);
      if (strcmp(dr,drivename)==0) {
         sprintf(devname,DEVICE_PATTERN,i);
         sprintf(def_mountpoint,DEFAULT_DIR_PATTERN,i);
         break;
      }
   }
   if (i==4) {
      die("invalid drive name: %s\n"
            "use fd[0-3]\n",
            drivename);
   }
   if (!mountpoint) mountpoint=def_mountpoint;
   is_default=(strcmp(mountpoint,def_mountpoint)==0);

   if (opt_interval==0) opt_interval=10;
   if (opt_interval>10000) opt_interval=10000;

   /* get drive type */
   drivetype=probe_drive(devname);
   if (!drivetype) {
      die("drive %s does not exist",drivename);
   }

   e=0;
   if (opt_help) {
      syntax();
   }
   else if (opt_list) {
      list_drives();
   }
   else if (opt_daemon) {
      if (getuid()!=0) {
         die("must be root to run as daemon");
      }
      if (1 /* fork()==0 */) {
         daemon_mode(devname,mountpoint,mountflags,
                     opt_interval,drivetype);
      }
   }
   else if (opt_umount) {
      e=do_umount(devname,opt_force);
   }
   else {
      if (getuid()!=0) mountflags |= MS_NODEV|MS_NOSUID;
      e=do_mount(devname,mountpoint,mountflags,
                 opt_force,is_default,drivetype);
   }

   return e;
}
