3516a690945728fcc3d11595535fac0d9ab62baa
[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 #define _GNU_SOURCE 1   /* for memrchr() */
21
22 #include "config.h"
23
24 #undef FALSE
25 #undef TRUE
26 #include <ntfs/types.h> /* for 'FALSE'/'TRUE' libntfs definition */
27 #define FALSE FALSE
28 #define TRUE TRUE
29
30 #include <glib/gmessages.h>
31 #include <popt.h>
32 #include <locale.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <mntent.h>
36 #include <glib/ghash.h>
37 #include <glib/gstrfuncs.h>
38 #include <sys/stat.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include "../libcaptive-install/proc_partitions.h"
42 #include <ctype.h>
43
44 #include <captive/macros.h>
45
46 #include <ntfs/volume.h>
47
48
49 /* Config: */
50 #define FILENAME_PROC_PARTITIONS  "/proc/partitions"
51 #define FILENAME_ETC_FSTAB        "/etc/fstab"
52 #define FILENAME_ETC_FSTAB_BACKUP FILENAME_ETC_FSTAB ".pre-captive"
53 #define FILENAME_ETC_FSTAB_TMP    FILENAME_ETC_FSTAB ".tmp"
54
55
56 static int optarg_verbose;
57 static int optarg_dry;
58 enum optarg_mode {
59                 OPTARG_MODE_UNDEF =0,
60                 OPTARG_MODE_ADD   =1,
61                 OPTARG_MODE_REMOVE=2,
62                 };
63 static int      /* Do not use 'enum optarg_mode' as 'poptOption.arg' is '(int *)'. */
64                 optarg_mode=OPTARG_MODE_UNDEF;
65
66 static const struct poptOption popt_table[]={
67 #define BUG_FSTAB_POPT(shortname,longname,argInfoP,argP,valP,descripP,argDescripP) \
68                 { \
69                         longName: (longname), \
70                         shortName: (shortname), \
71                         argInfo: (argInfoP)|(!(valP) ? 0 : POPT_ARG_VAL), \
72                         arg: (void *)argP, \
73                         val: (valP), \
74                         descrip: (descripP), \
75                         argDescrip: (argDescripP), \
76                 }
77
78                 BUG_FSTAB_POPT('v',"verbose",POPT_ARG_NONE,&optarg_verbose,0,N_("Display additional debug information"),NULL),
79                 BUG_FSTAB_POPT('n',"dry"    ,POPT_ARG_NONE,&optarg_dry    ,0,N_("No real modifications - simulate only"),NULL),
80                 BUG_FSTAB_POPT(0  ,"add"    ,POPT_ARG_NONE,&optarg_mode   ,OPTARG_MODE_ADD   ,N_("Add entries to /etc/fstab"),NULL),
81                 BUG_FSTAB_POPT(0  ,"remove" ,POPT_ARG_NONE,&optarg_mode   ,OPTARG_MODE_REMOVE,N_("Remove entries from /etc/fstab"),NULL),
82
83 #undef BUG_FSTAB_POPT
84                 POPT_AUTOHELP
85                 POPT_TABLEEND
86                 };
87
88
89 /* map: (gchar *)dir -> (gpointer)!NULL */
90 static GHashTable *dirs_used_hash;
91
92 static void dirs_used_hash_key_destroy_func(gchar *dir)
93 {
94         g_return_if_fail(dir!=NULL);
95
96         g_free(dir);
97 }
98
99
100 static void mntent_add_proc_partitions_ntfs_hash_entry
101                 (const gchar *device,const gchar *vol_name,FILE *mntentfilew /* user_data */)
102 {
103 struct mntent mntent_local;
104 gint dir_count;
105 gchar *s;
106
107         g_return_if_fail(device!=NULL);
108         g_return_if_fail(vol_name!=NULL);
109         g_return_if_fail(mntentfilew!=NULL);
110
111         vol_name=captive_strdup_alloca(vol_name);
112         for (s=(/* de-const */ gchar *)vol_name;*s;s++) {
113                 if (!isalnum(*s))
114                         *s='_';
115                 else
116                         *s=tolower(*s);
117                 }
118         if (!*vol_name)
119                 vol_name="noname";
120
121         CAPTIVE_MEMZERO(&mntent_local);
122         mntent_local.mnt_fsname=(/* de-const */ gchar *)device;
123         mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s",vol_name);
124         dir_count=1;
125         while (g_hash_table_lookup(dirs_used_hash,mntent_local.mnt_dir)) {
126                 dir_count++;
127                 mntent_local.mnt_dir=(/* de-const */ gchar *)captive_printf_alloca("/mnt/captive-%s%d",vol_name,dir_count);
128                 }
129         if (optarg_dry || !mkdir("/mnt",0755)) {
130                 if (optarg_verbose)
131                         g_message(_("Created base mount directory: %s"),"/mnt");
132                 }
133         else if (errno!=EEXIST)
134                 g_warning(_("Error creating base mount directory \"%s\" for device \"%s\": %m"),
135                                 "/mnt",mntent_local.mnt_fsname);
136         if (optarg_dry || !mkdir(mntent_local.mnt_dir,0755)) {
137                 if (optarg_verbose)
138                         g_message(_("Created mount directory for device \"%s\": %s"),
139                                         mntent_local.mnt_fsname,mntent_local.mnt_dir);
140                 }
141         else if (errno!=EEXIST)
142                 g_warning(_("Error creating mount directory \"%s\" for device \"%s\": %m"),
143                                 mntent_local.mnt_dir,mntent_local.mnt_fsname);
144         mntent_local.mnt_type="captive-ntfs";
145         mntent_local.mnt_opts="defaults,noauto";        /* 'mntent_local.mnt_opts' must be != NULL ! */
146         if (optarg_verbose)
147                 g_message(_("Creating captive-ntfs mntent: %s -> %s"),mntent_local.mnt_fsname,mntent_local.mnt_dir);
148         if (addmntent(mntentfilew,&mntent_local))
149                 g_warning(_("Error appending mntent for device \"%s\": %m"),mntent_local.mnt_fsname);
150         g_hash_table_insert(dirs_used_hash,g_strdup(mntent_local.mnt_dir),dirs_used_hash);
151 }
152
153
154 int main(int argc,char **argv)
155 {
156 poptContext context;
157 int errint;
158 FILE *mntentfiler,*mntentfilew;
159 gboolean modified=FALSE;
160 GHashTable *proc_partitions_ntfs_hash;
161 struct mntent *mntent;
162 gchar *mntent_mem=NULL;
163 size_t mntent_mem_alloc=0;
164
165         /* Initialize the i18n stuff */
166         setlocale(LC_ALL,"");
167         bindtextdomain(PACKAGE,LOCALEDIR);
168         textdomain(PACKAGE);
169
170         context=poptGetContext(
171                         PACKAGE,        /* name */
172                         argc,(/*en-const*/const char **)argv,   /* argc,argv */
173                         popt_table,     /* options */
174                         POPT_CONTEXT_POSIXMEHARDER);    /* flags; && !POPT_CONTEXT_KEEP_FIRST */
175         if (context==NULL) {
176                 g_assert_not_reached(); /* argument recognization args_error */
177                 return EXIT_FAILURE;
178                 }
179         errint=poptReadDefaultConfig(context,
180                         TRUE);  /* useEnv */
181         if (errint!=0) {
182                 g_assert_not_reached(); /* argument recognization args_error */
183                 return EXIT_FAILURE;
184                 }
185         errint=poptGetNextOpt(context);
186         if (errint!=-1) {
187                 g_assert_not_reached(); /* some non-callbacked argument reached */
188                 return EXIT_FAILURE;
189                 }
190         if (poptPeekArg(context)) {
191                 g_error(_("No arguments expected"));
192                 return EXIT_FAILURE;
193                 }
194         if (optarg_mode==OPTARG_MODE_UNDEF)
195                 g_error(_("No run mode specified"));
196
197         dirs_used_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
198                         (GDestroyNotify)dirs_used_hash_key_destroy_func,
199                         (GDestroyNotify)NULL);
200
201         /* FIXME: locking! */
202         if (!(mntentfiler=setmntent(FILENAME_ETC_FSTAB,"r")))
203                 g_error(_("Cannot open \"%s\" for reading: %m"),FILENAME_ETC_FSTAB);
204         if (!(mntentfilew=setmntent((optarg_dry ? "/dev/null" : FILENAME_ETC_FSTAB_TMP),"w")))
205                 g_error(_("Cannot open \"%s\" for writing: %m"),FILENAME_ETC_FSTAB_TMP);
206
207         proc_partitions_ntfs_hash=proc_partitions_ntfs_hash_get(optarg_verbose);
208         do {
209 long mntent_offset_start,mntent_offset_end;
210 gchar *mntent_mem_last_line;
211 size_t mntent_mem_len, mntent_mem_last_line_len;
212
213                 mntent_offset_start=ftell(mntentfiler);
214                 mntent=getmntent(mntentfiler);
215                 mntent_offset_end=ftell(mntentfiler);
216                 g_assert(mntent_offset_end>=mntent_offset_start);
217                 mntent_mem_len=mntent_offset_end-mntent_offset_start;
218                 if (mntent_mem_len>mntent_mem_alloc) {
219                         g_free(mntent_mem);
220                         mntent_mem_alloc=2*mntent_mem_len;
221                         mntent_mem=g_malloc(mntent_mem_alloc);
222                         }
223                 if (fseek(mntentfiler,mntent_offset_start,SEEK_SET))
224                         g_warning(_("Error seeking in \"%s\": %m"),FILENAME_ETC_FSTAB);
225                 if (mntent_mem_len!=fread(mntent_mem,1,mntent_mem_len,mntentfiler))
226                         g_warning(_("Error reading \"%s\": %m"),FILENAME_ETC_FSTAB);
227                 mntent_mem_last_line=NULL;
228                 if (mntent_offset_end!=ftell(mntentfiler))
229                         g_warning(_("Invalid position in \"%s\" after fread(3): %m"),FILENAME_ETC_FSTAB);
230                 if (mntent_mem_len) {
231 size_t comments_len;
232
233                         if (mntent_mem[mntent_mem_len-1]!='\n')
234                                 g_warning(_("mntent memory block not newline-terminated from \"%s\""),FILENAME_ETC_FSTAB);
235                         if ((mntent_mem_last_line=memrchr(mntent_mem,'\n',mntent_mem_len-1))) {
236                                 mntent_mem_last_line++;
237                                 comments_len=mntent_mem_last_line-mntent_mem;
238                                 mntent_mem_last_line_len=mntent_mem_len-comments_len;
239                                 if (comments_len!=fwrite(mntent_mem,1,comments_len,mntentfilew))
240                                         g_error(_("Error copying comments before device \"%s\" to \"%s\": %m"),
241                                                         (!mntent ? "<none>" : mntent->mnt_fsname),FILENAME_ETC_FSTAB_TMP);
242                                 }
243                         }
244                 if (!mntent_mem_last_line) {
245                         mntent_mem_last_line=mntent_mem;
246                         mntent_mem_last_line_len=mntent_mem_len;
247                         }
248                 if (mntent
249                                 && !strcmp(mntent->mnt_type,"captive-ntfs")
250                                 && !strncmp(mntent->mnt_fsname,"/dev/",strlen("/dev/"))) {
251                         switch (optarg_mode) {
252
253                                 case OPTARG_MODE_REMOVE:
254                                         if (optarg_verbose)
255                                                 g_message(_("Dropping captive mntent: %s"),mntent->mnt_fsname);
256                                         if (optarg_dry || !rmdir(mntent->mnt_dir)) {
257                                                 if (optarg_verbose)
258                                                         g_message(_("Deleted mount directory for device \"%s\": %s"),
259                                                                         mntent->mnt_fsname,mntent->mnt_dir);
260                                                 }
261                                         else if (errno!=EEXIST)
262                                                 g_warning(_("Error removing mount directory \"%s\" of device \"%s\": %m"),
263                                                                 mntent->mnt_dir,mntent->mnt_fsname);
264                                         modified=TRUE;
265                                         continue;
266                                         /* NOTREACHED */
267
268                                 case OPTARG_MODE_ADD:
269                                         if (!g_hash_table_lookup(proc_partitions_ntfs_hash,mntent->mnt_fsname))
270                                                 g_warning(_("Dropping no-longer valid captive filesystem mntent from \"%s\" of device: %s"),
271                                                                 FILENAME_ETC_FSTAB,mntent->mnt_fsname);
272                                         else {
273                                                 /* Original mntent is dropped to be replaced by new one. */
274                                                 if (optarg_verbose)
275                                                         g_message(_("Dropping captive mntent to be replaced by new one: %s"),mntent->mnt_fsname);
276                                                 }
277                                         modified=TRUE;
278                                         continue;
279                                         /* NOTREACHED */
280                                 
281                                 default: g_assert_not_reached();
282                                 }
283                         }
284                 if (mntent_mem_last_line_len!=fwrite(mntent_mem_last_line,1,mntent_mem_last_line_len,mntentfilew))
285                         g_error(_("Error copying mntent for device \"%s\" to \"%s\": %m"),mntent->mnt_fsname,FILENAME_ETC_FSTAB_TMP);
286                 if (mntent)
287                         g_hash_table_insert(dirs_used_hash,g_strdup(mntent->mnt_dir),dirs_used_hash);
288                 } while (mntent);
289         g_free(mntent_mem);
290         if (optarg_mode==OPTARG_MODE_ADD) {
291                 if (g_hash_table_size(proc_partitions_ntfs_hash))
292                         modified=TRUE;
293                 g_hash_table_foreach(proc_partitions_ntfs_hash,
294                                 (GHFunc)mntent_add_proc_partitions_ntfs_hash_entry,     /* func */
295                                 mntentfilew);   /* user_data */
296                 }
297         g_hash_table_destroy(proc_partitions_ntfs_hash);
298
299         if (optarg_verbose)
300                 g_message(_("Modified status: %s"),(modified ? _("YES") : _("NO")));
301         if (1!=endmntent(mntentfiler))
302                 g_error(_("Cannot close \"%s\" after reading: %m"),FILENAME_ETC_FSTAB);
303         if (!optarg_dry) {
304                 if (fchmod(fileno(mntentfilew),0644))
305                         g_error(_("Cannot set permissions for \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
306                 }
307         if (1!=endmntent(mntentfilew))
308                 g_error(_("Cannot close \"%s\" after writing: %m"),FILENAME_ETC_FSTAB_TMP);
309         if (!optarg_dry) {
310                 if (modified) {
311                         if (!access(FILENAME_ETC_FSTAB_BACKUP,F_OK)) {
312                                 if (optarg_verbose)
313                                         g_message(_("Backup file exists - keeping it intact: %s"),FILENAME_ETC_FSTAB_BACKUP);
314                                 }
315                         else if (errno==ENOENT) {
316                                 if (optarg_verbose)
317                                         g_message(_("File \"%s\" backed up to: %s"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
318                                 if (rename(FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP))
319                                         g_warning(_("Cannot backup \"%s\" to \"%s\": %m"),FILENAME_ETC_FSTAB,FILENAME_ETC_FSTAB_BACKUP);
320                                 }
321                         else
322                                 g_warning(_("Backup file \"%s\" state unknown: %m"),FILENAME_ETC_FSTAB_BACKUP);
323                         if (rename(FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB))
324                                 g_error(_("Cannot move new \"%s\" over old \"%s\": %m"),FILENAME_ETC_FSTAB_TMP,FILENAME_ETC_FSTAB);
325                         }
326                 else {
327                         if (unlink(FILENAME_ETC_FSTAB_TMP))
328                                 g_error(_("Cannot remove new unmodified \"%s\": %m"),FILENAME_ETC_FSTAB_TMP);
329                         }
330                 }
331
332         return EXIT_SUCCESS;
333 }