#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <mntent.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "kudzu.h"

#define _(a) a

#define MTAB		"/etc/mtab"
#define FSTAB		"/etc/fstab"

#define PROGRAM_NAME	"updfstab"

struct typeInfo {
    const char * name;
    int class;
    int makeSymlink;
    int defaultPartition;
    const char * descPattern;
};

static const struct typeInfo deviceTypeList[] = {
    { "cdrom",    CLASS_CDROM,	1,  0, NULL		    },
    { "jaz1g.",   CLASS_HD,	0,  4, "jaz 1g"	            },
    { "jaz2g.",	  CLASS_HD,	0,  4, "jaz 2g" 	    },
    { "jaz",	  CLASS_HD,	0,  4, "jaz" 	            },
    { "zip100.",  CLASS_FLOPPY,	0,  4, "zip 100"            },
    { "zip100.",  CLASS_HD,	0,  4, "zip 100"	    },
    { "zip250.",  CLASS_FLOPPY,	0,  4, "zip 250"	    },
    { "zip250.",  CLASS_HD,	0,  4, "zip 250"	    },
    { "zip",      CLASS_FLOPPY,	0,  4, "zip"	            },
    { "zip",      CLASS_HD,	0,  4, "zip"	            },
    { "pocketzip40.",  CLASS_FLOPPY,   0,  4, "clik! 40"    },
    { "pocketzip40.",  CLASS_HD,   0,  4, "clik! 40"        },
    { "pocketzip",  CLASS_FLOPPY,   0,  4, "clik"           },
    { "pocketzip",  CLASS_HD,   0,  4, "clik"               },
    { "ls120.",	  CLASS_FLOPPY,	0,  0, "ls120:ls-120"       },
    { "camera",	  CLASS_HD,	0,  1, "DSC"    	    },
    { "memstick", CLASS_HD,	0,  1, "MSC"	            },
    { "flash",    CLASS_HD,	0,  1, "CompactFlash"       },
    { "flash",    CLASS_HD,     0,  1, "ImageMate"          },
    { "floppy",   CLASS_HD,	0,  0, "USB-FDU"            },
    { "diskonkey", CLASS_HD,    0,  1, "DiskOnKey"          },
    { "microdrive", CLASS_HD,   0,  1, "IBM-DMDM",          },
    { "floppy", CLASS_FLOPPY,	0,  0, "floppy drive",      },

    { NULL,	0,		0,  0, NULL         	    }
};

/* includes trailing NULL */
#define NUM_DEVICE_TYPES    (sizeof(deviceTypeList) / sizeof(struct typeInfo))

struct mountInfo {
    dev_t devNum;
    char * device;
    char * mountPoint;
    int kudzuGenerated;
    int shouldRemove;
    struct mountInfo * next;
};

struct probedDevice {
    const struct typeInfo * type;
    char * device;	    /* /dev entry		    */
    dev_t devNum;
    struct probedDevice * next;
};

struct entryToAdd {
    char * mountPoint;
    char * symlink;		    /* may be NULL */
    struct probedDevice * device;
    struct entryToAdd * next;
};

struct partitionEntry {
    char * device;
    int partition;
};

static int readMountTable(char * fileName, struct mountInfo ** mountListPtr);
static int getDeviceNum(char * path, dev_t * device);
static int probeDeviceType(struct partitionEntry * partList,
			   const struct typeInfo * type, 
			   struct device ** kudzuDevices,
			   struct probedDevice ** list);
static int removeDeadDevices(struct mountInfo * mountTable, 
			     struct probedDevice * deviceList);
static int probeAllDevices(struct partitionEntry * partList,
			   struct probedDevice ** list);
static int buildAddList(struct mountInfo * mountedDevices, 
			struct mountInfo * mountTable, 
			struct probedDevice * deviceList,
			struct entryToAdd ** list,
			int normalize);
static int updateFstab(char * fileName, struct mountInfo * mountTable, 
		       struct entryToAdd * addList, int test);
/* returns NULL on error, NULL terminated list */
static struct partitionEntry * readPartitionList(void);

/* return the device number of the full disk (the one w/ the partition table) */
static dev_t fullDiskDevice(dev_t dev);

