/* $Id$ * UDP Gateway single-file bundle utility functions * Copyright (C) 2004 Jan Kratochvil * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; exactly version 2 of June 1991 is required * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "bundle-util.h" /* self */ #include "bundle.h" #include "main.h" static G_CONST_RETURN guint8 *bundle_util_file_retrieve(guint32 *data_length_return,const gchar *basename) { const guint8 *data; g_return_val_if_fail(data_length_return!=NULL,NULL); g_return_val_if_fail(basename!=NULL,NULL); if (optarg_verbose) g_message(_("Retrieving internally stored: %s"),basename); data=g_hash_table_lookup(bundle_hash_new(),basename); g_return_val_if_fail(data!=NULL,NULL); *data_length_return=GUINT32_FROM_BE(*(guint32 *)data); data+=sizeof(*data_length_return); return data; } void bundle_util_file_insert(const void *data,gsize data_length,const gchar *basename) { guint8 *buffer; g_return_if_fail(data!=NULL); g_return_if_fail(basename!=NULL); if (optarg_verbose) g_message(_("Internally storing autogenerated: %s"),basename); udpgate_newn(buffer,sizeof(guint32)+data_length); *((guint32 *)buffer)=GUINT32_TO_BE(data_length); g_assert(data_length==GUINT32_FROM_BE(*(guint32 *)buffer)); memcpy(buffer+sizeof(data_length),data,data_length); /* FIXME: Missing duplicity check! */ /* FIXME: Missing g_free() on the element removal! */ g_hash_table_insert(bundle_hash_new(),g_strdup(basename),buffer); } /* Returns TRUE if it safe to overwrite/unlink the file. */ static gboolean bundle_util_file_backup_conditional(const gchar *pathname,const gchar *basename) { const guint8 *data; guint32 data_length; int fd; guint8 *data_found; int got; char strftime_buffer[LINE_MAX]; const gchar *destination; time_t time_current; g_return_val_if_fail(pathname!=NULL,FALSE); g_return_val_if_fail(basename!=NULL,FALSE); if (!(data=bundle_util_file_retrieve(&data_length,basename))) return FALSE; if (-1==(fd=open(pathname,O_RDONLY))) { if (errno==ENOENT) return TRUE; else { g_warning(_("Error checking file modifications of \"%s\": %m"),pathname); return FALSE; } } /* WARNING: 'data_found' allocated in this block! */ data_found=g_malloc(data_length+1); if (-1==(got=read(fd,data_found,data_length+1))) g_warning(_("Error reading during the check of file modifications of \"%s\": %m"),pathname); if (close(fd)) g_warning(_("Error closing the file \"%s\" during the check of its modifications: %m"),pathname); /* memcmp(3) requires 'data_found'! */ if (got==(int)data_length && !memcmp(data_found,data,data_length)) { g_free(data_found); return TRUE; } g_free(data_found); time_current=time(NULL); /* It is segfault to gmtime(NULL). */ if (!strftime(strftime_buffer,sizeof(strftime_buffer),"GMT%FT%T",gmtime(&time_current))) { g_warning("strftime(3): %m"); /* shouldn't happen */ return FALSE; } destination=udpgate_printf_alloca("%s-%s-%d",pathname,strftime_buffer,(int)getpid()); if (rename(pathname,destination)) { g_warning(_("Error renaming your modified file \"%s\" to the backup \"%s\", giving up: %m"), pathname,destination); return FALSE; } return TRUE; } gboolean bundle_util_file_remove(const gchar *pathname,const gchar *basename) { g_return_val_if_fail(pathname!=NULL,FALSE); g_return_val_if_fail(basename!=NULL,FALSE); if (!bundle_util_file_backup_conditional(pathname,basename)) return FALSE; if (unlink(pathname) && errno!=ENOENT) { g_warning(_("Error removing the file \"%s\": %m"),pathname); return FALSE; } return TRUE; } struct dir_stack { struct dir_stack *next; gchar *dirname; /* in fact 'pathname' */ }; /* Always creates up to 'dirname(pathname)', never 'pathname' itself! */ static struct dir_stack *pathname_mkdirs(const gchar *pathname,mode_t mode) { gchar *dirname; struct dir_stack *r; g_return_val_if_fail(pathname!=NULL,NULL); dirname=g_path_get_dirname(pathname); if (!strcmp(dirname,pathname)) { g_free(dirname); dirname=NULL; } if (!dirname) return NULL; r=pathname_mkdirs(dirname,mode); if (mkdir(dirname,mode)) g_free(dirname); else { struct dir_stack *dir_stack; udpgate_new(dir_stack); dir_stack->dirname=dirname; dir_stack->next=r; r=dir_stack; } return r; } static struct dir_stack *bundle_util_file_write_atexit_dir_stack_head; struct file_stack { struct file_stack *next; gchar *pathname; gchar *basename; }; static struct file_stack *bundle_util_file_write_atexit_file_stack_head; static void bundle_util_file_write_atexit(void) { struct file_stack *file_stack; struct dir_stack *dir_stack; /* Always remove files first before their directories! */ while ((file_stack=bundle_util_file_write_atexit_file_stack_head)) { /* Errors already reported: */ bundle_util_file_remove(file_stack->pathname,file_stack->basename); g_free(file_stack->pathname); g_free(file_stack->basename); bundle_util_file_write_atexit_file_stack_head=file_stack->next; g_free(file_stack); } while ((dir_stack=bundle_util_file_write_atexit_dir_stack_head)) { if (rmdir(dir_stack->dirname)) g_warning(_("Error cleaning up created temporary directory: %s"),dir_stack->dirname); g_free(dir_stack->dirname); bundle_util_file_write_atexit_dir_stack_head=dir_stack->next; g_free(dir_stack); } } gboolean bundle_util_file_write(const gchar *pathname,const gchar *basename,mode_t pathname_mode, enum bundle_util_flags flags) { const guint8 *data; guint32 data_length; int fd; struct dir_stack *mkdirs=NULL; static gboolean atexited=FALSE; g_return_val_if_fail(pathname!=NULL,FALSE); g_return_val_if_fail(basename!=NULL,FALSE); g_return_val_if_fail(flags&(BUNDLE_UTIL_BACKUP_MASK|BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK),FALSE); /* Currently just unsupported: */ g_return_val_if_fail((flags&(BUNDLE_UTIL_MKDIRS_MASK|BUNDLE_UTIL_TEMPORARY_MASK))!=BUNDLE_UTIL_MKDIRS_MASK,FALSE); if (!(data=bundle_util_file_retrieve(&data_length,basename))) return FALSE; if ((flags&BUNDLE_UTIL_BACKUP_MASK) && !bundle_util_file_backup_conditional(pathname,basename)) return FALSE; if (flags&BUNDLE_UTIL_MKDIRS_MASK) { mode_t dir_mode=pathname_mode; dir_mode|=(pathname_mode&0444)>>2; /* mode|=('r'->'x') */ mkdirs=pathname_mkdirs(pathname,dir_mode); } if (!atexited) { atexited=TRUE; g_atexit(bundle_util_file_write_atexit); } if (flags&BUNDLE_UTIL_TEMPORARY_MASK) { struct dir_stack **mkdirs_tail_pointer; /* Stack current 'mkdirs' in front of the current: bundle_util_file_write_atexit_dir_stack_head * to remove them in the reverse order than created. * Removal ordering of the current 'mkdirs' stack is preserved * as it is already reversed. */ for (mkdirs_tail_pointer=&mkdirs;*mkdirs_tail_pointer;mkdirs_tail_pointer=&(*mkdirs_tail_pointer)->next); *mkdirs_tail_pointer=bundle_util_file_write_atexit_dir_stack_head; bundle_util_file_write_atexit_dir_stack_head=mkdirs; mkdirs=NULL; } /* Currently just unsupported: */ g_assert(!mkdirs); if (-1==(fd=open(pathname,O_WRONLY|O_CREAT|O_TRUNC,pathname_mode))) { g_warning(_("Error opening the file \"%s\" for rewrite: %m"),pathname); return FALSE; } /* Register the file only if it got already created. * Do not atempt to remove files which are not ours! */ if (flags&BUNDLE_UTIL_TEMPORARY_MASK) { struct file_stack *file_stack; /* Register also the file itself. */ udpgate_new(file_stack); file_stack->pathname=g_strdup(pathname); file_stack->basename=g_strdup(basename); file_stack->next=bundle_util_file_write_atexit_file_stack_head; bundle_util_file_write_atexit_file_stack_head=file_stack; } if ((int)data_length!=write(fd,data,data_length)) { g_warning(_("Error writing the data of the file \"%s\" being overwritten: %m"),pathname); close(fd); /* errors ignored */ return FALSE; } if (close(fd)) { g_warning(_("Error closing the file \"%s\" after its rewrite: %m"),pathname); return FALSE; } return TRUE; }