--- /dev/null
+/* $Id$
+ * /etc/fstab installation utility
+ * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
+ *
+ * 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; exactly version 2 of June 1991 is required
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "config.h"
+
+#undef FALSE
+#undef TRUE
+#include <ntfs/types.h> /* for 'FALSE'/'TRUE' libntfs definition */
+#define FALSE FALSE
+#define TRUE TRUE
+
+#include <glib/gmessages.h>
+#include <popt.h>
+#include <locale.h>
+#include <stdlib.h>
+#include <string.h>
+#include <mntent.h>
+#include <glib/ghash.h>
+#include <glib/gstrfuncs.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <captive/macros.h>
+
+#include <ntfs/volume.h>
+
+
+/* Config: */
+#define FILENAME_PROC_PARTITIONS "/proc/partitions"
+#define FILENAME_ETC_FSTAB "/etc/fstab"
+#define FILENAME_ETC_FSTAB_BACKUP FILENAME_ETC_FSTAB ".pre-captive"
+#define FILENAME_ETC_FSTAB_TMP FILENAME_ETC_FSTAB ".tmp"
+
+
+static int optarg_verbose;
+static int optarg_dry;
+enum optarg_mode {
+ OPTARG_MODE_UNDEF =0,
+ OPTARG_MODE_ADD =1,
+ OPTARG_MODE_REMOVE=2,
+ };
+static int /* Do not use 'enum optarg_mode' as 'poptOption.arg' is '(int *)'. */
+ optarg_mode=OPTARG_MODE_UNDEF;
+
+static const struct poptOption popt_table[]={
+#define BUG_FSTAB_POPT(shortname,longname,argInfoP,argP,valP,descripP,argDescripP) \
+ { \
+ longName: (longname), \
+ shortName: (shortname), \
+ argInfo: (argInfoP)|(!(valP) ? 0 : POPT_ARG_VAL), \
+ arg: (void *)argP, \
+ val: (valP), \
+ descrip: (descripP), \
+ argDescrip: (argDescripP), \
+ }
+
+ BUG_FSTAB_POPT('v',"verbose",POPT_ARG_NONE,&optarg_verbose,0,N_("Display additional debug information"),NULL),
+ BUG_FSTAB_POPT('n',"dry" ,POPT_ARG_NONE,&optarg_dry ,0,N_("No real modifications - simulate only"),NULL),
+ BUG_FSTAB_POPT(0 ,"add" ,POPT_ARG_NONE,&optarg_mode ,OPTARG_MODE_ADD ,N_("Add entries to /etc/fstab"),NULL),
+ BUG_FSTAB_POPT(0 ,"remove" ,POPT_ARG_NONE,&optarg_mode ,OPTARG_MODE_REMOVE,N_("Remove entries from /etc/fstab"),NULL),
+
+#undef BUG_FSTAB_POPT
+ POPT_AUTOHELP
+ POPT_TABLEEND
+ };
+
+
+static void devices_hash_key_destroy_func(gchar *device)
+{
+ g_return_if_fail(device!=NULL);
+
+ g_free(device);
+}
+
+static void devices_hash_value_destroy_func(gchar *vol_name)
+{
+ g_return_if_fail(vol_name!=NULL);
+
+ g_free(vol_name);
+}
+
+
+static GHashTable *devices_hash_get(void)
+{
+FILE *fpartitions;
+gchar line[LINE_MAX];
+gint lineno;
+/* map: (gchar *)device -> (gchar *)vol_name */
+GHashTable *devices_hash;
+
+ devices_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
+ (GDestroyNotify)devices_hash_key_destroy_func,
+ (GDestroyNotify)devices_hash_value_destroy_func);
+
+ if (!(fpartitions=fopen(FILENAME_PROC_PARTITIONS,"r")))
+ g_error(_("Cannot open \"%s\": %m"),FILENAME_PROC_PARTITIONS);
+ lineno=0;
+ while (fgets(line,sizeof(line),fpartitions)) {
+unsigned major,minor;
+unsigned long long blocks;
+char device[strlen("/dev/")+100];
+ntfs_volume *volume;
+
+ lineno++;
+ if (lineno<=2)
+ continue;
+ if (4!=sscanf(line,"%u%u%llu%100s",&major,&minor,&blocks,device+strlen("/dev/")))
+ g_error(_("Error parsing line of \"%s\": %s"),FILENAME_PROC_PARTITIONS,line);
+ memcpy(device,"/dev/",strlen("/dev/"));
+ if (!(volume=ntfs_mount(device,MS_RDONLY))) {
+ if (optarg_verbose)
+ g_message(_("not ntfs: %s"),device);
+ continue;
+ }
+ if (optarg_verbose)
+ g_message(_("FOUND ntfs: %s\t%s"),device,volume->vol_name);
+ g_hash_table_insert(devices_hash,g_strdup(device),g_strdup(volume->vol_name));
+ if (ntfs_umount(volume,
+ TRUE)) /* force; close even if it would mean data loss */
+ g_warning(_("Error unmounting volume: %s"),device);
+ }
+ if (fclose(fpartitions))
+ g_error(_("Cannot close \"%s\": %m"),FILENAME_PROC_PARTITIONS);
+ return devices_hash;
+}
+
+
+/* map: (gchar *)dir -> (gpointer)!NULL */
+static GHashTable *dirs_used_hash;
+
+static void dirs_used_hash_key_destroy_func(gchar *dir)
+{
+ g_return_if_fail(dir!=NULL);
+
+ g_free(dir);
+}
+
+
+static void mntent_add_devices_hash_entry(const gchar *device,const gchar *vol_name,FILE *mntentfilew /* user_data */)
+{
+struct mntent mntent_local;
+gint dir_count;
+
+ g_return_if_fail(device!=NULL);
+ g_return_if_fail(vol_name!=NULL);
+ g_return_if_fail(mntentfilew!=NULL);
+
+ CAPTIVE_MEMZERO(&mntent_local);
+ mntent_local.mnt_fsname=(/* de-const */ gchar *)device;
+ mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s",vol_name);
+ dir_count=1;
+ while (g_hash_table_lookup(dirs_used_hash,mntent_local.mnt_dir)) {
+ dir_count++;
+ mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s%d",vol_name,dir_count);
+ }
+ if (optarg_dry || !mkdir("/mnt",0755)) {
+ if (optarg_verbose)
+ g_message(_("Created base mount directory: %s"),"/mnt");
+ }
+ else if (errno!=EEXIST)
+ g_warning(_("Error creating base mount directory \"%s\" for device \"%s\": %m"),
+ "/mnt",mntent_local.mnt_fsname);
+ if (optarg_dry || !mkdir(mntent_local.mnt_dir,0755)) {
+ if (optarg_verbose)
+ g_message(_("Created mount directory for device \"%s\": %s"),
+ mntent_local.mnt_fsname,mntent_local.mnt_dir);
+ }
+ else if (errno!=EEXIST)
+ g_warning(_("Error creating mount directory \"%s\" for device \"%s\": %m"),
+ mntent_local.mnt_dir,mntent_local.mnt_fsname);
+ mntent_local.mnt_type="captive-ntfs";
+ mntent_local.mnt_opts="defaults"; /* 'mntent_local.mnt_opts' must be != NULL ! */
+ if (optarg_verbose)
+ g_message(_("Creating captive-ntfs mntent: %s -> %s"),mntent_local.mnt_fsname,mntent_local.mnt_dir);
+ if (addmntent(mntentfilew,&mntent_local))
+ g_warning(_("Error appending mntent for device \"%s\": %m"),mntent_local.mnt_fsname);
+ g_hash_table_insert(dirs_used_hash,g_strdup(mntent_local.mnt_dir),dirs_used_hash);
+}
+
+
+int main(int argc,char **argv)
+{
+poptContext context;
+int errint;
+FILE *mntentfiler,*mntentfilew;
+gboolean modified=FALSE;
+
+ /* Initialize the i18n stuff */
+ setlocale(LC_ALL,"");
+ bindtextdomain(PACKAGE,LOCALEDIR);
+ textdomain(PACKAGE);
+
+ context=poptGetContext(
+ PACKAGE, /* name */
+ argc,(/*en-const*/const char **)argv, /* argc,argv */
+ popt_table, /* options */
+ POPT_CONTEXT_POSIXMEHARDER); /* flags; && !POPT_CONTEXT_KEEP_FIRST */
+ if (context==NULL) {
+ g_assert_not_reached(); /* argument recognization args_error */
+ return EXIT_FAILURE;
+ }
+ errint=poptReadDefaultConfig(context,
+ TRUE); /* useEnv */
+ if (errint!=0) {
+ g_assert_not_reached(); /* argument recognization args_error */
+ return EXIT_FAILURE;
+ }
+ errint=poptGetNextOpt(context);
+ if (errint!=-1) {
+ g_assert_not_reached(); /* some non-callbacked argument reached */
+ return EXIT_FAILURE;
+ }
+ if (poptPeekArg(context)) {
+ g_error(_("No arguments expected"));
+ return EXIT_FAILURE;
+ }
+ if (optarg_mode==OPTARG_MODE_UNDEF)
+ g_error(_("No run mode specified"));
+
+ dirs_used_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
+ (GDestroyNotify)dirs_used_hash_key_destroy_func,
+ (GDestroyNotify)NULL);
+
+ /* FIXME: locking! */
+ if (!(mntentfiler=setmntent(FILENAME_ETC_FSTAB,"r")))
+ g_error(_("Cannot open \"%s\" for reading: %m"),FILENAME_ETC_FSTAB);
+ if (!(mntentfilew=setmntent((optarg_dry ? "/dev/null" : FILENAME_ETC_FSTAB_TMP),"w")))
+ g_error(_("Cannot open \"%s\" for writing: %m"),FILENAME_ETC_FSTAB_TMP);
+
+ switch (optarg_mode) {
+
+ case OPTARG_MODE_ADD: {
+GHashTable *devices_hash;
+struct mntent *mntent;
+
+ devices_hash=devices_hash_get();
+ while ((mntent=getmntent(mntentfiler))) {
+ if (!g_hash_table_lookup(devices_hash,mntent->mnt_fsname)) {
+ if (!strcmp(mntent->mnt_type,"captive-ntfs")
+ && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) {
+ g_warning(_("Dropping no-longer valid captive filesystem mntent from \"%s\" of device: %s"),
+ FILENAME_ETC_FSTAB,mntent->mnt_fsname);
+ modified=TRUE;
+ continue;
+ }
+ if (addmntent(mntentfilew,mntent))
+ g_error(_("Error copying mntent for device \"%s\": %m"),mntent->mnt_fsname);
+ g_hash_table_insert(dirs_used_hash,g_strdup(mntent->mnt_dir),dirs_used_hash);
+ }
+ else {
+ /* Original mntent is dropped to be replaced by new one. */
+ if (optarg_verbose)
+ g_message(_("Dropping captive mntent to be replaced by new one: %s"),mntent->mnt_fsname);
+ modified=TRUE;
+ }
+ }
+ if (g_hash_table_size(devices_hash))
+ modified=TRUE;
+ g_hash_table_foreach(devices_hash,
+ (GHFunc)mntent_add_devices_hash_entry, /* func */
+ mntentfilew); /* user_data */
+ g_hash_table_destroy(devices_hash);
+ } break;
+
+ case OPTARG_MODE_REMOVE: {
+struct mntent *mntent;
+
+ while ((mntent=getmntent(mntentfiler))) {
+ if (!strcmp(mntent->mnt_type,"captive-ntfs")
+ && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) {
+ if (optarg_verbose)
+ g_message(_("Dropping captive mntent: %s"),mntent->mnt_fsname);
+ if (optarg_dry || !rmdir(mntent->mnt_dir)) {
+ if (optarg_verbose)
+ g_message(_("Deleted mount directory for device \"%s\": %s"),
+ mntent->mnt_fsname,mntent->mnt_dir);
+ }
+ else if (errno!=EEXIST)
+ g_warning(_("Error removing mount directory \"%s\" of device \"%s\": %m"),
+ mntent->mnt_dir,mntent->mnt_fsname);
+ modified=TRUE;
+ continue;
+ }
+ if (addmntent(mntentfilew,mntent))
+ g_error(_("Error copying mntent for device \"%s\": %m"),mntent->mnt_fsname);
+ }
+ } break;
+ }
+
+ if (optarg_verbose)
+ g_message(_("Modified status: %s"),(modified ? _("YES") : _("NO")));
+ if (1!=endmntent(mntentfiler))
+ g_error(_("Cannot close \"%s\" after reading: %m"),FILENAME_ETC_FSTAB);
+ if (!optarg_dry) {
+ if (fchmod(fileno(mntentfilew),0644))
+ g_error(_("Cannot set permissions for \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
+ }
+ if (1!=endmntent(mntentfilew))
+ g_error(_("Cannot close \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
+ if (!optarg_dry) {
+ if (modified) {
+ if (!access(FILENAME_ETC_FSTAB_BACKUP,F_OK)) {
+ if (optarg_verbose)
+ g_message(_("Backup file exists - keeping it intact: %s"),FILENAME_ETC_FSTAB_BACKUP);
+ }
+ else if (errno==ENOENT) {
+ if (optarg_verbose)
+ g_message(_("File \"%s\" backed up to: %s"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
+ if (rename(FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP))
+ g_warning(_("Cannot backup \"%s\" to \"%s\": %m"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
+ }
+ else
+ g_warning(_("Backup file \"%s\" state unknown: %m"),FILENAME_ETC_FSTAB_BACKUP);
+ if (rename(FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB))
+ g_error(_("Cannot move new \"%s\" over old \"%s\": %m"),FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB);
+ }
+ else {
+ if (unlink(FILENAME_ETC_FSTAB_TMP))
+ g_error(_("Cannot remove new unmodified \"%s\": %m"),FILENAME_ETC_FSTAB_TMP);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}