static dev_t fullDiskDevice(dev_t dev) {
    int maj = major(dev);
    int min = minor(dev);
    int mask = 0;
	
    switch (maj) {
      /* ide */
      case 3: case 22: case 33: case 34: case 56: case 57: case 88:
      case 89: case 90: case 91:
	mask = 0x3f;
	break;

      /* scsi */
      case 8: case 65: case 66: case 67: case 68: case 69: case 70: case 71:
        mask = 0x0f;
	break;

      /* smartarray */
      case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79:
      case 104: case 105: case 106: case 107: case 108: case 109: case 110: case 111:
	mask = 0x0f;
	break;
      
      /* floppy */
      case 2:
	mask = 0;
	break;
	    
      default:
	mask = 0;
	break;
    }

    return makedev(maj, (min & ~mask));
}

static int getDeviceNum(char * path, dev_t * device) {
    struct stat sb;

    if (stat(path, &sb)) {
	fprintf(stderr, _("cannot stat %s: %s\n"), path, strerror(errno));
	return 1;
    }

    if (!S_ISBLK(sb.st_mode)) {
	fprintf(stderr, _("unexpected file type for %s\n"), path);
	return 1;
    } 

    *device = sb.st_rdev;

    return 0;
}

static int readMountTable(char * fileName, struct mountInfo ** mountListPtr) {
    struct mountInfo * mountList = *mountListPtr;
    struct mntent * mntent;
    FILE * mtab;
    struct mountInfo * newMount;

    if (!(mtab = setmntent(fileName, "r"))) {
	fprintf(stderr, _("failed to open %s for read: %s\n"),
		fileName, strerror(errno));
	return 1;
    }

    while ((mntent = getmntent(mtab))) {
	/* Ignore mounts that aren't of /dev/something; we just don't care */
	if (strncmp(mntent->mnt_fsname, "/dev/", 5)) continue;

	newMount = malloc(sizeof(*newMount));
	newMount->kudzuGenerated = strstr(mntent->mnt_opts, "kudzu") != NULL;
	newMount->shouldRemove = 0;
	newMount->mountPoint = strdup(mntent->mnt_dir);
	newMount->device = strdup(mntent->mnt_fsname);

	if (getDeviceNum(mntent->mnt_fsname, &newMount->devNum))
	    return 1;

	if (!mountList) {
	    mountList = newMount;
	} else {
	    newMount->next = mountList;
	    mountList = newMount;
	}
    }

    endmntent(mtab);

    *mountListPtr = mountList;

    return 0;
}

static int probeDeviceType(struct partitionEntry * partList,
			   const struct typeInfo * type,
			   struct device ** kudzuDevices,
			   struct probedDevice ** list) {
    struct probedDevice * endOfList = *list;
    struct probedDevice * newDevice;
    struct probedDevice * devListPtr;
    struct partitionEntry * part;
    struct device ** aDevicePtr, * aDevice;
    int keepDevice, flag;
    char *devName;

    while (endOfList && endOfList->next) endOfList = endOfList->next;

    for (aDevicePtr = kudzuDevices; *aDevicePtr; aDevicePtr++) {
	aDevice = *aDevicePtr;
	keepDevice = 1;

	if (type->class != CLASS_UNSPEC && 
	    (type->class != aDevice->class))
	    keepDevice = 0;

	if (aDevice->detached)
	    keepDevice = 0;

	if (keepDevice && type->descPattern) {
	    const char * start = type->descPattern;
	    const char * end;
	    char aPattern[50];

	    keepDevice = 0;

	    while (*start) {
		end = strchr(start, ':');
		if (!end) end = start + strlen(start);

		assert((end - start + 1) <= sizeof(aPattern));

		strncpy(aPattern, start, end - start);
		aPattern[end - start] = '\0';

		if (strcasestr(aDevice->desc, aPattern) &&
		    type->class == aDevice->class) {
		    keepDevice = 1;
		    break;
		}

		start = end;
		if (*start == ':') start++;
	    }
	    
	}

	if (keepDevice) {
	    for (part = partList; part->device; part++) {
		if (!strcmp(part->device, aDevice->device)) break;
	    }

	    /* if there are multiple partitions on a device, bail. note
	       the kernel sorts /proc/partitions for us */
	    if (part->device && part[1].device &&
		!strcmp(part[1].device, aDevice->device)) {
		continue;
	    }

	    devName = malloc(strlen(aDevice->device) + 10);
	    
	    if (part->device)
		sprintf(devName, "/dev/%s%d", part->device,
			part->partition);
	    else if (type->defaultPartition)
		sprintf(devName, "/dev/%s%d", aDevice->device,
			type->defaultPartition);
	    else
		sprintf(devName, "/dev/%s", aDevice->device);
	    
	    /* possibly already probed this device w/a different pattern */
	    for (flag = 0, devListPtr = *list; devListPtr;
		 devListPtr = devListPtr->next) {
		if (!strcmp(devListPtr->device, devName)) {
		    flag = 1;
		    break;
		}
	    }
	    
	    if (flag) {
		free(devName);
		continue;
	    }

	    newDevice = malloc(sizeof(*newDevice));
	    newDevice->device = devName;
	    newDevice->type = type;
	    newDevice->next = NULL;

	    if (getDeviceNum(newDevice->device, &newDevice->devNum))
		return 1;

	    if (endOfList) {
		endOfList->next = newDevice;
		endOfList = newDevice;
	    } else {
		*list = newDevice;
		endOfList = newDevice;
	    }
	}
    }
    return 0;
}

