2 * FILE: $Header: /home/egg/src/RCS/storage.c,v 1.4 1998/12/31 22:07:56 ghn Exp $
3 * PURPOSE: Data storage functions
9 * Revision 1.4 1998/12/31 22:07:56 ghn
10 * Rev 5 code: includes multi-reg support, HTML, etc.
12 * Revision 1.3 1998/08/03 20:32:46 kelvin
13 * File byte-order independence, STORAGE_DEBUG
15 * Revision 1.2 1998/08/01 17:18:45 ghn
16 * Fixes to prevent core dumps and make "database" engine work correctly.
18 * Revision 1.1 1998/07/21 11:36:21 ghn
21 * Copyright 1998 - Greg Nelson
22 * Redistributable under the terms of the GNU Public Licence (GPL)
32 #include <sys/types.h>
40 /* Some systems (e.g. Linux) implement strdup but don't define
41 it by default in string.h. Define it explicitly here, which
42 doesn't seem to do any harm on other systems. If you encounter
43 an error on the following declaration, disable it for your
46 extern char *strdup (const char *s1);
48 /* Eventually, these might deal with making a safe copy or mounting
49 and unmounting a partition. Until such time as we actually do
50 something with these, they are no-ops. */
54 #define DATAFMT "%Y%m/%%e"
55 #define PROJSTART 901947600L
57 static char *datafmt; /* Data file format string */
59 /* Prototypes for forward functions. */
61 static int32 FileLoadPacket(FILE *fp, EggCarton *cart);
62 static int32 next_filename(char *fn, uint32 tindex, int16 eggid,
63 int16 *lastind, int16 mustexist);
64 static int32 next_poss_fn(char *fn, uint32 tindex, int16 eggid);
66 /* The seek optimisation table saves the file name, last
67 packet returned, and corresponding file of the next
68 packet (if any) in the file, for the last SEEK_OPT_MAX
69 requests. This allows LoadNextPacket to avoid searching
70 the entire database for the next packet if the request
71 matches one in the seek optimisation table.
75 #define SEEK_OPT_MAX MAX_BASKETS
83 static struct seekopt seekOpt[SEEK_OPT_MAX];
84 static int seekOptIndex = 0;
86 /* The initialization provides a way to specify a format for the
87 database file names. If the argument is null, it uses a default
88 from the environment, or it takes the default given above. This
89 string is used as an argument to strftime(3) which replaces certain
90 characters with corresponding fields of the date and time. This
91 provides an automatic way of generating a logical new filename on a
92 periodic basis. Note that you can call InitStorage as many times
93 as you wish to change the format for file names opened
96 int32 InitStorage(char *path) {
101 /* First, take this opportunity to clear the seek optimisation
102 table. If we're changing the file name format, none of the
103 items in it will be valid in any case. */
105 memset(seekOpt, 0, sizeof seekOpt);
107 /* Set the path based on the environment variable, argument,
111 if ((path = getenv("EGG_DATA")) == NULL) {
116 /* If the path contains any of our special "$" editing
117 codes, interpolate the requested text into the string.
118 Strftime "%" editing codes are left intact. */
122 while ((np = strchr(p, '$')) != NULL) {
123 char *phrargs = np + 1;
125 /* Copy portion of string prior to phrase to output. */
130 memcpy(ns + l, p, np - p);
131 ns[l + (np - p)] = 0;
134 /* Parse format phrase and possible arguments. */
136 while ((*phrargs != 0) && !isalpha(*phrargs)) {
140 fprintf(stderr, "Data format string error in:\n %s\nunterminated $ phrase.\n", path);
144 /* Copy arguments, if any, to second character of arguments
145 string for possible use by phrase interpreters in
148 pa[0] = '%'; /* Start editing phrase */
150 if (phrargs > (np + 1)) {
151 memcpy(pa + 1, np + 1, phrargs - (np + 1));
152 pa[1 + (phrargs - (np + 1))] = 0;
154 /*fprintf(stderr, "Phrase arguments = <%s>\n", pa);*/
156 /* Now interpret the specific format phrase letters.
157 The value selected by each phrase should be concatenated
158 to the output string ns. Available format phrases
163 $[0][n]E Egg number, right justified,
164 optionally zero filled in n characters
171 case 'b': /* Basket name */
172 strcat(ns, baskettable[0].name);
175 case 'e': /* Egg name */
176 strcat(ns, eggtable[0].name);
179 case 'E': /* Egg number, edited decimal number */
181 sprintf(ns + strlen(ns), pa, eggtable[0].id);
185 fprintf(stderr, "Data format string error in:\n %s\nunknown phrase $%c.\n",
190 /* Adjust pointer to resume scan following the phrase
191 in the source string. */
198 strcat(ns, p); /* Concatenate balance of string */
201 fprintf(stderr, "Cannot allocate memory for file name format string.\n");
204 /*printf("Expanded path name phrase = <%s>\n", path); exit(0);*/
211 /* Save packet is the basic save function. It does not take a
212 filename, relying on the datafmt variable created during
213 initialization to generate an appropriate filename. The
214 date for the saved data is based on the timestamp of the packet's
217 int32 SavePacket(EggCarton *cart) {
219 char *packet, datatmp[255], datafile[255], *sp;
222 pktime = cart->records[0].timestamp;
224 fprintf(stderr, "SavePacket for %lu: %s", pktime, asctime(gmtime((time_t *) &pktime)));
227 /* Generate the file name corresponding to the date of
228 the first record in the packet. Since we know the
229 date, there's no need to use next_filename. */
231 res = next_poss_fn(datafile, pktime, cart->hdr.eggid);
233 /* Open the file for appending. */
236 fp = fopen(datafile, "a");
238 /* If we can't open the file, the odds are it's because
239 the file is in a directory we've yet to create. */
243 fprintf(stderr, "SavePacket: no such file %s\n", datafile);
245 sp = strrchr(datafile, '/');
247 strncpy(datatmp, datafile, sp - datafile);
248 datatmp[sp - datafile] = 0;
249 mkdir(datatmp, 0777);
251 fprintf(stderr, "SavePacket: mkdir %s\n", datatmp);
253 sp = strchr(sp + 1, '/');
256 /* Now try re-opening the file. */
259 if ((fp = fopen(datafile, "a")) == NULL) {
260 fprintf(stderr, "SavePacket: cannot create database file %s.\n", datafile);
267 fprintf(stderr, "SavePacket: error opening %s\n", datafile);
271 packet = Packetize(cart);
272 if (!packet) return -1;
273 fwrite(packet, sizeof(char), cart->hdr.pktsize, fp);
277 cart->hdr.numrec = 0;
282 /* Open database for reading, positioning to specified location.
283 If eggid is less than zero, it will load any packet. */
284 int32 OpenDatabase(DBRec *dbp, uint32 tindex, int16 eggid) {
288 fprintf(stderr, "OpenDatabase: Egg = %d, tindex = %ld %s",
289 eggid, tindex, asctime(gmtime((time_t *) &tindex)));
293 if ((res = next_filename(dbp->fn, tindex, eggid, &(dbp->eggind), TRUE)) < 0)
297 dbp->fp = fopen(dbp->fn, "r");
298 if (dbp->fp == NULL) {
301 fprintf(stderr, "OpenDatabase -- EOF\n");
306 fprintf(stderr, "OpenDatabase -- Opened %s\n", dbp->fn);
313 int32 CloseDatabase(DBRec *dbp) {
316 fprintf(stderr, "CloseDatabase -- Closed %s\n", dbp->fn);
320 /* We preserve file name; this lets us not open the same
321 file name again unless we want to. */
325 /* Reset database allows us to open same file name again. */
326 int32 ResetDatabase(DBRec *dbp) {
329 fprintf(stderr, "ResetDatabase\n");
334 /* Load next packet fitting specified time index and egg id.
335 If eggid is less than zero, it will load any packet. */
337 static int32 LoadNextPacket(DBRec *dbp, uint32 tindex, int16 eggid, EggCarton *cart) {
340 uint32 findex = tindex, now;
342 now = getzulutime(NULL);
343 if (findex < PROJSTART) {
347 fprintf(stderr, "LoadNextPacket(%s, %lu, %d)\n", dbp->fn, tindex, eggid);
350 /* See if the start address for this request is present
351 in the seek optimisation table. If so, seek directly
352 to the address to avoid having to read through all
353 earlier packets in the database. */
356 /* Conditioning while on tindex > 0 causes seek optimisation to be
357 skipped when a request for any packet is received. */
361 i = SEEK_OPT_MAX - 1;
363 if (strcmp(seekOpt[i].filename, dbp->fn) == 0 &&
364 seekOpt[i].last_time == tindex) {
365 fseek(dbp->fp, seekOpt[i].next_packet, SEEK_SET);
367 fprintf(stderr, "LoadNextPacket; Seek optimised [%ld] to %ld for\n file %s at %lu %s",
369 seekOpt[i].next_packet,
371 seekOpt[i].last_time,
372 asctime(gmtime((time_t *) &(seekOpt[i].last_time))));
377 if (i == seekOptIndex) {
379 fprintf(stderr, "LoadNextPacket; Cannot optimise seek in file %s\n at %lu %s",
380 dbp->fn, tindex, asctime(gmtime((time_t *) &(tindex))));
382 break; /* Search wrapped table--cannot optimise */
387 res = FileLoadPacket(dbp->fp, &pktbuf);
388 if (res == ERR_EOF) {
389 /* File is finished. Close and open the next. We assume
390 that tindex and eggid have been tracking appropriately. */
393 /* Advance findex to start of next day after the one we've
396 #define SECONDS_PER_DAY (24L * 60 * 60)
397 findex = ((findex / SECONDS_PER_DAY) + 1) * SECONDS_PER_DAY;
399 fprintf(stderr, "LoadNextPacket; EOF, CloseDatabase, findex = %lu: %s",
400 findex, asctime(gmtime((time_t *) &findex)));
404 fprintf(stderr, "LoadNextPacket; EOF findex = %lu > now = %lu %s",
405 findex, now, asctime(gmtime((time_t *) &now)));
409 /* Do not reset database; we won't open same one again. */
410 if ((res = OpenDatabase(dbp, findex, eggid)) < 0) return res;
414 fprintf(stderr, "Eggid = %d Packet.eggid = %d\n", eggid, pktbuf.hdr.eggid);
416 if (eggid >= 0 && pktbuf.hdr.eggid != eggid) continue;
417 for (i = 0; i < pktbuf.hdr.numrec; i++) {
418 /*fprintf(stderr, " Rec %ld timestamp = %lu tindex = %lu\n", i, pktbuf.records[i].timestamp, tindex);
420 if (pktbuf.records[i].timestamp > tindex) {
421 memcpy(cart, &pktbuf, sizeof(EggCarton));
423 /* Save the location of the packet following this one
424 in the seek optimisation table. */
426 strcpy(seekOpt[seekOptIndex].filename, dbp->fn);
427 seekOpt[seekOptIndex].last_time = pktbuf.records[pktbuf.hdr.numrec - 1].timestamp;
428 seekOpt[seekOptIndex].next_packet = ftell(dbp->fp);
430 fprintf(stderr, "LoadNextPacket; Seek optimisation[%d] for %s\n to address %ld for time %lu %s",
432 seekOpt[seekOptIndex].filename,
433 seekOpt[seekOptIndex].next_packet,
434 seekOpt[seekOptIndex].last_time,
435 asctime(gmtime((time_t *) &(seekOpt[seekOptIndex].last_time))));
437 seekOptIndex = (seekOptIndex + 1) % SEEK_OPT_MAX;
444 /* Load next packet following a particular time index and egg id */
446 int32 LoadPacket(uint32 tindex, int16 eggid, EggCarton *cart) {
450 ResetDatabase(&db); /* Here we need to be able to open same db as
452 if ((res = OpenDatabase(&db, tindex, eggid)) < 0) return res;
453 if ((res = LoadNextPacket(&db, tindex, eggid, cart)) < 0) return res;
454 return CloseDatabase(&db);
457 static int32 FileLoadPacket(FILE *fp, EggCarton *cart) {
462 if (feof(fp)) return ERR_EOF;
464 if (fread(&(cart->hdr.type), sizeof(uint16), 1, fp) != 1) {
465 if (feof(fp)) return ERR_EOF; else return ERR_CNREAD;
467 if (fread(&(cart->hdr.pktsize), sizeof(uint16), 1, fp) != 1) {
468 if (feof(fp)) return ERR_EOF; else return ERR_CNREAD;
471 /* Since the file packet is in network byte order, we need to
472 be sure the length field is in host order before using
473 it to allocate and read the balance of the packet from the
474 file. Once that's done, Unpacketize will take responsibility
475 for getting all the packet fields into host order. */
476 pksize = ntohs(cart->hdr.pktsize);
478 rbuf = (char *)malloc(pksize);
479 if (!rbuf) return ERR_NOMEM;
481 memcpy(rbuf, &(cart->hdr), 2*sizeof(uint16));
482 if (fread(rbuf+4, pksize - 4, 1, fp) != 1) return ERR_CNREAD;
484 res = Unpacketize(cart, rbuf);
490 static int32 next_filename(char *fn, uint32 tindex, int16 eggid,
491 int16 *lastind, int16 mustexist) {
498 fprintf(stderr, "next_filename: egg = %d, mustexist = %d, tindex = %lu %s",
499 eggid, mustexist, tindex, asctime(gmtime((time_t *) &tindex)));
501 if (eggid < 0 && !mustexist) {
503 fprintf(stderr, "next_filename: egg ID out of range.\n");
508 /* Can't go back before project began. */
509 now = getzulutime(NULL);
510 if (tindex < PROJSTART) {
516 /* Search possible file names until one is found. We jump by days,
517 assuming files don't have a greater granularity than that. In
518 the existence-testing process, we are checking to make sure the
519 file name is different each time. */
521 if (lastind == NULL) eggid = eggtable[0].id;
523 if (eggid < 0) eggid = eggtable[(*lastind)++].id;
524 if (*lastind == numeggs) *lastind = 0;
529 res = next_poss_fn(fn, findex, eggid);
531 if (!strcmp(fn, ldf)) continue;
533 /* Check for file. If stat fails, reason is ENOENT, isn't it? */
534 if (stat(fn, &statbuf) >= 0) {
536 fprintf(stderr, "next_filename: Found file %s\n", fn);
542 fprintf(stderr, "next_filename: File %s does not exist.\n", fn);
546 } while (findex < now+86400L);
549 fprintf(stderr, "next_filename: End of file.\n");
554 /* tindex and eggid must be defined here */
555 static int32 next_poss_fn(char *fn, uint32 tindex, int16 eggid) {
556 char datatmp[255], *sp;
560 tm = gmtime((time_t *) &tindex);
561 strftime(datatmp, 255, datafmt, tm);
563 fprintf(stderr, "next_poss_fn: egg = %d, tindex = %lu %s",
564 eggid, tindex, asctime(tm));
567 /* If an %e phrase remains after processing by strftime
568 (it should be specified as %%e in the original
569 DATAFMT specification), interpolate the egg number
572 if ((sp = strchr(datatmp, '%')) != NULL) {
575 eggind < numeggs && eggtable[eggind].id != eggid;
577 if (eggind == numeggs) {
579 fprintf(stderr, "next_poss_fn: Egg number out of range.\n");
585 strcat(fn, eggtable[eggind].name);
588 fprintf(stderr, "Bad file format expr: %%%c\n", sp[1]);
596 fprintf(stderr, "next_poss_fn: File name = %s\n", fn);