920049392a75e593cf64aff9ff570b26e5f77a7c
[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 /* Returns TRUE if it safe to overwrite/unlink the file.
57  */
58 static gboolean bundle_util_file_backup_conditional(const gchar *pathname,const gchar *basename)
59 {
60 const guint8 *data;
61 guint32 data_length;
62 int fd;
63 guint8 *data_found;
64 int got;
65 char strftime_buffer[LINE_MAX];
66 const gchar *destination;
67 time_t time_current;
68
69         g_return_val_if_fail(pathname!=NULL,FALSE);
70         g_return_val_if_fail(basename!=NULL,FALSE);
71
72         if (!(data=bundle_util_file_retrieve(&data_length,basename)))
73                 return FALSE;
74
75         if (-1==(fd=open(pathname,O_RDONLY))) {
76                 if (errno==ENOENT)
77                         return TRUE;
78                 else {
79                         g_warning(_("Error checking file modifications of \"%s\": %m"),pathname);
80                         return FALSE;
81                         }
82                 }
83
84         /* WARNING: 'data_found' allocated in this block! */
85         data_found=g_malloc(data_length+1);
86         if (-1==(got=read(fd,data_found,data_length+1)))
87                 g_warning(_("Error reading during the check of file modifications of \"%s\": %m"),pathname);
88         if (close(fd))
89                 g_warning(_("Error closing the file \"%s\" during the check of its modifications: %m"),pathname);
90         /* memcmp(3) requires 'data_found'! */
91         if (got==(int)data_length && !memcmp(data_found,data,data_length)) {
92                 g_free(data_found);
93                 return TRUE;
94                 }
95         g_free(data_found);
96
97         time_current=time(NULL);        /* It is segfault to gmtime(NULL). */
98         if (!strftime(strftime_buffer,sizeof(strftime_buffer),"GMT%FT%T",gmtime(&time_current))) {
99                 g_warning("strftime(3): %m");   /* shouldn't happen */
100                 return FALSE;
101                 }
102         destination=udpgate_printf_alloca("%s-%s-%d",pathname,strftime_buffer,(int)getpid());
103         if (rename(pathname,destination)) {
104                 g_warning(_("Error renaming your modified file \"%s\" to the backup \"%s\", giving up: %m"),
105                                 pathname,destination);
106                 return FALSE;
107                 }
108
109         return TRUE;
110 }
111
112 gboolean bundle_util_file_remove(const gchar *pathname,const gchar *basename)
113 {
114         g_return_val_if_fail(pathname!=NULL,FALSE);
115         g_return_val_if_fail(basename!=NULL,FALSE);
116
117         if (!bundle_util_file_backup_conditional(pathname,basename))
118                 return FALSE;
119
120         if (unlink(pathname) && errno!=ENOENT) {
121                 g_warning(_("Error removing the file \"%s\": %m"),pathname);
122                 return FALSE;
123                 }
124         
125         return TRUE;
126 }
127
128 struct dir_stack {
129         struct dir_stack *next;
130         gchar *dirname; /* in fact 'pathname' */
131         };
132
133 /* Always creates up to 'dirname(pathname)', never 'pathname' itself! */
134 static struct dir_stack *pathname_mkdirs(const gchar *pathname,mode_t mode)
135 {
136 gchar *dirname;
137 struct dir_stack *r;
138
139         g_return_val_if_fail(pathname!=NULL,NULL);
140
141         dirname=g_path_get_dirname(pathname);
142         if (!strcmp(dirname,pathname)) {
143                 g_free(dirname);
144                 dirname=NULL;
145                 }
146         if (!dirname)
147                 return NULL;
148         r=pathname_mkdirs(dirname,mode);
149         if (mkdir(dirname,mode))
150                 g_free(dirname);
151         else {
152 struct dir_stack *dir_stack;
153
154                 udpgate_new(dir_stack);
155                 dir_stack->dirname=dirname;
156                 dir_stack->next=r;
157                 r=dir_stack;
158                 }
159         return r;
160 }
161
162 static struct dir_stack *bundle_util_file_write_atexit_dir_stack_head;
163 struct file_stack {
164         struct file_stack *next;
165         gchar *pathname;
166         gchar *basename;
167         };
168 static struct file_stack *bundle_util_file_write_atexit_file_stack_head;
169
170 static void bundle_util_file_write_atexit(void)
171 {
172 struct file_stack *file_stack;
173 struct dir_stack *dir_stack;
174
175         /* Always remove files first before their directories! */
176         while ((file_stack=bundle_util_file_write_atexit_file_stack_head)) {
177                 /* Errors already reported: */
178                 bundle_util_file_remove(file_stack->pathname,file_stack->basename);
179                 g_free(file_stack->pathname);
180                 g_free(file_stack->basename);
181                 bundle_util_file_write_atexit_file_stack_head=file_stack->next;
182                 g_free(file_stack);
183                 }
184
185         while ((dir_stack=bundle_util_file_write_atexit_dir_stack_head)) {
186                 if (rmdir(dir_stack->dirname))
187                         g_warning(_("Error cleaning up created temporary directory: %s"),dir_stack->dirname);
188                 g_free(dir_stack->dirname);
189                 bundle_util_file_write_atexit_dir_stack_head=dir_stack->next;
190                 g_free(dir_stack);
191                 }
192 }
193
194 gboolean bundle_util_file_write(const gchar *pathname,const gchar *basename,mode_t pathname_mode,
195                 enum bundle_util_flags flags)
196 {
197 const guint8 *data;
198 guint32 data_length;
199 int fd;
200 struct dir_stack *mkdirs=NULL;
201 static gboolean atexited=FALSE;
202
203         g_return_val_if_fail(pathname!=NULL,FALSE);
204         g_return_val_if_fail(basename!=NULL,FALSE);
205         g_return_val_if_fail(flags&(BUNDLE_UTIL_BACKUP_MASK|BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK),FALSE);
206         /* Currently just unsupported: */
207         g_return_val_if_fail((flags&(BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK))!=BUNDLE_UTIL_MKDIRS_MASK,FALSE);
208
209         if (!(data=bundle_util_file_retrieve(&data_length,basename)))
210                 return FALSE;
211
212         if ((flags&BUNDLE_UTIL_BACKUP_MASK) && !bundle_util_file_backup_conditional(pathname,basename))
213                 return FALSE;
214         
215         if (flags&BUNDLE_UTIL_MKDIRS_MASK) {
216 mode_t dir_mode=pathname_mode;
217
218                 dir_mode|=(pathname_mode&0444)>>2;      /* mode|=('r'->'x') */
219                 mkdirs=pathname_mkdirs(pathname,dir_mode);
220                 }
221
222         if (!atexited) {
223                 atexited=TRUE;
224                 g_atexit(bundle_util_file_write_atexit);
225                 }
226
227         if (flags&BUNDLE_UTIL_TEMPORARY_MASK) {
228 struct dir_stack **mkdirs_tail_pointer;
229 struct file_stack *file_stack;
230
231                 /* Stack current 'mkdirs' in front of the current: bundle_util_file_write_atexit_dir_stack_head
232                  * to remove them in the reverse order than created.
233                  * Removal ordering of the current 'mkdirs' stack is preserved
234                  * as it is already reversed.
235                  */
236                 for (mkdirs_tail_pointer=&mkdirs;*mkdirs_tail_pointer;mkdirs_tail_pointer=&(*mkdirs_tail_pointer)->next);
237                 *mkdirs_tail_pointer=bundle_util_file_write_atexit_dir_stack_head;
238                 bundle_util_file_write_atexit_dir_stack_head=mkdirs;
239                 mkdirs=NULL;
240
241                 /* Register also the file itself. */
242                 udpgate_new(file_stack);
243                 file_stack->pathname=g_strdup(pathname);
244                 file_stack->basename=g_strdup(basename);
245                 file_stack->next=bundle_util_file_write_atexit_file_stack_head;
246                 bundle_util_file_write_atexit_file_stack_head=file_stack;
247                 }
248         /* Currently just unsupported: */
249         g_assert(!mkdirs);
250
251         if (-1==(fd=open(pathname,O_WRONLY|O_CREAT|O_TRUNC,pathname_mode))) {
252                 if (errno!=EPERM)
253                         g_warning(_("Error opening the file \"%s\" for rewrite: %m"),pathname);
254                 return FALSE;
255                 }
256         if ((int)data_length!=write(fd,data,data_length)) {
257                 g_warning(_("Error writing the data of the file \"%s\" being overwritten: %m"),pathname);
258                 close(fd);      /* errors ignored */
259                 return FALSE;
260                 }
261         if (close(fd)) {
262                 g_warning(_("Error closing the file \"%s\" after its rewrite: %m"),pathname);
263                 return FALSE;
264                 }
265         return TRUE;
266 }