static int probeAllDevices(struct partitionEntry * partList,
			   struct probedDevice ** list) {
    const struct typeInfo * aType;
    struct device ** kudzuDevices;
    int x;

    kudzuDevices = probeDevices(CLASS_UNSPEC, BUS_IDE | BUS_SCSI | BUS_MISC, 0);
    if (!kudzuDevices) return 0;
    for (aType = deviceTypeList; aType->name; aType++)
	if (probeDeviceType(partList, aType, kudzuDevices, list)) return 1;
    for (x=0;kudzuDevices[x];x++) {
	    kudzuDevices[x]->freeDevice(kudzuDevices[x]);
    }
    free(kudzuDevices);
    return 0;
}

/* Devices that we added to the mount table, but for which devices no
   longer exist, should be removed */
static int removeDeadDevices(struct mountInfo * mountTable, 
			     struct probedDevice * deviceList) {
    struct mountInfo * mnt;
    struct probedDevice * dev;

    for (mnt = mountTable; mnt; mnt = mnt->next) {
	if (!mnt->kudzuGenerated) continue;

	for (dev = deviceList; dev && (dev->devNum != mnt->devNum);
		dev = dev->next);
	if (!dev)
	    mnt->shouldRemove = 1;
    }

    return 0;
}

static int buildAddList(struct mountInfo * mountedDevices, 
			struct mountInfo * mountTable, 
			struct probedDevice * deviceList,
			struct entryToAdd ** list,
			int normalize) {
    struct probedDevice * device;
    struct mountInfo * mnt, * needle;
    int mountCounters[NUM_DEVICE_TYPES];
    int typeNum;
    char newMntPoint[PATH_MAX];
    char newSymLink[PATH_MAX];
    struct entryToAdd * endOfList = *list, *addition;

    memset(mountCounters, 0, sizeof(mountCounters));

    while (endOfList && endOfList->next) endOfList = endOfList->next;

    /* add new items to fstab */
    for (device = deviceList; device; device = device->next) {
	for (mnt = mountTable; mnt && 
		fullDiskDevice(mnt->devNum) != fullDiskDevice(device->devNum);
	     mnt = mnt->next);

	/* the device is there, and should be left there */
	if (mnt && (!mnt->kudzuGenerated || !normalize))
	    continue;

	typeNum = device->type - deviceTypeList;

	/* find a mountpoint to use */
	while (1) {
	    sprintf(newMntPoint, "/mnt/%s", device->type->name);

	    /* This is absolutely, totally moronic. We're only doing
	       this because iomega thinks it's a good idea. Which it's
	       not. */
	    if (mountCounters[typeNum] ||
		(newMntPoint[strlen(newMntPoint) - 1] == '.')) {
		sprintf(newSymLink, "%d", mountCounters[typeNum]);
		strcat(newMntPoint, newSymLink);
	    }
		
	    strcpy(newSymLink, newMntPoint + 5);

	    mountCounters[typeNum]++;

	    /* if this name is already used by us, we're done */
	    if (mnt && !strcmp(mnt->mountPoint, newMntPoint)) break;

	    /* if this name is already mounted, we can't use it unless it's
	       being used for this device */
	    for (needle = mountedDevices;
		    needle && strcmp(needle->mountPoint, newMntPoint);
		    needle = needle->next) ;
	    if (needle && needle->devNum != device->devNum) continue;

	    /* if this name is used for anything else in the fstab, and
	       it's staying but we're not normalizing, we can't use it */
	    for (needle = mountTable; 
		    needle && strcmp(needle->mountPoint, newMntPoint);
		    needle = needle->next) ;
	    if (needle && !needle->kudzuGenerated) continue;
	    if (needle && !normalize && !needle->shouldRemove) continue;

	    if (device->type->makeSymlink) {
		/* if this symlink is in the mount table, and is staying,
		   we can't use it [same idea as above ] */
		for (needle = mountTable; needle; needle = needle->next) {
		    if (strncmp("/dev/", needle->device, 5)) continue;
		    if (!strcmp(needle->device + 5, newSymLink)) break;
		}
	    }

	    if (needle && !needle->kudzuGenerated) continue;
	    if (needle && !normalize && !needle->shouldRemove) continue;

	    /* if we didn't find the symlink in the mount table, but it's
	       in the filesystem we'll just blow it away */

	    if (needle) {
		/* Normalize the entry. This is okay, because if we'd
		   seen this device in the device list already and it 
		   should be left alone, the mountCounter for this device
		   would be past it's entry. */
		needle->shouldRemove = 1;
	    }

	    /* we can use this one */
	    addition = malloc(sizeof(*addition));
	    addition->mountPoint = strdup(newMntPoint);

	    if (device->type->makeSymlink)
		addition->symlink = strdup(newSymLink);
	    else
		addition->symlink = NULL;

	    addition->device = device;
	    addition->next = NULL;

	    if (endOfList) {
		endOfList->next = addition;
		endOfList = addition;
	    } else {
		*list = addition;
		endOfList = addition;
	    }

	    /* if we normalized this entry, make sure we don't have a dup */
	    if (normalize)
		if (mnt) mnt->shouldRemove = 1;

	    break;
	}
    }

    return 0;
}


