/* Database file handling routines. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #ifndef CONVERT_DB # include "modules.h" #endif #include "fileutil.h" #include /*************************************************************************/ #ifndef CONVERT_DB static Module *module; #endif /*************************************************************************/ /*************************************************************************/ /* Return the version number of the file. Return -1 if there is no version * number or the number doesn't make sense (i.e. less than 1). */ int32 get_file_version(dbFILE *f) { FILE *fp = f->fp; int version = fgetc(fp)<<24 | fgetc(fp)<<16 | fgetc(fp)<<8 | fgetc(fp); if (ferror(fp)) { #ifndef CONVERT_DB module_log_perror("Error reading version number on %s", f->filename); #endif return -1; } else if (feof(fp)) { #ifndef CONVERT_DB module_log("Error reading version number on %s: End of file detected", f->filename); #endif return -1; } else if (version < 1) { #ifndef CONVERT_DB module_log("Invalid version number (%d) on %s", version, f->filename); #endif return -1; } return version; } /*************************************************************************/ /* Write the version number to the file. Return 0 on success, -1 on * failure. */ int write_file_version(dbFILE *f, int32 filever) { FILE *fp = f->fp; if ( fputc(filever>>24 & 0xFF, fp) < 0 || fputc(filever>>16 & 0xFF, fp) < 0 || fputc(filever>> 8 & 0xFF, fp) < 0 || fputc(filever & 0xFF, fp) < 0 ) { #ifndef CONVERT_DB module_log_perror("Error writing version number on %s", f->filename); #endif return -1; } return 0; } /*************************************************************************/ /*************************************************************************/ static dbFILE *open_db_read(const char *filename) { dbFILE *f; FILE *fp; f = smalloc(sizeof(*f)); *f->tempname = 0; strscpy(f->filename, filename, sizeof(f->filename)); f->mode = 'r'; fp = fopen(f->filename, "rb"); if (!fp) { int errno_save = errno; #ifndef CONVERT_DB if (errno != ENOENT) module_log_perror("Can't read database file %s", f->filename); #endif free(f); errno = errno_save; return NULL; } f->fp = fp; return f; } /*************************************************************************/ static dbFILE *open_db_write(const char *filename, int32 filever) { dbFILE *f; int fd; f = smalloc(sizeof(*f)); *f->tempname = 0; strscpy(f->filename, filename, sizeof(f->filename)); filename = f->filename; f->mode = 'w'; snprintf(f->tempname, sizeof(f->tempname), "%s.new", filename); if (!*f->tempname || strcmp(f->tempname, filename) == 0) { #ifndef CONVERT_DB module_log("Opening database file %s for write: Filename too long", filename); #endif free(f); errno = ENAMETOOLONG; return NULL; } remove(f->tempname); /* Use open() to avoid people sneaking a new file in under us */ fd = open(f->tempname, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd >= 0) f->fp = fdopen(fd, "wb"); if (!f->fp || write_file_version(f, filever) < 0) { int errno_save = errno; #ifndef CONVERT_DB static int walloped = 0; if (!walloped) { walloped++; wallops(NULL, "Can't create temporary database file %s", f->tempname); } errno = errno_save; module_log_perror("Can't create temporary database file %s", f->tempname); #endif if (f->fp) fclose(f->fp); remove(f->tempname); errno = errno_save; return NULL; } return f; } /*************************************************************************/ /* Open a database file for reading (*mode == 'r') or writing (*mode == 'w'). * Return the stream pointer, or NULL on error. When opening for write, the * file actually opened is a temporary file, which will be renamed to the * original file on close. * * `version' is only used when opening a file for writing, and indicates the * version number to write to the file. */ dbFILE *open_db(const char *filename, const char *mode, int32 version) { if (*mode == 'r') { return open_db_read(filename); } else if (*mode == 'w') { return open_db_write(filename, version); } else { errno = EINVAL; return NULL; } } /*************************************************************************/ /* Restore the database file to its condition before open_db(). This is * identical to close_db() for files open for reading; however, for files * open for writing, we discard the new temporary file instead of renaming * it over the old file. */ void restore_db(dbFILE *f) { int errno_orig = errno; if (f->fp) fclose(f->fp); if (f->mode == 'w' && *f->tempname) remove(f->tempname); free(f); errno = errno_orig; } /*************************************************************************/ /* Close a database file. If the file was opened for write, moves the new * file over the old one, and logs/wallops an error message if the rename() * fails. */ int close_db(dbFILE *f) { int res; if (!f->fp) { errno = EINVAL; return -1; } res = fclose(f->fp); f->fp = NULL; if (res != 0) return -1; if (f->mode=='w' && *f->tempname && strcmp(f->tempname,f->filename)!=0) { if (rename(f->tempname, f->filename) < 0) { #ifndef CONVERT_DB int errno_save = errno; wallops(NULL, "Unable to move new data to database file %s;" " new data NOT saved.", f->filename); errno = errno_save; module_log_perror("Unable to move new data to database file %s;" " new data NOT saved.", f->filename); #endif remove(f->tempname); } } free(f); return 0; } /*************************************************************************/ /*************************************************************************/ /* Read and write 2- and 4-byte quantities, pointers, and strings. All * multibyte values are stored in big-endian order (most significant byte * first). A pointer is stored as a byte, either 0 if NULL or 1 if not, * and read pointers are returned as either (void *)0 or (void *)1. A * string is stored with a 2-byte unsigned length (including the trailing * \0) first; a length of 0 indicates that the string pointer is NULL. * Written strings are truncated silently at 65534 bytes, and are always * null-terminated. * * All routines return -1 on error, 0 otherwise. */ /*************************************************************************/ int read_int8(int8 *ret, dbFILE *f) { int c = fgetc(f->fp); if (c == EOF) return -1; *ret = c; return 0; } /* Alternative version of read_int8() to avoid GCC's pointer signedness * warnings when reading into an unsigned variable: */ int read_uint8(uint8 *ret, dbFILE *f) { return read_int8((int8 *)ret, f); } int write_int8(int8 val, dbFILE *f) { if (fputc(val, f->fp) == EOF) return -1; return 0; } /*************************************************************************/ /* These are inline to help out {read,write}_string. */ inline int read_int16(int16 *ret, dbFILE *f) { int c1, c2; c1 = fgetc(f->fp); c2 = fgetc(f->fp); if (c2 == EOF) return -1; *ret = c1<<8 | c2; return 0; } inline int read_uint16(uint16 *ret, dbFILE *f) { return read_int16((int16 *)ret, f); } inline int write_int16(int16 val, dbFILE *f) { fputc((val>>8), f->fp); if (fputc(val, f->fp) == EOF) return -1; return 0; } /*************************************************************************/ int read_int32(int32 *ret, dbFILE *f) { int c1, c2, c3, c4; c1 = fgetc(f->fp); c2 = fgetc(f->fp); c3 = fgetc(f->fp); c4 = fgetc(f->fp); if (c4 == EOF) return -1; *ret = c1<<24 | c2<<16 | c3<<8 | c4; return 0; } int read_uint32(uint32 *ret, dbFILE *f) { return read_int32((int32 *)ret, f); } int write_int32(int32 val, dbFILE *f) { fputc((val>>24), f->fp); fputc((val>>16), f->fp); fputc((val>> 8), f->fp); if (fputc((val & 0xFF), f->fp) == EOF) return -1; return 0; } /*************************************************************************/ int read_time(time_t *ret, dbFILE *f) { int32 high, low; if (read_int32(&high, f) < 0 || read_int32(&low, f) < 0) return -1; #if SIZEOF_TIME_T > 4 *ret = (time_t)high << 32 | (time_t)low; #else *ret = low; #endif return 0; } int write_time(time_t val, dbFILE *f) { #if SIZEOF_TIME_T > 4 if (write_int32(val>>32, f) < 0 || write_int32(val & (time_t)0xFFFFFFFF, f) < 0) #else if (write_int32(0, f) < 0 || write_int32(val, f) < 0) #endif return -1; return 0; } /*************************************************************************/ int read_ptr(void **ret, dbFILE *f) { int c; c = fgetc(f->fp); if (c == EOF) return -1; *ret = (c ? (void *)1 : (void *)0); return 0; } int write_ptr(const void *ptr, dbFILE *f) { if (fputc(ptr ? 1 : 0, f->fp) == EOF) return -1; return 0; } /*************************************************************************/ int read_string(char **ret, dbFILE *f) { char *s; uint16 len; if (read_uint16(&len, f) < 0) return -1; if (len == 0) { *ret = NULL; return 0; } s = smalloc(len); if (len != fread(s, 1, len, f->fp)) { free(s); return -1; } *ret = s; return 0; } int write_string(const char *s, dbFILE *f) { uint32 len; if (!s) return write_int16(0, f); len = strlen(s); if (len > 65534) len = 65534; write_int16((uint16)(len+1), f); fwrite(s, 1, len, f->fp); if (fputc(0, f->fp) == EOF) return -1; return 0; } /*************************************************************************/ /*************************************************************************/ #ifndef CONVERT_DB void init_fileutil(Module *my_module) { module = my_module; } #endif /*************************************************************************/