/* $Id$ * /etc/fstab installation utility * Copyright (C) 2003 Jan Kratochvil * * 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 */ #define _GNU_SOURCE 1 /* for memrchr() */ #include "config.h" #undef FALSE #undef TRUE #include /* for 'FALSE'/'TRUE' libntfs definition */ #define FALSE FALSE #define TRUE TRUE #include #include #include #include #include #include #include #include #include #include #include #include "../libcaptive-install/proc_partitions.h" #include #include #include #include /* 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; static int optarg_replace; 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), BUG_FSTAB_POPT(0 ,"replace",POPT_ARG_NONE,&optarg_replace,0,N_("Replace existing entries by new ones on --add"),NULL), #undef BUG_FSTAB_POPT POPT_AUTOHELP POPT_TABLEEND }; /* 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_proc_partitions_ntfs_hash_entry (const gchar *device,const gchar *vol_name,FILE *mntentfilew /* user_data */) { struct mntent mntent_local; gint dir_count; gchar *s; g_return_if_fail(device!=NULL); g_return_if_fail(vol_name!=NULL); g_return_if_fail(mntentfilew!=NULL); vol_name=captive_strdup_alloca(vol_name); for (s=(/* de-const */ gchar *)vol_name;*s;s++) { if (!isalnum(*s)) *s='_'; else *s=tolower(*s); } if (!*vol_name) vol_name="noname"; 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,noauto"; /* '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; GHashTable *proc_partitions_ntfs_hash; struct mntent *mntent; gchar *mntent_mem=NULL; size_t mntent_mem_alloc=0; captive_standalone_init(); 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); proc_partitions_ntfs_hash=proc_partitions_ntfs_hash_get(optarg_verbose); do { long mntent_offset_start,mntent_offset_end; gchar *mntent_mem_last_line; size_t mntent_mem_len, mntent_mem_last_line_len; mntent_offset_start=ftell(mntentfiler); mntent=getmntent(mntentfiler); mntent_offset_end=ftell(mntentfiler); g_assert(mntent_offset_end>=mntent_offset_start); mntent_mem_len=mntent_offset_end-mntent_offset_start; if (mntent_mem_len>mntent_mem_alloc) { g_free(mntent_mem); mntent_mem_alloc=2*mntent_mem_len; mntent_mem=g_malloc(mntent_mem_alloc); } if (fseek(mntentfiler,mntent_offset_start,SEEK_SET)) g_warning(_("Error seeking in \"%s\": %m"),FILENAME_ETC_FSTAB); if (mntent_mem_len!=fread(mntent_mem,1,mntent_mem_len,mntentfiler)) g_warning(_("Error reading \"%s\": %m"),FILENAME_ETC_FSTAB); mntent_mem_last_line=NULL; if (mntent_offset_end!=ftell(mntentfiler)) g_warning(_("Invalid position in \"%s\" after fread(3): %m"),FILENAME_ETC_FSTAB); if (mntent_mem_len) { size_t comments_len; if (mntent_mem[mntent_mem_len-1]!='\n') g_warning(_("mntent memory block not newline-terminated from \"%s\""),FILENAME_ETC_FSTAB); if ((mntent_mem_last_line=memrchr(mntent_mem,'\n',mntent_mem_len-1))) { mntent_mem_last_line++; comments_len=mntent_mem_last_line-mntent_mem; mntent_mem_last_line_len=mntent_mem_len-comments_len; if (comments_len!=fwrite(mntent_mem,1,comments_len,mntentfilew)) g_error(_("Error copying comments before device \"%s\" to \"%s\": %m"), (!mntent ? "" : mntent->mnt_fsname),FILENAME_ETC_FSTAB_TMP); } } if (!mntent_mem_last_line) { mntent_mem_last_line=mntent_mem; mntent_mem_last_line_len=mntent_mem_len; } if (mntent && !strcmp(mntent->mnt_type,"captive-ntfs") && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) { switch (optarg_mode) { case OPTARG_MODE_REMOVE: 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; /* NOTREACHED */ case OPTARG_MODE_ADD: if (!g_hash_table_lookup(proc_partitions_ntfs_hash,mntent->mnt_fsname)) g_warning(_("Dropping no-longer valid captive filesystem mntent from \"%s\" of device: %s"), FILENAME_ETC_FSTAB,mntent->mnt_fsname); else if (optarg_replace) { /* 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); } else { gboolean errbool; errbool=g_hash_table_remove(proc_partitions_ntfs_hash,mntent->mnt_fsname); g_assert(errbool); if (optarg_verbose) g_message(_("Keeping existing captive mntent: %s"),mntent->mnt_fsname); break; } modified=TRUE; continue; /* NOTREACHED */ default: g_assert_not_reached(); } } if (mntent_mem_last_line_len!=fwrite(mntent_mem_last_line,1,mntent_mem_last_line_len,mntentfilew)) g_error(_("Error copying mntent for device \"%s\" to \"%s\": %m"),mntent->mnt_fsname,FILENAME_ETC_FSTAB_TMP); if (mntent) g_hash_table_insert(dirs_used_hash,g_strdup(mntent->mnt_dir),dirs_used_hash); } while (mntent); g_free(mntent_mem); if (optarg_mode==OPTARG_MODE_ADD) { if (g_hash_table_size(proc_partitions_ntfs_hash)) modified=TRUE; g_hash_table_foreach(proc_partitions_ntfs_hash, (GHFunc)mntent_add_proc_partitions_ntfs_hash_entry, /* func */ mntentfilew); /* user_data */ } g_hash_table_destroy(proc_partitions_ntfs_hash); 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; }