static int updateFstab(char * fileName, struct mountInfo * mountTable, 
		       struct entryToAdd * addList, int test) {
    int fd;
    struct stat before, after;
    char * fstab;
    FILE * output, * revoke;
    char * start, * end, * lineStart;
    char * outputFileName = NULL;
    char * revokeFileName = NULL;
    int keepLine;
    struct mountInfo * mnt;
    struct stat sb;
    char * deviceName;
    struct entryToAdd * addition;
    char symlinkSource[PATH_MAX];
    pid_t pid;
    int status;

    /* see if there is anything to do */
    fd = 0;
    if (addList) {
	fd = 1;
    } else  {
	for (mnt = mountTable; mnt; mnt = mnt->next)
	    if (mnt->shouldRemove) fd = 1;
    }

    if (!fd) {
	if (test) printf("(nothing to do)\n");
	return 0;
    }

    if ((fd = open(fileName, O_RDONLY)) < 0) {
	fprintf(stderr, _("failed to open %s for read: %s\n"),
		fileName, strerror(errno));
	return 1;
    }

    if (test) {
	output = stdout;
	outputFileName = NULL;

	revoke = stdout;
	revokeFileName = NULL;
    } else {
	outputFileName = alloca(strlen(fileName) + 5);
	strcpy(outputFileName, fileName);
	strcat(outputFileName, ".NEW");

	if (!(output = fopen(outputFileName, "w"))) {
	    fprintf(stderr, _("failed to open %s: %s\n"),
		    outputFileName, strerror(errno));
	    return 1;
	}

	revokeFileName = alloca(strlen(fileName) + 10);
	strcpy(revokeFileName, fileName);
	strcat(revokeFileName, ".REVOKE");

	if (!(revoke = fopen(revokeFileName, "w"))) {
	    fprintf(stderr, _("failed to open %s: %s\n"),
		    revokeFileName, strerror(errno));
	    return 1;
	}
    }

    fstat(fd, &before);

    fstab = alloca(before.st_size + 2);
    if (read(fd, fstab, before.st_size) != before.st_size) {
	fprintf(stderr, _("failed to read %s: %s\n"), fileName, 
		strerror(errno));
	exit(1);
    }

    close(fd);

    /* make sure there is a trailing \n\0 sequence */
    fstab[before.st_size] = '\0';

    if (fstab[before.st_size - 1] != '\n') {
	fstab[before.st_size] = '\n';
	fstab[before.st_size + 1] = '\0';
    }

    lineStart = fstab;

    /* copy what's there, removing things which need to disappear */
    while (*lineStart) {
	start = lineStart;
	end = strchr(lineStart, '\n');
	*end = '\0';

	while (*start && isspace(*start)) start++;

	if (*start == '#' || !*start) {
	    keepLine = 1;
	} else if (strncmp(start, "/dev/", 5)) {
	    keepLine = 1;
	} else {
	    char * chptr = start;

	    while (!isspace(*chptr) && *chptr) chptr++;
	    
	    if (isspace(*chptr)) {
		char * dev;
		dev_t devNum;

		dev = malloc(chptr - start + 1);
		strncpy(dev, start, chptr - start);
		dev[chptr - start] = '\0';

		if (getDeviceNum(dev, &devNum)) {
		    if (outputFileName) unlink(outputFileName);
		    return 1;
		}

		for (mnt = mountTable; mnt && mnt->devNum != devNum;
		     mnt = mnt->next);

		if (!mnt || !mnt->shouldRemove)
		    keepLine = 1;
		else
		    keepLine = 0;
	    } else {
		keepLine = 1;
	    }
	}

	if (keepLine) {
	    fprintf(output, "%s\n", lineStart);
	} else {
	    if (!test) {
		/* this will fail harmlessly if the directory is in use */
		rmdir(mnt->mountPoint);

		if (!lstat(mnt->device, &sb) && S_ISLNK(sb.st_mode))
		    unlink(mnt->device);
	    }

	    if (test) 
		fprintf(revoke, "REVOKE: ");
	    fprintf(revoke, "%s %s auto defaults\n",
		    mnt->device, mnt->mountPoint);
	}


	lineStart = end + 1;
    }

    if (!test)
	fclose(revoke);

    for (addition = addList; addition; addition = addition->next) {
	if (!addition->symlink) {
	    deviceName = addition->device->device;
	} else {
	    sprintf(symlinkSource, "/dev/%s", addition->symlink);

	    if (!test) {
		unlink(symlinkSource);
		if (symlink(addition->device->device, symlinkSource)) {
		    fprintf(stderr, _("failed to create %s -> %s symlink: %s"),
			    symlinkSource, addition->device->device, 
			    strerror(errno));
		    if (outputFileName) unlink(outputFileName);
		    return 1;
		}
	    }

	    deviceName = symlinkSource;
	}

	if (!test) {
	    if (mkdir(addition->mountPoint, 0775) && errno != EEXIST) {
		fprintf(stderr, _("failed to create %s: %s\n"),
			addition->mountPoint, strerror(errno));

		if (outputFileName) unlink(outputFileName);
		return 1;
	    }
	}

	fprintf(output, "%-23s %-23s %-7s %-15s %d %d\n",
		deviceName,
		addition->mountPoint,
		!(strcmp(addition->device->type->name, "cdrom") &&
		  strcmp(addition->device->type->name, "cdwriter") &&
		  strcmp(addition->device->type->name, "cdrw")) ?
		"iso9660" : "auto",
		!(strcmp(addition->device->type->name, "cdrom") &&
		  strcmp(addition->device->type->name, "cdwriter") &&
		  strcmp(addition->device->type->name, "cdrw")) ?
		"noauto,owner,kudzu,ro" : "noauto,owner,kudzu",
		0, 0);
    }

    if (!test) {
	fclose(output);

	stat(fileName, &after);

	if (before.st_mtime != after.st_mtime ||
	    before.st_dev != after.st_dev ||
	    before.st_ino != after.st_ino ||
	    before.st_size != after.st_size) {
	    fprintf(stderr, _("%s changed during run -- aborting\n"),
		    fileName);
	    unlink(outputFileName);

	    return 1;
	}

	/* do this before moving onto /etc/fstab; this keeps magicdev
	   from noticing the change before the permissions are correct */

	pid = fork();
	if (!pid) {
	    execl("/sbin/pam_console_apply", "/sbin/pam_console_apply",
		  "-f", revokeFileName, "-r", NULL);
	    printf("failed\n");
	    exit(-1);
	}
	waitpid(pid, &status, 0);

	if (!WIFEXITED(status) || WEXITSTATUS(status))
	    fprintf(stderr, _("/sbin/pam_console_apply -r failed\n"));

	pid = fork();
	if (!pid) {
	    execl("/sbin/pam_console_apply", "/sbin/pam_console_apply",
		  "-f", outputFileName, NULL);
	    printf("failed\n");
	    exit(-1);
	}

	waitpid(pid, &status, 0);
	if (!WIFEXITED(status) || WEXITSTATUS(status))
	    fprintf(stderr, _("/sbin/pam_console_apply failed\n"));

	if (rename(outputFileName, fileName)) {
	    fprintf(stderr, _("failed to rename %s to %s: %s\n"),
		    outputFileName, fileName, strerror(errno));
	    unlink(outputFileName);
	    return 1;
	}
    }

    return 0;
}

