52a6afcbbe7d57a5a23f8bbeefe9e819f22a1709
[udpgate.git] / src / bundle-util.c
1 /* $Id$
2  * UDP Gateway single-file bundle utility functions
3  * Copyright (C) 2004 Jan Kratochvil <project-udpgate@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 #include <glib/gmessages.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <glib/gmem.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <glib/gstrfuncs.h>
33
34 #include "bundle-util.h"        /* self */
35 #include "bundle.h"
36 #include "main.h"
37
38
39 static G_CONST_RETURN guint8 *bundle_util_file_retrieve(guint32 *data_length_return,const gchar *basename)
40 {
41 const guint8 *data;
42
43         g_return_val_if_fail(data_length_return!=NULL,NULL);
44         g_return_val_if_fail(basename!=NULL,NULL);
45
46         if (optarg_verbose)
47                 g_message(_("Retrieving internally stored: %s"),basename);
48         data=g_hash_table_lookup(bundle_hash_new(),basename);
49         g_return_val_if_fail(data!=NULL,NULL);
50         *data_length_return=GUINT32_FROM_BE(*(guint32 *)data);
51         data+=sizeof(*data_length_return);
52
53         return data;
54 }
55
56 void bundle_util_file_insert(const void *data,gsize data_length,const gchar *basename)
57 {
58 guint8 *buffer;
59
60         g_return_if_fail(data!=NULL);
61         g_return_if_fail(basename!=NULL);
62
63         if (optarg_verbose)
64                 g_message(_("Internally storing autogenerated: %s"),basename);
65         udpgate_newn(buffer,sizeof(guint32)+data_length);
66         *((guint32 *)buffer)=GUINT32_TO_BE(data_length);
67         g_assert(data_length==GUINT32_FROM_BE(*(guint32 *)buffer));
68         memcpy(buffer+sizeof(data_length),data,data_length);
69         /* FIXME: Missing duplicity check! */
70         /* FIXME: Missing g_free() on the element removal! */
71         g_hash_table_insert(bundle_hash_new(),g_strdup(basename),buffer);
72 }
73
74 /* Returns TRUE if it safe to overwrite/unlink the file.
75  */
76 static gboolean bundle_util_file_backup_conditional(const gchar *pathname,const gchar *basename)
77 {
78 const guint8 *data;
79 guint32 data_length;
80 int fd;
81 guint8 *data_found;
82 int got;
83 char strftime_buffer[LINE_MAX];
84 const gchar *destination;
85 time_t time_current;
86
87         g_return_val_if_fail(pathname!=NULL,FALSE);
88         g_return_val_if_fail(basename!=NULL,FALSE);
89
90         if (!(data=bundle_util_file_retrieve(&data_length,basename)))
91                 return FALSE;
92
93         if (-1==(fd=open(pathname,O_RDONLY))) {
94                 if (errno==ENOENT)
95                         return TRUE;
96                 else {
97                         g_warning(_("Error checking file modifications of \"%s\": %m"),pathname);
98                         return FALSE;
99                         }
100                 }
101
102         /* WARNING: 'data_found' allocated in this block! */
103         data_found=g_malloc(data_length+1);
104         if (-1==(got=read(fd,data_found,data_length+1)))
105                 g_warning(_("Error reading during the check of file modifications of \"%s\": %m"),pathname);
106         if (close(fd))
107                 g_warning(_("Error closing the file \"%s\" during the check of its modifications: %m"),pathname);
108         /* memcmp(3) requires 'data_found'! */
109         if (got==(int)data_length && !memcmp(data_found,data,data_length)) {
110                 g_free(data_found);
111                 return TRUE;
112                 }
113         g_free(data_found);
114
115         time_current=time(NULL);        /* It is segfault to gmtime(NULL). */
116         if (!strftime(strftime_buffer,sizeof(strftime_buffer),"GMT%FT%T",gmtime(&time_current))) {
117                 g_warning("strftime(3): %m");   /* shouldn't happen */
118                 return FALSE;
119                 }
120         destination=udpgate_printf_alloca("%s-%s-%d",pathname,strftime_buffer,(int)getpid());
121         if (rename(pathname,destination)) {
122                 g_warning(_("Error renaming your modified file \"%s\" to the backup \"%s\", giving up: %m"),
123                                 pathname,destination);
124                 return FALSE;
125                 }
126
127         return TRUE;
128 }
129
130 gboolean bundle_util_file_remove(const gchar *pathname,const gchar *basename)
131 {
132         g_return_val_if_fail(pathname!=NULL,FALSE);
133         g_return_val_if_fail(basename!=NULL,FALSE);
134
135         if (!bundle_util_file_backup_conditional(pathname,basename))
136                 return FALSE;
137
138         if (unlink(pathname) && errno!=ENOENT) {
139                 g_warning(_("Error removing the file \"%s\": %m"),pathname);
140                 return FALSE;
141                 }
142         
143         return TRUE;
144 }
145
146 struct dir_stack {
147         struct dir_stack *next;
148         gchar *dirname; /* in fact 'pathname' */
149         };
150
151 /* Always creates up to 'dirname(pathname)', never 'pathname' itself! */
152 static struct dir_stack *pathname_mkdirs(const gchar *pathname,mode_t mode)
153 {
154 gchar *dirname;
155 struct dir_stack *r;
156
157         g_return_val_if_fail(pathname!=NULL,NULL);
158
159         dirname=g_path_get_dirname(pathname);
160         if (!strcmp(dirname,pathname)) {
161                 g_free(dirname);
162                 dirname=NULL;
163                 }
164         if (!dirname)
165                 return NULL;
166         r=pathname_mkdirs(dirname,mode);
167         if (mkdir(dirname,mode))
168                 g_free(dirname);
169         else {
170 struct dir_stack *dir_stack;
171
172                 udpgate_new(dir_stack);
173                 dir_stack->dirname=dirname;
174                 dir_stack->next=r;
175                 r=dir_stack;
176                 }
177         return r;
178 }
179
180 static struct dir_stack *bundle_util_file_write_atexit_dir_stack_head;
181 struct file_stack {
182         struct file_stack *next;
183         gchar *pathname;
184         gchar *basename;
185         };
186 static struct file_stack *bundle_util_file_write_atexit_file_stack_head;
187
188 static void bundle_util_file_write_atexit(void)
189 {
190 struct file_stack *file_stack;
191 struct dir_stack *dir_stack;
192
193         /* Always remove files first before their directories! */
194         while ((file_stack=bundle_util_file_write_atexit_file_stack_head)) {
195                 /* Errors already reported: */
196                 bundle_util_file_remove(file_stack->pathname,file_stack->basename);
197                 g_free(file_stack->pathname);
198                 g_free(file_stack->basename);
199                 bundle_util_file_write_atexit_file_stack_head=file_stack->next;
200                 g_free(file_stack);
201                 }
202
203         while ((dir_stack=bundle_util_file_write_atexit_dir_stack_head)) {
204                 if (rmdir(dir_stack->dirname))
205                         g_warning(_("Error cleaning up created temporary directory: %s"),dir_stack->dirname);
206                 g_free(dir_stack->dirname);
207                 bundle_util_file_write_atexit_dir_stack_head=dir_stack->next;
208                 g_free(dir_stack);
209                 }
210 }
211
212 gboolean bundle_util_file_write(const gchar *pathname,const gchar *basename,mode_t pathname_mode,
213                 enum bundle_util_flags flags)
214 {
215 const guint8 *data;
216 guint32 data_length;
217 int fd;
218 struct dir_stack *mkdirs=NULL;
219 static gboolean atexited=FALSE;
220
221         g_return_val_if_fail(pathname!=NULL,FALSE);
222         g_return_val_if_fail(basename!=NULL,FALSE);
223         g_return_val_if_fail(flags&(BUNDLE_UTIL_BACKUP_MASK|BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK),FALSE);
224         /* Currently just unsupported: */
225         g_return_val_if_fail((flags&(BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK))!=BUNDLE_UTIL_MKDIRS_MASK,FALSE);
226
227         if (!(data=bundle_util_file_retrieve(&data_length,basename)))
228                 return FALSE;
229
230         if ((flags&BUNDLE_UTIL_BACKUP_MASK) && !bundle_util_file_backup_conditional(pathname,basename))
231                 return FALSE;
232         
233         if (flags&BUNDLE_UTIL_MKDIRS_MASK) {
234 mode_t dir_mode=pathname_mode;
235
236                 dir_mode|=(pathname_mode&0444)>>2;      /* mode|=('r'->'x') */
237                 mkdirs=pathname_mkdirs(pathname,dir_mode);
238                 }
239
240         if (!atexited) {
241                 atexited=TRUE;
242                 g_atexit(bundle_util_file_write_atexit);
243                 }
244
245         if (flags&BUNDLE_UTIL_TEMPORARY_MASK) {
246 struct dir_stack **mkdirs_tail_pointer;
247
248                 /* Stack current 'mkdirs' in front of the current: bundle_util_file_write_atexit_dir_stack_head
249                  * to remove them in the reverse order than created.
250                  * Removal ordering of the current 'mkdirs' stack is preserved
251                  * as it is already reversed.
252                  */
253                 for (mkdirs_tail_pointer=&mkdirs;*mkdirs_tail_pointer;mkdirs_tail_pointer=&(*mkdirs_tail_pointer)->next);
254                 *mkdirs_tail_pointer=bundle_util_file_write_atexit_dir_stack_head;
255                 bundle_util_file_write_atexit_dir_stack_head=mkdirs;
256                 mkdirs=NULL;
257                 }
258         /* Currently just unsupported: */
259         g_assert(!mkdirs);
260
261         if (-1==(fd=open(pathname,O_WRONLY|O_CREAT|O_TRUNC,pathname_mode))) {
262                 if (errno!=EACCES && errno!=ENOENT)
263                         g_warning(_("Error opening the file \"%s\" for rewrite: %m"),pathname);
264                 return FALSE;
265                 }
266         /* Register the file only if it got already created.
267          * Do not atempt to remove files which are not ours!
268          */
269         if (flags&BUNDLE_UTIL_TEMPORARY_MASK) {
270 struct file_stack *file_stack;
271
272                 /* Register also the file itself. */
273                 udpgate_new(file_stack);
274                 file_stack->pathname=g_strdup(pathname);
275                 file_stack->basename=g_strdup(basename);
276                 file_stack->next=bundle_util_file_write_atexit_file_stack_head;
277                 bundle_util_file_write_atexit_file_stack_head=file_stack;
278                 }
279
280         if ((int)data_length!=write(fd,data,data_length)) {
281                 g_warning(_("Error writing the data of the file \"%s\" being overwritten: %m"),pathname);
282                 close(fd);      /* errors ignored */
283                 return FALSE;
284                 }
285         if (close(fd)) {
286                 g_warning(_("Error closing the file \"%s\" after its rewrite: %m"),pathname);
287                 return FALSE;
288                 }
289         return TRUE;
290 }