Implemented automatic /etc/fstab modifications.
[captive.git] / src / install / fstab / main.c
1 /* $Id$
2  * /etc/fstab installation utility
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #undef FALSE
23 #undef TRUE
24 #include <ntfs/types.h> /* for 'FALSE'/'TRUE' libntfs definition */
25 #define FALSE FALSE
26 #define TRUE TRUE
27
28 #include <glib/gmessages.h>
29 #include <popt.h>
30 #include <locale.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <mntent.h>
34 #include <glib/ghash.h>
35 #include <glib/gstrfuncs.h>
36 #include <sys/stat.h>
37 #include <errno.h>
38 #include <unistd.h>
39
40 #include <captive/macros.h>
41
42 #include <ntfs/volume.h>
43
44
45 /* Config: */
46 #define FILENAME_PROC_PARTITIONS  "/proc/partitions"
47 #define FILENAME_ETC_FSTAB        "/etc/fstab"
48 #define FILENAME_ETC_FSTAB_BACKUP FILENAME_ETC_FSTAB ".pre-captive"
49 #define FILENAME_ETC_FSTAB_TMP    FILENAME_ETC_FSTAB ".tmp"
50
51
52 static int optarg_verbose;
53 static int optarg_dry;
54 enum optarg_mode {
55                 OPTARG_MODE_UNDEF =0,
56                 OPTARG_MODE_ADD   =1,
57                 OPTARG_MODE_REMOVE=2,
58                 };
59 static int      /* Do not use 'enum optarg_mode' as 'poptOption.arg' is '(int *)'. */
60                 optarg_mode=OPTARG_MODE_UNDEF;
61
62 static const struct poptOption popt_table[]={
63 #define BUG_FSTAB_POPT(shortname,longname,argInfoP,argP,valP,descripP,argDescripP) \
64                 { \
65                         longName: (longname), \
66                         shortName: (shortname), \
67                         argInfo: (argInfoP)|(!(valP) ? 0 : POPT_ARG_VAL), \
68                         arg: (void *)argP, \
69                         val: (valP), \
70                         descrip: (descripP), \
71                         argDescrip: (argDescripP), \
72                 }
73
74                 BUG_FSTAB_POPT('v',"verbose",POPT_ARG_NONE,&optarg_verbose,0,N_("Display additional debug information"),NULL),
75                 BUG_FSTAB_POPT('n',"dry"    ,POPT_ARG_NONE,&optarg_dry    ,0,N_("No real modifications - simulate only"),NULL),
76                 BUG_FSTAB_POPT(0  ,"add"    ,POPT_ARG_NONE,&optarg_mode   ,OPTARG_MODE_ADD   ,N_("Add entries to /etc/fstab"),NULL),
77                 BUG_FSTAB_POPT(0  ,"remove" ,POPT_ARG_NONE,&optarg_mode   ,OPTARG_MODE_REMOVE,N_("Remove entries from /etc/fstab"),NULL),
78
79 #undef BUG_FSTAB_POPT
80                 POPT_AUTOHELP
81                 POPT_TABLEEND
82                 };
83
84
85 static void devices_hash_key_destroy_func(gchar *device)
86 {
87         g_return_if_fail(device!=NULL);
88
89         g_free(device);
90 }
91
92 static void devices_hash_value_destroy_func(gchar *vol_name)
93 {
94         g_return_if_fail(vol_name!=NULL);
95
96         g_free(vol_name);
97 }
98
99
100 static GHashTable *devices_hash_get(void)
101 {
102 FILE *fpartitions;
103 gchar line[LINE_MAX];
104 gint lineno;
105 /* map: (gchar *)device -> (gchar *)vol_name */
106 GHashTable *devices_hash;
107
108         devices_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
109                         (GDestroyNotify)devices_hash_key_destroy_func,
110                         (GDestroyNotify)devices_hash_value_destroy_func);
111
112         if (!(fpartitions=fopen(FILENAME_PROC_PARTITIONS,"r")))
113                 g_error(_("Cannot open \"%s\": %m"),FILENAME_PROC_PARTITIONS);
114         lineno=0;
115         while (fgets(line,sizeof(line),fpartitions)) {
116 unsigned major,minor;
117 unsigned long long blocks;
118 char device[strlen("/dev/")+100];
119 ntfs_volume *volume;
120
121                 lineno++;
122                 if (lineno<=2)
123                         continue;
124                 if (4!=sscanf(line,"%u%u%llu%100s",&major,&minor,&blocks,device+strlen("/dev/")))
125                         g_error(_("Error parsing line of \"%s\": %s"),FILENAME_PROC_PARTITIONS,line);
126                 memcpy(device,"/dev/",strlen("/dev/"));
127                 if (!(volume=ntfs_mount(device,MS_RDONLY))) {
128                         if (optarg_verbose)
129                                 g_message(_("not ntfs: %s"),device);
130                         continue;
131                         }
132                 if (optarg_verbose)
133                         g_message(_("FOUND ntfs: %s\t%s"),device,volume->vol_name);
134                 g_hash_table_insert(devices_hash,g_strdup(device),g_strdup(volume->vol_name));
135                 if (ntfs_umount(volume,
136                                 TRUE))  /* force; close even if it would mean data loss */
137                         g_warning(_("Error unmounting volume: %s"),device);
138                 }
139         if (fclose(fpartitions))
140                 g_error(_("Cannot close \"%s\": %m"),FILENAME_PROC_PARTITIONS);
141         return devices_hash;
142 }
143
144
145 /* map: (gchar *)dir -> (gpointer)!NULL */
146 static GHashTable *dirs_used_hash;
147
148 static void dirs_used_hash_key_destroy_func(gchar *dir)
149 {
150         g_return_if_fail(dir!=NULL);
151
152         g_free(dir);
153 }
154
155
156 static void mntent_add_devices_hash_entry(const gchar *device,const gchar *vol_name,FILE *mntentfilew /* user_data */)
157 {
158 struct mntent mntent_local;
159 gint dir_count;
160
161         g_return_if_fail(device!=NULL);
162         g_return_if_fail(vol_name!=NULL);
163         g_return_if_fail(mntentfilew!=NULL);
164
165         CAPTIVE_MEMZERO(&mntent_local);
166         mntent_local.mnt_fsname=(/* de-const */ gchar *)device;
167         mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s",vol_name);
168         dir_count=1;
169         while (g_hash_table_lookup(dirs_used_hash,mntent_local.mnt_dir)) {
170                 dir_count++;
171                 mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s%d",vol_name,dir_count);
172                 }
173         if (optarg_dry || !mkdir("/mnt",0755)) {
174                 if (optarg_verbose)
175                         g_message(_("Created base mount directory: %s"),"/mnt");
176                 }
177         else if (errno!=EEXIST)
178                 g_warning(_("Error creating base mount directory \"%s\" for device \"%s\": %m"),
179                                 "/mnt",mntent_local.mnt_fsname);
180         if (optarg_dry || !mkdir(mntent_local.mnt_dir,0755)) {
181                 if (optarg_verbose)
182                         g_message(_("Created mount directory for device \"%s\": %s"),
183                                         mntent_local.mnt_fsname,mntent_local.mnt_dir);
184                 }
185         else if (errno!=EEXIST)
186                 g_warning(_("Error creating mount directory \"%s\" for device \"%s\": %m"),
187                                 mntent_local.mnt_dir,mntent_local.mnt_fsname);
188         mntent_local.mnt_type="captive-ntfs";
189         mntent_local.mnt_opts="defaults";       /* 'mntent_local.mnt_opts' must be != NULL ! */
190         if (optarg_verbose)
191                 g_message(_("Creating captive-ntfs mntent: %s -> %s"),mntent_local.mnt_fsname,mntent_local.mnt_dir);
192         if (addmntent(mntentfilew,&mntent_local))
193                 g_warning(_("Error appending mntent for device \"%s\": %m"),mntent_local.mnt_fsname);
194         g_hash_table_insert(dirs_used_hash,g_strdup(mntent_local.mnt_dir),dirs_used_hash);
195 }
196
197
198 int main(int argc,char **argv)
199 {
200 poptContext context;
201 int errint;
202 FILE *mntentfiler,*mntentfilew;
203 gboolean modified=FALSE;
204
205         /* Initialize the i18n stuff */
206         setlocale(LC_ALL,"");
207         bindtextdomain(PACKAGE,LOCALEDIR);
208         textdomain(PACKAGE);
209
210         context=poptGetContext(
211                         PACKAGE,        /* name */
212                         argc,(/*en-const*/const char **)argv,   /* argc,argv */
213                         popt_table,     /* options */
214                         POPT_CONTEXT_POSIXMEHARDER);    /* flags; && !POPT_CONTEXT_KEEP_FIRST */
215         if (context==NULL) {
216                 g_assert_not_reached(); /* argument recognization args_error */
217                 return EXIT_FAILURE;
218                 }
219         errint=poptReadDefaultConfig(context,
220                         TRUE);  /* useEnv */
221         if (errint!=0) {
222                 g_assert_not_reached(); /* argument recognization args_error */
223                 return EXIT_FAILURE;
224                 }
225         errint=poptGetNextOpt(context);
226         if (errint!=-1) {
227                 g_assert_not_reached(); /* some non-callbacked argument reached */
228                 return EXIT_FAILURE;
229                 }
230         if (poptPeekArg(context)) {
231                 g_error(_("No arguments expected"));
232                 return EXIT_FAILURE;
233                 }
234         if (optarg_mode==OPTARG_MODE_UNDEF)
235                 g_error(_("No run mode specified"));
236
237         dirs_used_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
238                         (GDestroyNotify)dirs_used_hash_key_destroy_func,
239                         (GDestroyNotify)NULL);
240
241         /* FIXME: locking! */
242         if (!(mntentfiler=setmntent(FILENAME_ETC_FSTAB,"r")))
243                 g_error(_("Cannot open \"%s\" for reading: %m"),FILENAME_ETC_FSTAB);
244         if (!(mntentfilew=setmntent((optarg_dry ? "/dev/null" : FILENAME_ETC_FSTAB_TMP),"w")))
245                 g_error(_("Cannot open \"%s\" for writing: %m"),FILENAME_ETC_FSTAB_TMP);
246
247         switch (optarg_mode) {
248
249                 case OPTARG_MODE_ADD: {
250 GHashTable *devices_hash;
251 struct mntent *mntent;
252
253                         devices_hash=devices_hash_get();
254                         while ((mntent=getmntent(mntentfiler))) {
255                                 if (!g_hash_table_lookup(devices_hash,mntent->mnt_fsname)) {
256                                         if (!strcmp(mntent->mnt_type,"captive-ntfs")
257                                                         && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) {
258                                                 g_warning(_("Dropping no-longer valid captive filesystem mntent from \"%s\" of device: %s"),
259                                                                 FILENAME_ETC_FSTAB,mntent->mnt_fsname);
260                                                 modified=TRUE;
261                                                 continue;
262                                                 }
263                                         if (addmntent(mntentfilew,mntent))
264                                                 g_error(_("Error copying mntent for device \"%s\": %m"),mntent->mnt_fsname);
265                                         g_hash_table_insert(dirs_used_hash,g_strdup(mntent->mnt_dir),dirs_used_hash);
266                                         }
267                                 else {
268                                         /* Original mntent is dropped to be replaced by new one. */
269                                         if (optarg_verbose)
270                                                 g_message(_("Dropping captive mntent to be replaced by new one: %s"),mntent->mnt_fsname);
271                                         modified=TRUE;
272                                         }
273                                 }
274                         if (g_hash_table_size(devices_hash))
275                                 modified=TRUE;
276                         g_hash_table_foreach(devices_hash,
277                                         (GHFunc)mntent_add_devices_hash_entry,  /* func */
278                                         mntentfilew);   /* user_data */
279                         g_hash_table_destroy(devices_hash);
280                         } break;
281
282                 case OPTARG_MODE_REMOVE: {
283 struct mntent *mntent;
284
285                         while ((mntent=getmntent(mntentfiler))) {
286                                 if (!strcmp(mntent->mnt_type,"captive-ntfs")
287                                                 && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) {
288                                         if (optarg_verbose)
289                                                 g_message(_("Dropping captive mntent: %s"),mntent->mnt_fsname);
290                                         if (optarg_dry || !rmdir(mntent->mnt_dir)) {
291                                                 if (optarg_verbose)
292                                                         g_message(_("Deleted mount directory for device \"%s\": %s"),
293                                                                         mntent->mnt_fsname,mntent->mnt_dir);
294                                                 }
295                                         else if (errno!=EEXIST)
296                                                 g_warning(_("Error removing mount directory \"%s\" of device \"%s\": %m"),
297                                                                 mntent->mnt_dir,mntent->mnt_fsname);
298                                         modified=TRUE;
299                                         continue;
300                                         }
301                                 if (addmntent(mntentfilew,mntent))
302                                         g_error(_("Error copying mntent for device \"%s\": %m"),mntent->mnt_fsname);
303                                 }
304                         } break;
305                 }
306
307         if (optarg_verbose)
308                 g_message(_("Modified status: %s"),(modified ? _("YES") : _("NO")));
309         if (1!=endmntent(mntentfiler))
310                 g_error(_("Cannot close \"%s\" after reading: %m"),FILENAME_ETC_FSTAB);
311         if (!optarg_dry) {
312                 if (fchmod(fileno(mntentfilew),0644))
313                         g_error(_("Cannot set permissions for \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
314                 }
315         if (1!=endmntent(mntentfilew))
316                 g_error(_("Cannot close \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
317         if (!optarg_dry) {
318                 if (modified) {
319                         if (!access(FILENAME_ETC_FSTAB_BACKUP,F_OK)) {
320                                 if (optarg_verbose)
321                                         g_message(_("Backup file exists - keeping it intact: %s"),FILENAME_ETC_FSTAB_BACKUP);
322                                 }
323                         else if (errno==ENOENT) {
324                                 if (optarg_verbose)
325                                         g_message(_("File \"%s\" backed up to: %s"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
326                                 if (rename(FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP))
327                                         g_warning(_("Cannot backup \"%s\" to \"%s\": %m"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
328                                 }
329                         else
330                                 g_warning(_("Backup file \"%s\" state unknown: %m"),FILENAME_ETC_FSTAB_BACKUP);
331                         if (rename(FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB))
332                                 g_error(_("Cannot move new \"%s\" over old \"%s\": %m"),FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB);
333                         }
334                 else {
335                         if (unlink(FILENAME_ETC_FSTAB_TMP))
336                                 g_error(_("Cannot remove new unmodified \"%s\": %m"),FILENAME_ETC_FSTAB_TMP);
337                         }
338                 }
339
340         return EXIT_SUCCESS;
341 }