static struct partitionEntry * readPartitionList(void) {
    FILE * f;
    struct partitionEntry * list = NULL;
    int numItems = 0;
    char line[256];
    int major, minor;
    long long blocks;
    char name[256];
    char * frc;
    char * chptr;
    char lastFullDevice[256];
    long long lastBlockCount = -1;

    f = fopen("/proc/partitions", "r");
    if (!f) {
	fprintf(stderr, _("failed to open /proc/partitions: %s\n"),
		strerror(errno));
	return NULL;
    }

    /* first two lines are junk */
    frc = fgets(line, sizeof(line), f);
    if (frc) frc = fgets(line, sizeof(line), f);

    if (frc) frc = fgets(line, sizeof(line), f);
    while (frc) {
	if (sscanf(line, "%d %d %lld %s", &major, &minor, &blocks, name) != 4) {
	    line[strlen(line) - 1] = '\0';  /* chop */
	    fprintf(stderr, "invalid line in /proc/partitions: '%s'\n", line);

	    /* XXX memory leak */
	    return NULL;
	}

	/* throw away devices w/ aren't partitions, or don't begin with a
	   letter (because we don't know what those are) */
	chptr = name + strlen(name) - 1;
	if (!isdigit(*name) && isdigit(*chptr)) {
	    while (isdigit(*chptr)) chptr--;
	    chptr++;

	    /* if the blocks don't add up properly, give up on this device */
	    if (blocks < 0 || (blocks > lastBlockCount))
		lastBlockCount = -1;

	    if (lastBlockCount != -1) {
		list = realloc(list, (numItems + 1) * sizeof(*list));
		list[numItems].partition = strtol(chptr, NULL, 10);
		*chptr = '\0';
		list[numItems].device = strdup(name);
		numItems++;
	    }
	} else {
	    strcpy(lastFullDevice, name);
	    lastBlockCount = blocks;
	}

	frc = fgets(line, sizeof(line), f);
    }

