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