/* pdb.c * * Functions for dealing with Palm databases and such. * * Copyright (C) 1999-2001, Andrew Arensburger. * You may distribute this file under the terms of the Artistic * License, as specified in the README file. * * $Id: pdb.c,v 1.44 2001/11/20 14:32:01 arensb Exp $ */ /* XXX - The way zero-length records are handled is a bit of a kludge. They * shouldn't normally exist, with the exception of expunged records. But, * of course, a malformed conduit or something can create them. * The half-assed way they're handled here is to a) not upload zero-length * records to the Palm, b) warn the user if they're written to a file, c) * provide a utility (in the p5-Palm package) to delete zero-length * records. */ /* XXX - This is a library. It shouldn't print error messages. * Add 'int pdb_errno'; define error numbers and error messages that go * with them. * Debugging messages should go to 'FILE *pdb_logfile'. */ #include "config.h" #include #include /* For open() */ #include #include #include /* For MAXPATHLEN */ #include #include #include #if STDC_HEADERS # include /* For strncat(), memcpy() et al. */ #else /* STDC_HEADERS */ # ifndef HAVE_STRCHR # define strchr index # define strrchr rindex # endif /* HAVE_STRCHR */ # ifndef HAVE_MEMCPY # define memcpy(d,s,n) bcopy ((s), (d), (n)) # define memmove(d,s,n) bcopy ((s), (d), (n)) # endif /* HAVE_MEMCPY */ #endif /* STDC_HEADERS */ /* XXX - Is this right? Should this be in the "else" clause, above? */ #if HAVE_STRINGS_H # include /* For bzero() */ #endif /* HAVE_STRINGS_H */ #if HAVE_LIBINTL_H # include /* For i18n */ #endif /* HAVE_LIBINTL_H */ #include #include "pdb.h" /* XXX - The functions declared INLINE, below, really ought to be inline * functions. I'm not sure how to do this portably, though. */ #ifdef __GNUC__ # define INLINE __inline__ #else # define INLINE #endif /* __GNUC__ */ /* Functions for extracting values from an array of ubytes */ extern INLINE ubyte get_ubyte(const ubyte **buf); extern INLINE uword get_uword(const ubyte **buf); extern INLINE udword get_udword(const ubyte **buf); /* Functions for writing values to an array of ubytes */ extern INLINE void put_ubyte(ubyte **buf, const ubyte value); extern INLINE void put_uword(ubyte **buf, const uword value); extern INLINE void put_udword(ubyte **buf, const udword value); extern void debug_dump(FILE *outfile, const char *prefix, const ubyte *buf, const udword len); int pdb_trace = 0; /* Debugging level for PDB stuff */ #define PDB_TRACE(n) if (pdb_trace >= (n)) /* Helper functions */ static uword get_file_length(int fd); int pdb_LoadHeader(int fd, struct pdb *db); /* pdb_LoadHeader() is visible to other files */ static int pdb_LoadRecListHeader(int fd, struct pdb *db); static int pdb_LoadRsrcIndex(int fd, struct pdb *db); static int pdb_LoadRecIndex(int fd, struct pdb *db); static int pdb_LoadAppBlock(int fd, struct pdb *db); static int pdb_LoadSortBlock(int fd, struct pdb *db); static int pdb_LoadResources(int fd, struct pdb *db); static int pdb_LoadRecords(int fd, struct pdb *db); /* merge_attributes * Takes a record's flags and category, and merges them into a single byte, * with the flags in the top nybble and the category in the bottom one */ static inline ubyte merge_attributes(const ubyte flags, const ubyte category) { /* The PDB_REC_ARCHIVED flag is troublesome, since it overlaps the * category field. The idea here is that if the record was deleted, * then it doesn't have a category anymore, so the category part * gets set to 0. */ if ((flags & PDB_REC_DELETED) == 0) return (flags & 0xf0) | (category & 0x0f); else return (flags & 0xf8); } /* split_attributes * The converse of merge_attributes(). Takes the combined field attributes * and writes its contents to *flags and *category, using the same rules as * merge_attributes(), above. */ static inline void split_attributes(const ubyte attributes, ubyte *flags, ubyte *category) { if ((attributes & PDB_REC_DELETED) == 0) { *flags = (attributes & 0xf0); *category = (attributes & 0x0f); } else { *flags = (attributes & 0xf8); *category = 0; } PDB_TRACE(6) fprintf(stderr, "split 0x%02x into 0x%02x, 0x%02x\n", attributes, *flags, *category); } /* new_pdb * struct pdb constructor. */ struct pdb * new_pdb() { struct pdb *retval; /* Allocate the new pdb */ if ((retval = (struct pdb *) malloc(sizeof(struct pdb))) == NULL) /* Out of memory */ return NULL; /* Write zeros all over it, just for safety */ bzero((void *) retval, sizeof(struct pdb)); return retval; } /* pdb_FreeRecord * Free a previously-allocated 'pdb_record'. This function wouldn't really * be necessary, except that pdb_CopyRecord() returns a 'pdb_record'. */ void pdb_FreeRecord(struct pdb_record *rec) { if (rec->data != NULL) free(rec->data); free(rec); } /* pdb_FreeResource * Free a previously-allocated 'pdb_resource'. This function wouldn't * really be necessary, except that pdb_CopyResource() returns a * 'pdb_resource'. */ void pdb_FreeResource(struct pdb_resource *rsrc) { if (rsrc->data != NULL) free(rsrc->data); free(rsrc); } /* free_pdb * Cleanly free a struct pdb, and all of its subparts (destructor). */ void free_pdb(struct pdb *db) { PDB_TRACE(7) fprintf(stderr, "Inside free_pdb(%p)\n", (void *) db); if (db == NULL) /* Trivial case */ return; /* Free the array of records/resources */ if (IS_RSRC_DB(db)) { /* It's a resource database */ struct pdb_resource *rsrc; struct pdb_resource *next; PDB_TRACE(8) fprintf(stderr, "Freeing resource list\n"); /* Walk the linked list, freeing as we go along */ for (rsrc = db->rec_index.rsrc; rsrc != NULL; rsrc = next) { next = rsrc->next; /* Remember the next * element on the list. We * won't have a chance to * look it up after this * one has been free()d. */ /* Free this element */ pdb_FreeResource(rsrc); } } else { /* It's a record database */ struct pdb_record *rec; struct pdb_record *next; PDB_TRACE(8) fprintf(stderr, "Freeing record list\n"); /* Walk the linked list, freeing as we go along */ for (rec = db->rec_index.rec; rec != NULL; rec = next) { next = rec->next; /* Remember the next * element on the list. We * won't have a chance to * look it up after this * one has been free()d. */ /* Free this element */ pdb_FreeRecord(rec); } } /* Free the sort block */ if (db->sortinfo != NULL) free(db->sortinfo); /* Free the app info block */ if (db->appinfo != NULL) free(db->appinfo); free(db); } /* pdb_Read * Read a PDB from the file descriptor 'fd'. This must already have been * opened for reading and/or writing. * * Note: this function does not to any locking. The caller is responsible * for that. */ struct pdb * pdb_Read(int fd) { int err; struct pdb *retval; /* Create a new pdb to return */ if ((retval = new_pdb()) == NULL) { return NULL; } /* Find out how long the file is */ retval->file_size = get_file_length(fd); if (retval->file_size == ~0) { /* The file isn't seekable */ fprintf(stderr, _("File isn't seekable.\n")); free_pdb(retval); return NULL; } /* Load the header */ if ((err = pdb_LoadHeader(fd, retval)) < 0) { fprintf(stderr, _("Can't load header.\n")); free_pdb(retval); return NULL; } /* Load the record list header */ if ((err = pdb_LoadRecListHeader(fd, retval)) < 0) { fprintf(stderr, _("Can't load record list header for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } /* Read the record/resource list */ if (IS_RSRC_DB(retval)) { /* Read the resource index */ if ((err = pdb_LoadRsrcIndex(fd, retval)) < 0) { fprintf(stderr, _("Can't read resource index for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } } else { /* Read the record index */ if ((err = pdb_LoadRecIndex(fd, retval)) < 0) { fprintf(stderr, _("Can't read record index for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } } /* In most PDBs, there are two NUL bytes here. They are allowed by * the spec, but not mandated, and some PDBs don't have them. We'll * ignore them for now, and have the appropriate pdb_Load*() * function lseek() to the proper position. */ /* Load the AppInfo block, if any */ if ((err = pdb_LoadAppBlock(fd, retval)) < 0) { fprintf(stderr, _("Can't read AppInfo block for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } /* Load the sort block, if any */ if ((err = pdb_LoadSortBlock(fd, retval)) < 0) { fprintf(stderr, _("Can't read sort block for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } /* Load the records themselves */ if (IS_RSRC_DB(retval)) { /* Read the resources */ if ((err = pdb_LoadResources(fd, retval)) < 0) { fprintf(stderr, _("Can't read resources for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } } else { /* Read the records */ if ((err = pdb_LoadRecords(fd, retval)) < 0) { fprintf(stderr, _("Can't read records for " "\"%.*s\".\n"), PDB_DBNAMELEN, retval->name); free_pdb(retval); return NULL; } } return retval; /* Success */ } /* pdb_Write * Write 'db' to the file descriptor 'fd'. This must already have been * opened for writing. * * Note that while you can open the backup file for reading and writing, * read from it with pdb_Read() and save it with pdb_Write(), this is not * recommended: if anything should go wrong at the wrong time (e.g., the * disk fills up just as you're about to write the database back to disk), * you will lose the entire backup. * A better approach is to use a staging file: read from the backup file, * write to a temporary file, then use rename() to move the temporary file * onto the real one. Alternately, you can copy the original file to a * temporary one, then open the temporary for both reading and writing. * This might have some advantages, in that it allows you to lock a single * file for the duration of the sync. * * Note: this function does not lock the file. The caller is responsible * for that. */ int pdb_Write(const struct pdb *db, int fd) { static ubyte header_buf[PDB_HEADER_LEN]; /* Buffer for writing database header */ static ubyte rlheader_buf[PDB_RECORDLIST_LEN]; /* Buffer for writing the record list header */ static ubyte nul_buf[2]; /* Buffer for writing the two useless NULs */ ubyte *wptr; /* Pointer into buffers, for writing */ udword offset; /* The next offset we're interested in */ /* Initialize 'offset': the next variable-sized item will go after * the header, after the index header, after the index, after the * two useless NULs. */ offset = PDB_HEADER_LEN + PDB_RECORDLIST_LEN; if (IS_RSRC_DB(db)) offset += db->numrecs * PDB_RESOURCEIX_LEN; else offset += db->numrecs * PDB_RECORDIX_LEN; offset += 2; /* Those two useless NUL bytes */ /** Write the database header **/ /* Construct the header in 'header_buf' */ wptr = header_buf; memcpy(wptr, db->name, PDB_DBNAMELEN); wptr += PDB_DBNAMELEN; put_uword(&wptr, (db->attributes & ~PDB_ATTR_OPEN)); /* Clear the 'open' flag before writing */ put_uword(&wptr, db->version); put_udword(&wptr, db->ctime); put_udword(&wptr, db->mtime); put_udword(&wptr, db->baktime); put_udword(&wptr, db->modnum); if (db->appinfo == NULL) /* Write the AppInfo block, if any */ /* This database doesn't have an AppInfo block */ put_udword(&wptr, 0L); else { /* This database has an AppInfo block */ put_udword(&wptr, offset); offset += db->appinfo_len; } if (db->sortinfo == NULL) /* Write the sort block, if any */ /* This database doesn't have a sort block */ put_udword(&wptr, 0L); else { put_udword(&wptr, offset); offset += db->sortinfo_len; } put_udword(&wptr, db->type); put_udword(&wptr, db->creator); put_udword(&wptr, db->uniqueIDseed); /* Write the database header */ if (write(fd, header_buf, PDB_HEADER_LEN) != PDB_HEADER_LEN) { fprintf(stderr, _("%s: can't write database header for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); close(fd); return -1; } /** Write the record/resource index header **/ /* Construct the record list header */ wptr = rlheader_buf; put_udword(&wptr, 0L); /* nextID */ /* XXX - What is this? Should this be something * other than 0? */ put_uword(&wptr, db->numrecs); /* Write the record list header */ if (write(fd, rlheader_buf, PDB_RECORDLIST_LEN) != PDB_RECORDLIST_LEN) { fprintf(stderr, _("%s: can't write record list header for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } /* Write the record/resource index */ if (IS_RSRC_DB(db)) { /* It's a resource database */ struct pdb_resource *rsrc; /* Current resource */ /* Go through the list of resources, writing each one */ for (rsrc = db->rec_index.rsrc; rsrc != NULL; rsrc = rsrc->next) { static ubyte rsrcbuf[PDB_RESOURCEIX_LEN]; /* Buffer to hold the resource * index entry. */ /* Construct the resource index entry */ wptr = rsrcbuf; put_udword(&wptr, rsrc->type); put_uword(&wptr, rsrc->id); put_udword(&wptr, offset); /* Write the resource index entry */ if (write(fd, rsrcbuf, PDB_RESOURCEIX_LEN) != PDB_RESOURCEIX_LEN) { fprintf(stderr, _("%s: Can't write resource " "index entry for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } /* Bump 'offset' up to point to the offset of the * next variable-sized thing in the file. */ offset += rsrc->data_len; } } else { /* It's a record database */ struct pdb_record *rec; /* Current record */ /* Go through the list of records, writing each one */ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) { static ubyte recbuf[PDB_RECORDIX_LEN]; /* Buffer to hold the record index * entry. */ /* Construct the record index entry */ wptr = recbuf; /* Sanity check */ if (rec->data_len == 0) { fprintf(stderr, _("\"%.*s\" record 0x%08lx has " "length 0.\n"), PDB_DBNAMELEN, db->name, rec->id); } put_udword(&wptr, offset); put_ubyte(&wptr, merge_attributes( rec->flags, rec->category)); put_ubyte(&wptr, (char) ((rec->id >> 16) & 0xff)); put_ubyte(&wptr, (char) ((rec->id >> 8) & 0xff)); put_ubyte(&wptr, (char) (rec->id & 0xff)); /* Write the resource index entry */ if (write(fd, recbuf, PDB_RECORDIX_LEN) != PDB_RECORDIX_LEN) { fprintf(stderr, _("%s: Can't write record " "index entry for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } /* Bump 'offset' up to point to the offset of the * next variable-sized thing in the file. */ offset += rec->data_len; } } /* Write the two useless NUL bytes */ nul_buf[0] = nul_buf[1] = '\0'; if (write(fd, nul_buf, 2) != 2) { fprintf(stderr, _("%s: Can't write the two useless NULs to " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } /* Write the AppInfo block, if any */ if (db->appinfo != NULL) { if (write(fd, db->appinfo, db->appinfo_len) != db->appinfo_len) { fprintf(stderr, _("%s: Can't write AppInfo block for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } } /* Write the sort block, if any */ if (db->sortinfo != NULL) { if (write(fd, db->sortinfo, db->sortinfo_len) != db->sortinfo_len) { fprintf(stderr, _("%s: Can't write sort block for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } } /* Write the record/resource data */ if (IS_RSRC_DB(db)) { /* It's a resource database */ struct pdb_resource *rsrc; /* Go through the list of resources, writing each one's * data. */ for (rsrc = db->rec_index.rsrc; rsrc != NULL; rsrc = rsrc->next) { /* Write the data */ if (write(fd, rsrc->data, rsrc->data_len) != rsrc->data_len) { fprintf(stderr, _("%s: Can't write resource " "data for \"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } } } else { /* It's a record database */ struct pdb_record *rec; /* Go through the list of records, writing each one's data. */ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) { /* Write the data */ if (write(fd, rec->data, rec->data_len) != rec->data_len) { fprintf(stderr, _("%s: Can't write record data for " "\"%.*s\".\n"), "pdb_Write", PDB_DBNAMELEN, db->name); perror("write"); return -1; } } } return 0; /* Success */ } /* pdb_FindRecordByID * Find the record in 'db' whose ID is 'id'. Return a pointer to it. If no * such record exists, or in case of error, returns NULL. */ struct pdb_record * pdb_FindRecordByID( const struct pdb *db, const udword id) { struct pdb_record *rec; /* Walk the list of records, comparing IDs */ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) { if (rec->id == id) return rec; } return NULL; /* Couldn't find it */ } /* pdb_FindRecordByIndex * Find the 'index'th record in 'db', and return a pointer to it. If no * such record exists, or in case of error, return NULL. */ struct pdb_record * pdb_FindRecordByIndex( const struct pdb *db, /* Database to look in */ const uword index) /* Index of the record to look for */ { struct pdb_record *rec; int i; /* Walk the list, decrementing the count as we go along. If it * reaches 0, we've found the record. */ rec = db->rec_index.rec; for (i = index; i > 0; i--) { if (rec == NULL) /* Oops! We've fallen off the end of the list */ return NULL; rec = rec->next; } return rec; /* Success */ } /* pdb_NextRecord * Find the next record after 'rec' in 'db', and return a pointer to it. If * 'rec' is the last record in the list, return NULL. */ struct pdb_record * pdb_NextRecord(const struct pdb *db, /* Database to look in */ const struct pdb_record *rec) /* Return 'rec's successor */ { return rec->next; } /* pdb_DeleteRecordByID * Find the record whose unique ID is 'id' and delete it from 'db'. If the * record isn't found, well, that's okay; we wanted to delete it anyway. * Returns 0 if successful, -1 in case of error. */ int pdb_DeleteRecordByID( struct pdb *db, const udword id) { struct pdb_record *rec; /* Record we're looking at */ struct pdb_record *last; /* Last record we saw */ if (IS_RSRC_DB(db)) /* This only works with record databases */ return -1; /* Look through the list of records */ last = NULL; /* Haven't seen any records yet */ for (rec = db->rec_index.rec; rec != NULL; rec = rec->next) { /* See if the ID matches */ if (rec->id == id) { /* Found it */ /* XXX - Presumably better to use pdb_FreeRecord() */ /* Free 'rec's data */ if (rec->data != NULL) free(rec->data); /* Cut 'rec' out of the list. The first element of * the list is a special case. */ if (last == NULL) db->rec_index.rec = rec->next; else last->next = rec->next; free(rec); /* Free it */ db->numrecs--; /* Decrement record count */ return 0; /* Success */ } last = rec; /* Remember what we just saw */ } /* Couldn't find it. Oh, well. Call it a success anyway. */ return 0; } /* pdb_AppendRecord * Append a new record to 'db's record list. 'newrec' is not copied, so it * is important that the caller not free it afterwards. */ /* XXX - Ought to make sure that the ID is unique */ int pdb_AppendRecord(struct pdb *db, struct pdb_record *newrec) { struct pdb_record *rec; /* Sanity check */ if (IS_RSRC_DB(db)) /* This only works with record databases */ return -1; /* Check to see if the list is empty */ if (db->rec_index.rec == NULL) { db->rec_index.rec = newrec; newrec->next = NULL; db->numrecs++; /* Bump record counter */ return 0; /* Success */ } /* Walk the list to find its end */ for (rec = db->rec_index.rec; rec->next != NULL; rec = rec->next) ; rec->next = newrec; newrec->next = NULL; db->numrecs++; /* Bump record counter */ return 0; /* Success */ } /* pdb_AppendResource * Append a new resource to 'db's resource list. 'newrsrc' is not copied, * so it is important that the caller not free it afterwards. */ int pdb_AppendResource(struct pdb *db, struct pdb_resource *newrsrc) { struct pdb_resource *rsrc; /* Sanity check */ if (!IS_RSRC_DB(db)) /* This only works with resource databases */ return -1; /* Check to see if the list is empty */ if (db->rec_index.rsrc == NULL) { db->rec_index.rsrc = newrsrc; newrsrc->next = NULL; db->numrecs++; /* Bump resource counter */ return 0; /* Success */ } /* Walk the list to find its end */ for (rsrc = db->rec_index.rsrc; rsrc->next != NULL; rsrc = rsrc->next) ; rsrc->next = newrsrc; newrsrc->next = NULL; db->numrecs++; /* Bump resource counter */ return 0; /* Success */ } /* pdb_InsertRecord * Insert 'newrec' into 'db', just after 'prev'. If 'prev' is NULL, * 'newrec' is inserted at the beginning of the list. * Returns 0 if successful, -1 otherwise. * 'newrec' is not copied, so it is important that the caller not free it. */ int pdb_InsertRecord(struct pdb *db, /* The database to insert into */ struct pdb_record *prev, /* Insert after this record */ struct pdb_record *newrec) /* The record to insert */ { /* If 'prev' is NULL, insert at the beginning of the list */ if (prev == NULL) { newrec->next = db->rec_index.rec; db->rec_index.rec = newrec; db->numrecs++; /* Increment record count */ return 0; /* Success */ } /* XXX - This function doesn't actually check to make sure that * 'prev' is in 'db'. You could really fuck yourself over with * this. * So make it a documented requirement. */ /* The new record goes in the middle of the list. Insert it. */ newrec->next = prev->next; prev->next = newrec; db->numrecs++; /* Increment record count */ return 0; /* Success */ } /* pdb_InsertResource * Insert 'newrsrc' into 'db', just after 'prev'. If 'prev' is NULL, 'newrsrc' * is inserted at the beginning of the list. * Returns 0 if successful, -1 otherwise. * 'newrec' is not copied, so it is important that the caller not free it. */ int pdb_InsertResource(struct pdb *db, /* The database to insert into */ struct pdb_resource *prev, /* Insert after this resource */ struct pdb_resource *newrsrc) /* The resource to insert */ { /* If 'prev' is NULL, insert at the beginning of the list */ if (prev == NULL) { newrsrc->next = db->rec_index.rsrc; db->rec_index.rsrc = newrsrc; db->numrecs++; /* Increment record count */ return 0; /* Success */ } /* XXX - This function doesn't actually check to make sure that * 'prev' is in 'db'. You could really fuck yourself over with * this. * So make it a documented requirement. */ /* The new resource goes in the middle of the list. Insert it. */ newrsrc->next = prev->next; prev->next = newrsrc; db->numrecs++; /* Increment record count */ return 0; /* Success */ } /* new_Record * Create a new record from the given arguments, and return a pointer to * it. Returns NULL in case of error. * The record data is copied, so the caller needs to take care of freeing * 'data'. * * The 'attributes' and 'category' arguments are combined into one field: * if the new record is deleted, then the category is silently dropped. * Otherwise, the category occupies the bottom 4 bits of the * attributes/category field. */ struct pdb_record * new_Record(const ubyte flags, const ubyte category, const udword id, const uword len, const ubyte *data) { struct pdb_record *retval; PDB_TRACE(6) { fprintf(stderr, "new_Record: Creating new record:\n"); fprintf(stderr, "\tflags == 0x%02x\n", flags); fprintf(stderr, "\tcategory == 0x%02x\n", category); fprintf(stderr, "\tid == 0x%08lx\n", id); fprintf(stderr, "\tlen == %d\n", len); debug_dump(stderr, "NEW", data, len); } /* Allocate the record to be returned */ if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record))) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "new_Record"); return NULL; } /* Initialize the new record */ retval->next = NULL; retval->offset = 0L; retval->flags = flags; retval->category = category; retval->id = id; /* Allocate space to put the record data */ if (len == 0) { /* Special case: the record has no data (e.g., this is an * expunged record). */ retval->data_len = len; retval->data = NULL; return retval; } if ((retval->data = (ubyte *) malloc(len)) == NULL) { /* Couldn't allocate data portion of record */ fprintf(stderr, _("%s: can't allocate data.\n"), "new_Record"); free(retval); return NULL; } /* Copy the data to the new record */ retval->data_len = len; memcpy(retval->data, data, len); return retval; /* Success */ } /* new_Resource * Create a new resource from the given arguments, and return a pointer to * it. Returns NULL in case of error. * The resource data is copied, so the caller needs to take care of freeing * 'data'. */ struct pdb_resource * new_Resource(const udword type, const uword id, const uword len, const ubyte *data) { struct pdb_resource *retval; PDB_TRACE(6) { fprintf(stderr, "new_Resource: Creating new resource:\n"); fprintf(stderr, "\ttype == 0x%08lx (%c%c%c%c)\n", type, (int) ((type >> 24) & 0xff), (int) ((type >> 16) & 0xff), (int) ((type >> 8) & 0xff), (int) (type & 0xff)); fprintf(stderr, "\tid == 0x%04x\n", id); fprintf(stderr, "\tlen == %d\n", len); debug_dump(stderr, "NEW", data, len); } /* Allocate the resource to be returned */ if ((retval = (struct pdb_resource *) malloc(sizeof(struct pdb_resource))) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "new_Resource"); return NULL; } /* Initialize the new resource */ retval->next = NULL; retval->offset = 0L; retval->type = type; retval->id = id; /* Allocate space to put the resource data */ if (len == 0) { /* Special case: zero-length resource (dunno if this should * ever happen, but this way we avoid malloc(0). */ retval->data_len = len; retval->data = NULL; return retval; } if ((retval->data = (ubyte *) malloc(len)) == NULL) { /* Couldn't allocate data portion of resource */ fprintf(stderr, _("%s: can't allocate data.\n"), "new_Resource"); free(retval); return NULL; } /* Copy the data to the new resource */ retval->data_len = len; memcpy(retval->data, data, len); return retval; /* Success */ } /* pdb_CopyRecord * Make a copy of record 'rec' in database 'db' (and its data), and return * it. The new record is allocated by pdb_CopyRecord(), so the caller has * to take care of freeing it. * Returns a pointer to the new copy, or NULL in case of error. */ struct pdb_record *pdb_CopyRecord( const struct pdb *db, const struct pdb_record *rec) { struct pdb_record *retval; /* Allocate the record to be returned */ if ((retval = (struct pdb_record *) malloc(sizeof(struct pdb_record))) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_CopyRecord"); return NULL; } retval->next = NULL; /* For cleanliness */ /* Copy the old record to the new copy */ retval->offset = rec->offset; retval->flags = rec->flags; retval->category = rec->category; retval->id = rec->id; /* Allocate space for the record data itself */ if ((retval->data = (ubyte *) malloc(rec->data_len)) == NULL) { fprintf(stderr, _("%s: can't allocate record data for " "\"%.*s\".\n"), "pdb_CopyRecord", PDB_DBNAMELEN, db->name); free(retval); return NULL; } /* Copy the record data */ retval->data_len = rec->data_len; memcpy(retval->data, rec->data, retval->data_len); return retval; /* Success */ } /* pdb_CopyResource * Make a copy of resource 'rsrc' in database 'db' (and its data), and * return it. The new record is allocated by pdb_CopyResource(), so the * caller has to take care of freeing it. * Returns a pointer to the new copy, or NULL in case of error. */ struct pdb_resource *pdb_CopyResource( const struct pdb *db, const struct pdb_resource *rsrc) { struct pdb_resource *retval; /* Allocate the resource to be returned */ if ((retval = (struct pdb_resource *) malloc(sizeof(struct pdb_resource))) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_CopyResource"); return NULL; } retval->next = NULL; /* For cleanliness */ /* Copy the old resource to the new copy */ retval->type = rsrc->type; retval->id = rsrc->id; retval->offset = rsrc->offset; /* Allocate space for the record data itself */ if ((retval->data = (ubyte *) malloc(rsrc->data_len)) == NULL) { fprintf(stderr, _("%s: can't allocate resource data for " "\"%.*s\".\n"), "pdb_CopyResource", PDB_DBNAMELEN, db->name); free(retval); return NULL; } /* Copy the resource data */ retval->data_len = rsrc->data_len; memcpy(retval->data, rsrc->data, retval->data_len); return retval; /* Success */ } /*** Helper functions ***/ /* get_file_length * Return the length of a file, in bytes. In case of error, returns ~0. */ static uword get_file_length(int fd) { off_t here; off_t eof; /* Get the current position within the file */ here = lseek(fd, 0L, SEEK_CUR); if (here < 0) /* The file isn't seekable, presumably either because it * isn't open, or because it's a pipe/socket/FIFO/tty. */ return ~0; /* Go to the end of the file */ eof = lseek(fd, 0L, SEEK_END); /* And return to where we were before */ lseek(fd, here, SEEK_SET); return eof - here; } /* pdb_LoadHeader * Read the header of a pdb file, and fill in the appropriate fields in * 'db'. */ int pdb_LoadHeader(int fd, struct pdb *db) { int err; static ubyte buf[PDB_HEADER_LEN]; /* Buffer to hold the file header */ const ubyte *rptr; /* Pointer into buffers, for reading */ /* Read the header */ if ((err = read(fd, buf, PDB_HEADER_LEN)) != PDB_HEADER_LEN) { perror("pdb_LoadHeader: read"); return -1; } /* Parse the database header */ rptr = buf; memcpy(db->name, buf, PDB_DBNAMELEN); rptr += PDB_DBNAMELEN; db->attributes = get_uword(&rptr); db->version = get_uword(&rptr); db->ctime = get_udword(&rptr); db->mtime = get_udword(&rptr); db->baktime = get_udword(&rptr); db->modnum = get_udword(&rptr); db->appinfo_offset = get_udword(&rptr); db->sortinfo_offset = get_udword(&rptr); db->type = get_udword(&rptr); db->creator = get_udword(&rptr); db->uniqueIDseed = get_udword(&rptr); PDB_TRACE(5) { time_t t; fprintf(stderr, "\tname: \"%s\"\n", db->name); fprintf(stderr, "\tattributes: 0x%04x", db->attributes); if (db->attributes & PDB_ATTR_RESDB) fprintf(stderr, " RESDB"); if (db->attributes & PDB_ATTR_RO) fprintf(stderr, " RO"); if (db->attributes & PDB_ATTR_APPINFODIRTY) fprintf(stderr, " APPINFODIRTY"); if (db->attributes & PDB_ATTR_BACKUP) fprintf(stderr, " BACKUP"); if (db->attributes & PDB_ATTR_OKNEWER) fprintf(stderr, " OKNEWER"); if (db->attributes & PDB_ATTR_RESET) fprintf(stderr, " RESET"); if (db->attributes & PDB_ATTR_NOCOPY) fprintf(stderr, " NOCOPY"); if (db->attributes & PDB_ATTR_STREAM) fprintf(stderr, " STREAM"); if (db->attributes & PDB_ATTR_OPEN) fprintf(stderr, " OPEN"); fprintf(stderr, "\n"); fprintf(stderr, "\tversion: %u\n", db->version); t = db->ctime - EPOCH_1904; fprintf(stderr, "\tctime: %lu %s", db->ctime, ctime(&t)); t = db->mtime - EPOCH_1904; fprintf(stderr, "\tmtime: %lu %s", db->mtime, ctime(&t)); t = db->baktime - EPOCH_1904; fprintf(stderr, "\tbaktime: %lu %s", db->baktime, ctime(&t)); fprintf(stderr, "\tmodnum: %ld\n", db->modnum); fprintf(stderr, "\tappinfo_offset: 0x%08lx\n", db->appinfo_offset); fprintf(stderr, "\tsortinfo_offset: 0x%08lx\n", db->sortinfo_offset); fprintf(stderr, "\ttype: '%c%c%c%c' (0x%08lx)\n", (char) (db->type >> 24) & 0xff, (char) (db->type >> 16) & 0xff, (char) (db->type >> 8) & 0xff, (char) db->type & 0xff, db->type); fprintf(stderr, "\tcreator: '%c%c%c%c' (0x%08lx)\n", (char) (db->creator >> 24) & 0xff, (char) (db->creator >> 16) & 0xff, (char) (db->creator >> 8) & 0xff, (char) db->creator & 0xff, db->creator); fprintf(stderr, "\tuniqueIDseed: %ld\n", db->uniqueIDseed); } return 0; /* Success */ } /* pdb_LoadRecListHeader * Load the record list header from a pdb file, and fill in the appropriate * fields in 'db'. */ static int pdb_LoadRecListHeader(int fd, struct pdb *db) { int err; static ubyte buf[PDB_RECORDLIST_LEN]; const ubyte *rptr; /* Pointer into buffers, for reading */ /* Read the record list header */ if ((err = read(fd, buf, PDB_RECORDLIST_LEN)) != PDB_RECORDLIST_LEN) { perror("pdb_LoadRecListHeader: read2"); return -1; } /* Parse the record list */ rptr = buf; db->next_reclistID = get_udword(&rptr); db->numrecs = get_uword(&rptr); PDB_TRACE(6) { fprintf(stderr, "\tnextID: %ld\n", db->next_reclistID); fprintf(stderr, "\tlen: %u\n", db->numrecs); } return 0; } /* pdb_LoadRsrcIndex * Read the resource index from a resource database file, and fill in the * appropriate fields in 'db'. */ static int pdb_LoadRsrcIndex(int fd, struct pdb *db) { int i; int err; uword totalrsrcs; /* The real number of resources in the * database. */ totalrsrcs = db->numrecs; /* Get the number of resources in * the database. It is necessary to * remember this here because * pdb_AppendResource() increments * db->numrecs in the name of * convenience. */ if (totalrsrcs == 0) { /* There are no resources in this file */ db->rec_index.rsrc = NULL; return 0; } /* Read the resource index */ for (i = 0; i < totalrsrcs; i++) { static ubyte inbuf[PDB_RESOURCEIX_LEN]; /* Input buffer */ const ubyte *rptr; /* Pointer into buffers, for reading */ struct pdb_resource *rsrc; /* New resource entry */ /* Allocate the resource entry */ if ((rsrc = (struct pdb_resource *) malloc(sizeof(struct pdb_resource))) == NULL) return -1; /* Scribble zeros all over it, just in case */ bzero((void *) rsrc, sizeof(struct pdb_resource)); /* Read the next resource index entry */ if ((err = read(fd, inbuf, PDB_RESOURCEIX_LEN)) != PDB_RESOURCEIX_LEN) return -1; /* Parse it */ rptr = inbuf; rsrc->type = get_udword(&rptr); rsrc->id = get_uword(&rptr); rsrc->offset = get_udword(&rptr); PDB_TRACE(6) { fprintf(stderr, "\tResource %d: type '%c%c%c%c' (0x%08lx), " "id %u, offset 0x%08lx\n", i, (char) (rsrc->type >> 24) & 0xff, (char) (rsrc->type >> 16) & 0xff, (char) (rsrc->type >> 8) & 0xff, (char) rsrc->type & 0xff, rsrc->type, rsrc->id, rsrc->offset); } /* Append the new resource to the list */ pdb_AppendResource(db, rsrc); /* XXX - Error-checking */ db->numrecs = totalrsrcs; /* Kludge */ } return 0; } /* pdb_LoadRecIndex * Read the record index from a record database file, and fill in the * appropriate fields in 'db'. */ static int pdb_LoadRecIndex(int fd, struct pdb *db) { int i; int err; uword totalrecs; /* The real number of records in the * database. */ totalrecs = db->numrecs; /* Get the number of records in the * database. It is necessary to * remember this here because * pdb_AppendResource() increments * db->numrecs in the name of * convenience. */ if (totalrecs == 0) { /* There are no records in this file */ db->rec_index.rec = NULL; return 0; } /* Read the record index */ /* XXX - It would be a Good Thing to check for zero-length records * here. They've been known to appear as a result of a broken * conduit. */ for (i = 0; i < totalrecs; i++) { static ubyte inbuf[PDB_RECORDIX_LEN]; /* Input buffer */ const ubyte *rptr; /* Pointer into buffers, for reading */ struct pdb_record *rec; /* New record entry */ ubyte attributes; /* Combined flags+category field */ /* Allocate the record entry */ if ((rec = (struct pdb_record *) malloc(sizeof(struct pdb_record))) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_LoadRecIndex"); return -1; } /* Scribble zeros all over it, just in case */ bzero((void *) rec, sizeof(struct pdb_record)); /* Read the next record index entry */ if ((err = read(fd, inbuf, PDB_RECORDIX_LEN)) != PDB_RECORDIX_LEN) { fprintf(stderr, _("%s: error reading record index " "entry for \"%.*s\" (%d bytes): " "%d.\n"), "LoadRecIndex", PDB_DBNAMELEN, db->name, PDB_RECORDIX_LEN, err); perror("read"); free(rec); return -1; } /* Parse it */ rptr = inbuf; rec->offset = get_udword(&rptr); attributes = get_ubyte(&rptr); split_attributes(attributes, &(rec->flags), &(rec->category)); rec->id = ((udword) (get_ubyte(&rptr) << 16)) | ((udword) (get_ubyte(&rptr) << 8)) | ((udword) get_ubyte(&rptr)); PDB_TRACE(6) fprintf(stderr, "\tRecord %d: offset 0x%08lx, flags 0x%02x, " " category 0x%02x, ID 0x%08lx\n", i, rec->offset, rec->flags, rec->category, rec->id); /* Append the new record to the database */ pdb_AppendRecord(db, rec); /* XXX - Error-checking */ db->numrecs = totalrecs; /* Kludge */ } return 0; } /* pdb_LoadAppBlock * Read the AppInfo block from a database file, and fill in the appropriate * fields in 'db'. If the file doesn't have an AppInfo block, set it to * NULL. */ static int pdb_LoadAppBlock(int fd, struct pdb *db) { int err; localID next_off; /* Offset of the next thing in the file * after the AppInfo block */ off_t offset; /* Offset into file, for checking */ /* Check to see if there even *is* an AppInfo block */ if (db->appinfo_offset == 0L) { /* Nope */ db->appinfo_len = 0L; db->appinfo = NULL; return 0; } /* Figure out how long the AppInfo block is, by comparing its * offset to that of the next thing in the file. */ if (db->sortinfo_offset > 0L) /* There's a sort block */ next_off = db->sortinfo_offset; else if (db->numrecs > 0) { /* There's no sort block, but there are records. Get the * offset of the first one. */ if (IS_RSRC_DB(db)) next_off = db->rec_index.rsrc->offset; else next_off = db->rec_index.rec->offset; } else /* There is neither sort block nor records, so the AppInfo * block must go to the end of the file. */ next_off = db->file_size; /* Subtract the AppInfo block's offset from that of the next thing * in the file to get the AppInfo block's length. */ db->appinfo_len = next_off - db->appinfo_offset; /* This is probably paranoid, but what the hell */ if (db->appinfo_len == 0L) { /* An effective no-op */ db->appinfo = NULL; return 0; } /* Now that we know the length of the AppInfo block, allocate space * for it and read it. */ if ((db->appinfo = (ubyte *) malloc(db->appinfo_len)) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_LoadAppBlock"); return -1; } /* Just out of paranoia, make sure we're at the correct offset in * the file. Since the two NULs may or may not have appeared in the * file, the only thing that it makes sense to check is whether * we've already passed the beginning of the AppInfo block, as * given by its offset in the header. */ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */ if (offset != db->appinfo_offset) { if (offset > db->appinfo_offset) { /* Oops! We're in the wrong place */ fprintf(stderr, _("Warning: AppInfo block in \"%.*s\" " "isn't where I thought it would " "be.\n" "Expected 0x%lx, but we're at " "0x%lx.\n"), PDB_DBNAMELEN, db->name, db->appinfo_offset, (long) offset); } /* Try to recover */ offset = lseek(fd, db->appinfo_offset, SEEK_SET); /* Go to where the AppInfo block * ought to be */ if (offset < 0) { /* Something's wrong */ fprintf(stderr, _("Can't find the AppInfo block in " "\"%.*s\"!\n"), PDB_DBNAMELEN, db->name); return -1; } } /* Read the AppInfo block */ if ((err = read(fd, db->appinfo, db->appinfo_len)) != db->appinfo_len) { perror("pdb_LoadAppBlock: read"); return -1; } PDB_TRACE(6) debug_dump(stderr, "appinfo, db->appinfo_len); return 0; } /* pdb_LoadSortBlock * Read the sort block from a database file, and fill in the appropriate * fields in 'db'. If the file doesn't have a sort block, set it to NULL. * * XXX - Largely untested, since not that many databases have sort blocks. * But it's basically just a clone of pdb_LoadAppBlock(), so it should be * okay. */ static int pdb_LoadSortBlock(int fd, struct pdb *db) { int err; localID next_off; /* Offset of the next thing in the file * after the sort block */ off_t offset; /* Offset into file, for checking */ /* Check to see if there even *is* a sort block */ if (db->sortinfo_offset == 0L) { /* Nope */ db->sortinfo_len = 0L; db->sortinfo = NULL; return 0; } /* Figure out how long the sort block is, by comparing its * offset to that of the next thing in the file. */ if (db->numrecs > 0) { /* There are records. Get the offset of the first one. */ if (IS_RSRC_DB(db)) next_off = db->rec_index.rsrc->offset; else next_off = db->rec_index.rec->offset; } else /* There are no records, so the sort block must go to the * end of the file. */ next_off = db->file_size; /* Subtract the sort block's offset from that of the next thing * in the file to get the sort block's length. */ db->sortinfo_len = next_off - db->sortinfo_offset; /* This is probably paranoid, but what the hell */ if (db->sortinfo_len == 0L) { /* An effective no-op */ db->sortinfo = NULL; return 0; } /* Now that we know the length of the sort block, allocate space * for it and read it. */ if ((db->sortinfo = (ubyte *) malloc(db->sortinfo_len)) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_LoadSortBlock"); return -1; } /* Just out of paranoia, make sure we're at the correct offset in * the file. Since the two NULs may or may not have appeared in the * file, the only thing that it makes sense to check is whether * we've already passed the beginning of the sort block, as given * by its offset in the header. */ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are */ if (offset != db->sortinfo_offset) { if (offset > db->sortinfo_offset) { /* Oops! We're in the wrong place */ fprintf(stderr, _("Warning: sort block in \"%.*s\" " "isn't where I thought it would " "be.\n" "Expected 0x%lx, but we're at " "0x%lx.\n"), PDB_DBNAMELEN, db->name, db->sortinfo_offset, (long) offset); } /* Try to recover */ offset = lseek(fd, db->sortinfo_offset, SEEK_SET); /* Go to where the sort block * ought to be */ if (offset < 0) { /* Something's wrong */ fprintf(stderr, _("Can't find the sort block in " "\"%.*s\"!\n"), PDB_DBNAMELEN, db->name); return -1; } } /* Read the sort block */ if ((err = read(fd, db->sortinfo, db->sortinfo_len)) != db->sortinfo_len) { perror("pdb_LoadSortBlock: read"); return -1; } PDB_TRACE(6) debug_dump(stderr, "sortinfo, db->sortinfo_len); return 0; } /* pdb_LoadResources * Read each resource in turn from a resource database file. */ static int pdb_LoadResources(int fd, struct pdb *db) { int i; int err; struct pdb_resource *rsrc; /* This assumes that the resource list has already been created by * 'pdb_LoadRsrcIndex()'. */ for (i = 0, rsrc = db->rec_index.rsrc; i < db->numrecs; i++, rsrc = rsrc->next) { off_t offset; /* Current offset, for checking */ udword next_off; /* Offset of next resource in file */ /* Sanity check: make sure we haven't stepped off the end * of the list. */ if (rsrc == NULL) { fprintf(stderr, _("Hey! I can't find the %dth " "resource in \"%.*s\"!\n"), i, PDB_DBNAMELEN, db->name); return -1; } PDB_TRACE(5) fprintf(stderr, "Reading resource %d (type '%c%c%c%c')\n", i, (char) (rsrc->type >> 24) & 0xff, (char) (rsrc->type >> 16) & 0xff, (char) (rsrc->type >> 8) & 0xff, (char) rsrc->type & 0xff); /* Out of paranoia, make sure we're in the right place. * Since the two NULs may or may not have appeared in the * file, the only thing that it makes sense to check is * whether we've already passed the beginning of the * resource, as given by its offset in the resource index. */ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are now */ if (offset != rsrc->offset) { if (offset > rsrc->offset) { fprintf(stderr, _("Warning: resource %d in " "\"%.*s\" isn't where " "I thought it would be.\n" "Expected 0x%lx, but we're " "at 0x%lx.\n"), i, PDB_DBNAMELEN, db->name, rsrc->offset, (long) offset); } /* Try to recover */ offset = lseek(fd, rsrc->offset, SEEK_SET); /* Go to where this * resource ought to be. */ if (offset < 0) { /* Something's wrong */ fprintf(stderr, _("Can't find resource %d in " "\"%.*s\".\n"), i, PDB_DBNAMELEN, db->name); return -1; } } /* Okay, now that we're in the right place, find out what * the next thing in the file is: its offset will tell us * how much to read. * It's debatable whether 'i' or 'rsrc' should be * authoritative for determining the offset of the next * resource. I'm going to choose 'rsrc', since I think * that's more likely to be immune to fencepost errors. The * two should, however, be equivalent. In fact, it might be * a Good Thing to add a check to make sure. */ if (rsrc->next == NULL) { /* This is the last resource in the file, so it * goes to the end of the file. */ next_off = db->file_size; } else { /* This isn't the last resource. Find the next * one's offset. */ next_off = rsrc->next->offset; } /* Subtract this resource's index from that of the next * thing, to get the size of this resource. */ rsrc->data_len = next_off - rsrc->offset; /* Allocate space for this resource */ if ((rsrc->data = (ubyte *) malloc(rsrc->data_len)) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_LoadResources"); return -1; } /* Read the resource */ if ((err = read(fd, rsrc->data, rsrc->data_len)) != rsrc->data_len) { fprintf(stderr, _("Can't read resource %d in " "\"%.*s\".\n"), i, PDB_DBNAMELEN, db->name); perror("pdb_LoadResources: read"); return -1; } PDB_TRACE(6) { fprintf(stderr, "Contents of resource %d:\n", i); debug_dump(stderr, "data, rsrc->data_len); } } return 0; /* Success */ } /* pdb_LoadRecords * Read each record in turn from a record database file. */ static int pdb_LoadRecords(int fd, struct pdb *db) { int i; int err; struct pdb_record *rec; /* This assumes that the record list has already been created by * 'pdb_LoadRecIndex()'. */ for (i = 0, rec = db->rec_index.rec; i < db->numrecs; i++, rec = rec->next) { off_t offset; /* Current offset, for checking */ localID next_off; /* Offset of next resource in file */ /* Sanity check: make sure we haven't stepped off the end * of the list. */ if (rec == NULL) { fprintf(stderr, _("Hey! I can't find the %dth " "record in \"%.*s\"!\n"), i, PDB_DBNAMELEN, db->name); return -1; } PDB_TRACE(5) fprintf(stderr, "Reading record %d (id 0x%08lx)\n", i, rec->id); /* Out of paranoia, make sure we're in the right place. * Since the two NULs may or may not have appeared in the * file, the only thing that it makes sense to check is * whether we've already passed the beginning of the * record, as given by its offset in the record index. */ offset = lseek(fd, 0L, SEEK_CUR); /* Find out where we are now */ if (offset != rec->offset) { if (offset > rec->offset) { fprintf(stderr, _("Warning: record %d in " "\"%.*s\" isn't where " "I thought it would be.\n" "Expected 0x%lx, but we're " "at 0x%lx.\n"), i, PDB_DBNAMELEN, db->name, rec->offset, (long) offset); } /* Try to recover */ offset = lseek(fd, rec->offset, SEEK_SET); /* Go to where this record * ought to be. */ if (offset < 0) { /* Something's wrong */ fprintf(stderr, _("Can't find record %d in " "\"%.*s\".\n"), i, PDB_DBNAMELEN, db->name); return -1; } } /* Okay, now that we're in the right place, find out what * the next thing in the file is: its offset will tell us * how much to read. * It's debatable whether 'i' or 'rec' should be * authoritative for determining the offset of the next * resource. I'm going to choose 'rec', since I think * that's more likely to be immune from fencepost errors. * The two should, however, be equivalent. In fact, it * might be a Good Thing to add a check to make sure. */ if (rec->next == NULL) { /* This is the last record in the file, so it goes * to the end of the file. */ next_off = db->file_size; } else { /* This isn't the last record. Find the next one's * offset. */ next_off = rec->next->offset; } /* Subtract this record's index from that of the next one, * to get the size of this record. */ rec->data_len = next_off - rec->offset; /* Allocate space for this record * If there's a record with length zero, don't pass that to * malloc(). This is most likely due to a broken conduit. * XXX - The Right Thing to do would be not to read * zero-length records, but that would involve fixing the * record index. */ if (rec->data_len > 0) { if ((rec->data = (ubyte *) malloc(rec->data_len)) == NULL) { fprintf(stderr, _("%s: Out of memory.\n"), "pdb_LoadRecords"); return -1; } /* Read the record */ if ((err = read(fd, rec->data, rec->data_len)) != rec->data_len) { fprintf(stderr, _("Can't read record %d in " "\"%.*s\".\n"), i, PDB_DBNAMELEN, db->name); perror("pdb_LoadRecords: read"); return -1; } PDB_TRACE(6) { fprintf(stderr, "Contents of record %d:\n", i); debug_dump(stderr, "data, rec->data_len); } } } return 0; /* Success */ } /* This is for Emacs's benefit: * Local Variables: *** * fill-column: 75 *** * End: *** */