    if (frc) {
	fprintf(stderr, _("error reading /proc/partitions: %s\n"),
		strerror(errno));

	/* XXX memory leak */

	return NULL;
    }

    list = realloc(list, (numItems + 1) * sizeof(*list));
    list[numItems++].device = NULL;

    fclose(f);

    return list;
}

int main(int argc, const char * argv[]) {
    struct mountInfo * mountList = NULL;
    struct mountInfo * mountTable = NULL;
    struct probedDevice * devices = NULL;
    struct entryToAdd * addList = NULL;
    struct partitionEntry * partitionList;
    int test = 0, normalize = 0;
    int rc;
    poptContext optCon;
    struct poptOption myOptions[] = {
	{ "normalize", 'n', 0, &normalize, 0, 
		"update fstab entries to match device probing order" },
	{ "test", 't', 0, &test, 0,
		"don't update anything, display new fstab on stdout" },
	POPT_AUTOHELP
	{ NULL, '\0', 0, NULL },
    };

    optCon = poptGetContext(PROGRAM_NAME, argc, argv, myOptions, 0);
    poptReadDefaultConfig(optCon, 1);

    if ((rc = poptGetNextOpt(optCon)) < -1) {
	fprintf(stderr, "%s: bad argument %s: %s\n", PROGRAM_NAME,
		poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
		poptStrerror(rc));

    }

    poptFreeContext(optCon);

    if (readMountTable(MTAB, &mountList)) return 1;
    if (readMountTable(FSTAB, &mountTable)) return 1;

    if (!(partitionList = readPartitionList())) return 1;
    if (probeAllDevices(partitionList, &devices)) return 1;

    if (removeDeadDevices(mountTable, devices)) return 1;
    if (buildAddList(mountList, mountTable, devices, &addList, normalize)) 
	return 1;

    if (updateFstab(FSTAB, mountTable, addList, test)) return 1;

    return 0;
}
