File: storage.c

package info (click to toggle)
gcpegg 5.1-14
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 324 kB
  • ctags: 439
  • sloc: ansic: 3,726; makefile: 97; sh: 33; csh: 21
file content (608 lines) | stat: -rw-r--r-- 17,515 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
/* PROGRAM:	eggsh
 * FILE:	$Header: /home/egg/src/RCS/storage.c,v 1.4 1998/12/31 22:07:56 ghn Exp $
 * PURPOSE:	Data storage functions
 * AUTHOR:	Greg Nelson
 * DATE:	98-05-09
 *
 * REVISED:
 * $Log: storage.c,v $
 * Revision 1.4  1998/12/31 22:07:56  ghn
 * Rev 5 code: includes multi-reg support, HTML, etc.
 *
 * Revision 1.3  1998/08/03 20:32:46  kelvin
 * File byte-order independence, STORAGE_DEBUG
 *
 * Revision 1.2  1998/08/01  17:18:45  ghn
 * Fixes to prevent core dumps and make "database" engine work correctly.
 *
 * Revision 1.1  1998/07/21 11:36:21  ghn
 * Initial revision
 *
 * Copyright 1998 - Greg Nelson
 * Redistributable under the terms of the GNU Public Licence (GPL)
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "global.h"
#include "genlib.h"
#include "storage.h"
#include "errnos.h"

/* Some systems (e.g. Linux) implement strdup but don't define
   it by default in string.h.  Define it explicitly here, which
   doesn't seem to do any harm on other systems.  If you encounter
   an error on the following declaration, disable it for your
   platform. */

/*extern char *strdup (const char *s1);*/

/* Eventually, these might deal with making a safe copy or mounting
   and unmounting a partition. Until such time as we actually do
   something with these, they are no-ops. */
#define Protect(x)
#define Unprotect(x)

#define DATAFMT         "%Y%m/%%e"
#define PROJSTART	901947600L

static char *datafmt;		      /* Data file format string */

/*  Prototypes for forward functions.  */

static int32 FileLoadPacket(FILE *fp, EggCarton *cart);
static int32 next_filename(char *fn, uint32 tindex, int16 eggid,
			   int16 *lastind, int16 mustexist);
static int32 next_poss_fn(char *fn, uint32 tindex, int16 eggid);

/* The seek optimisation table saves the file name, last
   packet returned, and corresponding file of the next
   packet (if any) in the file, for the last SEEK_OPT_MAX
   requests.  This allows LoadNextPacket to avoid searching
   the entire database for the next packet if the request
   matches one in the seek optimisation table.

*/

#define SEEK_OPT_MAX MAX_BASKETS

struct seekopt {
    char filename[256];
    uint32 last_time;
    int32 next_packet;
};

static struct seekopt seekOpt[SEEK_OPT_MAX];
static int seekOptIndex = 0;

/* The initialization provides a way to specify a format for the
   database file names.  If the argument is null, it uses a default
   from the environment, or it takes the default given above.  This
   string is used as an argument to strftime(3) which replaces certain
   characters with corresponding fields of the date and time.  This
   provides an automatic way of generating a logical new filename on a
   periodic basis.  Note that you can call InitStorage as many times
   as you wish to change the format for file names opened
   subsequently. */

int32 InitStorage(char *path) {
  char *p, *np;
  char ns[256], pa[12];
  int changed = 0;

  /* First, take this opportunity to clear the seek optimisation
     table.  If we're changing the file name format, none of the
     items in it will be valid in any case. */

  memset(seekOpt, 0, sizeof seekOpt);

  /* Set the path based on the environment variable, argument,
     or default. */

  if (path == NULL) {
    if ((path = getenv("EGG_DATA")) == NULL) {
      path = DATAFMT;
    }
  }

  /* If the path contains any of our special "$" editing
     codes, interpolate the requested text into the string.
     Strftime "%" editing codes are left intact. */

  ns[0] = 0;
  p = path;
  while ((np = strchr(p, '$')) != NULL) {
    char *phrargs = np + 1;

    /* Copy portion of string prior to phrase to output. */

    if (np > p) {
	int l = strlen(ns);

	memcpy(ns + l, p, np - p);
	ns[l + (np - p)] = 0;
    }

    /* Parse format phrase and possible arguments. */

    while ((*phrargs != 0) && !isalpha(*phrargs)) {
	phrargs++;
    }
    if (*phrargs == 0) {
        fprintf(stderr, "Data format string error in:\n    %s\nunterminated $ phrase.\n", path);
	exit(-1);
    }

    /* Copy arguments, if any, to second character of arguments
       string for possible use by phrase interpreters in
       sprintf. */

    pa[0] = '%';                      /* Start editing phrase */
    pa[1] = 0;
    if (phrargs > (np + 1)) {
	memcpy(pa + 1, np + 1, phrargs - (np + 1));
	pa[1 + (phrargs - (np + 1))] = 0;
    }
/*fprintf(stderr, "Phrase arguments = <%s>\n", pa);*/

    /* Now interpret the specific format phrase letters.
       The value selected by each phrase should be concatenated
       to the output string ns.  Available format phrases
       are:

	   $b	       Basket name
	   $e	       Egg name
	   $[0][n]E    Egg number, right justified,
		       optionally zero filled in n characters

    */


    switch (*phrargs) {

        case 'b':                     /* Basket name */
	    strcat(ns, baskettable[0].name);
	    break;

        case 'e':                     /* Egg name */
	    strcat(ns, eggtable[0].name);
	    break;

        case 'E':                     /* Egg number, edited decimal number */
            strcat(pa, "d");
	    sprintf(ns + strlen(ns), pa, eggtable[0].id);
	    break;

	default:
            fprintf(stderr, "Data format string error in:\n    %s\nunknown phrase $%c.\n",
		    path, *phrargs);
	    exit(-1);
    }

    /* Adjust pointer to resume scan following the phrase
       in the source string. */

    p = phrargs + 1;
    changed++;
  }

  if (changed) {
    strcat(ns, p);		      /* Concatenate balance of string */
    path = strdup(ns);
    if (path == NULL) {
        fprintf(stderr, "Cannot allocate memory for file name format string.\n");
	exit(-1);
    }
/*printf("Expanded path name phrase = <%s>\n", path); exit(0);*/
  }

  datafmt = path;
  return 0;
}

/* Save packet is the basic save function.  It does not take a
   filename, relying on the datafmt variable created during
   initialization to generate an appropriate filename.	The
   date for the saved data is based on the timestamp of the packet's
   first record. */

int32 SavePacket(EggCarton *cart) {
  FILE		*fp;
  char		*packet, datatmp[255], datafile[255], *sp;
  uint32 	pktime, res;

  pktime = cart->records[0].timestamp;
#ifdef STORAGE_DEBUG
  time_t pktime_val = pktime;
  fprintf(stderr, "SavePacket for %u: %s", pktime, asctime(gmtime(&pktime_val)));
#endif

  /* Generate the file name corresponding to the date of
     the first record in the packet.  Since we know the
     date, there's no need to use next_filename. */

  res = next_poss_fn(datafile, pktime, cart->hdr.eggid);

  /* Open the file for appending. */

  Unprotect(datafile);
  fp = fopen(datafile, "a");

  /* If we can't open the file, the odds are it's because
     the file is in a directory we've yet to create. */

  if (fp == NULL) {
#ifdef STORAGE_DEBUG
    fprintf(stderr, "SavePacket: no such file %s\n", datafile);
#endif
    sp = strrchr(datafile, '/');
    while (sp) {
      strncpy(datatmp, datafile, sp - datafile);
      datatmp[sp - datafile] = 0;
      mkdir(datatmp, 0777);
#ifdef STORAGE_DEBUG
      fprintf(stderr, "SavePacket: mkdir %s\n", datatmp);
#endif
      sp = strchr(sp + 1, '/');
    }

    /* Now try re-opening the file.  */

    Unprotect(datafile);
    if ((fp = fopen(datafile, "a")) == NULL) {
      fprintf(stderr, "SavePacket: cannot create database file %s.\n", datafile);
      exit(-1);
    }
  }
  
  if (!fp) {
#ifdef STORAGE_DEBUG
    fprintf(stderr, "SavePacket: error opening %s\n", datafile);
#endif
    return -2;
  }
  packet = Packetize(cart);
  if (!packet) return -1;
  fwrite(packet, sizeof(char), cart->hdr.pktsize, fp);
  free(packet);
  fclose(fp);
  Protect(datafile);
  cart->hdr.numrec = 0;

  return 1;
}

/* Open database for reading, positioning to specified location.
   If eggid is less than zero, it will load any packet. */
int32 OpenDatabase(DBRec *dbp, uint32 tindex, int16 eggid) {
  int32 	res;

#ifdef STORAGE_DEBUG
  time_t tindex_val = tindex;
  fprintf(stderr, "OpenDatabase: Egg = %d, tindex = %u %s",
    eggid, tindex, asctime(gmtime(&tindex_val)));
#endif
  dbp->eggind = 0;
  dbp->fp = NULL;
  if ((res = next_filename(dbp->fn, tindex, eggid, &(dbp->eggind), TRUE)) < 0)
    return res;
    
  Unprotect(dbp->fn);
  dbp->fp = fopen(dbp->fn, "r");
  if (dbp->fp == NULL) {
    Protect(dbp->fn);
#ifdef STORAGE_DEBUG
  fprintf(stderr, "OpenDatabase -- EOF\n");
#endif
    return ERR_EOF;
  } else {
#ifdef STORAGE_DEBUG
  fprintf(stderr, "OpenDatabase -- Opened %s\n", dbp->fn);
#endif
    return ERR_NONE;
  }
}

/* Close database */
int32 CloseDatabase(DBRec *dbp) {
  fclose(dbp->fp);
#ifdef STORAGE_DEBUG
  fprintf(stderr, "CloseDatabase -- Closed %s\n", dbp->fn);
#endif
  Protect(dbp->fn);
  dbp->fp = NULL;
  /* We preserve file name; this lets us not open the same
     file name again unless we want to. */
  return ERR_NONE;
}

/* Reset database allows us to open same file name again. */
int32 ResetDatabase(DBRec *dbp) {
  *(dbp->fn) = 0;
#ifdef STORAGE_DEBUG
  fprintf(stderr, "ResetDatabase\n");
#endif
  return ERR_NONE;
}

/* Load next packet fitting specified time index and egg id.
   If eggid is less than zero, it will load any packet. */

static int32 LoadNextPacket(DBRec *dbp, uint32 tindex, int16 eggid, EggCarton *cart) {
  EggCarton	pktbuf;
  int32 	res, i;
  uint32	findex = tindex, now;

  now = getzulutime(NULL);
  if (findex < PROJSTART) {
    findex = PROJSTART;
  }
#ifdef STORAGE_DEBUG
  fprintf(stderr, "LoadNextPacket(%s, %u, %d)\n", dbp->fn, tindex, eggid);
#endif

  /* See if the start address for this request is present
     in the seek optimisation table.  If so, seek directly
     to the address to avoid having to read through all
     earlier packets in the database. */

  i = seekOptIndex;
  /* Conditioning while on tindex > 0 causes seek optimisation to be
     skipped when a request for any packet is received. */
  while (tindex > 0) { 
      i--;
      if (i < 0) {
	  i = SEEK_OPT_MAX - 1;
      }
      if (strcmp(seekOpt[i].filename, dbp->fn) == 0 &&
	  seekOpt[i].last_time == tindex) {
	  fseek(dbp->fp, seekOpt[i].next_packet, SEEK_SET);
#ifdef STORAGE_DEBUG
        time_t last_time_val = seekOpt[i].last_time;
        fprintf(stderr, "LoadNextPacket; Seek optimised [%d] to %d for\n    file %s at %u %s",
	      i,
	      seekOpt[i].next_packet, 
	      seekOpt[i].filename,
	      seekOpt[i].last_time,
	      asctime(gmtime(&last_time_val)));

#endif
	  break;
      }
      if (i == seekOptIndex) {
#ifdef STORAGE_DEBUG
        time_t tindex_val = tindex;
          fprintf(stderr, "LoadNextPacket; Cannot optimise seek in file %s\n    at %u %s",
	      dbp->fn, tindex, asctime(gmtime(&tindex_val)));
#endif
	  break;		      /* Search wrapped table--cannot optimise */
      }
  }
  
  while (1) {
    res = FileLoadPacket(dbp->fp, &pktbuf);
    if (res == ERR_EOF) {
      /* File is finished.  Close and open the next.  We assume
	 that tindex and eggid have been tracking appropriately. */
      CloseDatabase(dbp);

      /* Advance findex to start of next day after the one we've
	 just exhausted. */

#define SECONDS_PER_DAY (24L * 60 * 60)
      findex = ((findex / SECONDS_PER_DAY) + 1) * SECONDS_PER_DAY;
#ifdef STORAGE_DEBUG
     time_t findex_val = findex;
      fprintf(stderr, "LoadNextPacket; EOF, CloseDatabase, findex = %u: %s",
	  findex, asctime(gmtime(&findex_val)));
#endif
      if (findex > now) {
#ifdef STORAGE_DEBUG
     time_t now_val = now;
          fprintf(stderr, "LoadNextPacket; EOF findex = %u > now = %u %s",
	  findex, now, asctime(gmtime(&now_val)));
#endif
	return ERR_EOF;
      }
      /* Do not reset database; we won't open same one again. */
      if ((res = OpenDatabase(dbp, findex, eggid)) < 0) return res;
      continue;
    }
/*
fprintf(stderr, "Eggid = %d Packet.eggid = %d\n", eggid, pktbuf.hdr.eggid);
*/
    if (eggid >= 0 && pktbuf.hdr.eggid != eggid) continue;
    for (i = 0; i < pktbuf.hdr.numrec; i++) {
/*fprintf(stderr, " Rec %ld timestamp = %lu  tindex = %lu\n", i, pktbuf.records[i].timestamp, tindex);
*/
      if (pktbuf.records[i].timestamp > tindex) {
	memcpy(cart, &pktbuf, sizeof(EggCarton));

	/* Save the location of the packet following this one
	   in the seek optimisation table. */

	strcpy(seekOpt[seekOptIndex].filename, dbp->fn);
	seekOpt[seekOptIndex].last_time = pktbuf.records[pktbuf.hdr.numrec - 1].timestamp;
	seekOpt[seekOptIndex].next_packet = ftell(dbp->fp);
#ifdef STORAGE_DEBUG
     time_t last_time_val = seekOpt[seekOptIndex].last_time;
        fprintf(stderr, "LoadNextPacket; Seek optimisation[%d] for %s\n    to address %d for time %u %s",
	    seekOptIndex,
	    seekOpt[seekOptIndex].filename,
	    seekOpt[seekOptIndex].next_packet, 
	    seekOpt[seekOptIndex].last_time,
	    asctime(gmtime(&last_time_val)));
#endif
	seekOptIndex = (seekOptIndex + 1) % SEEK_OPT_MAX;
	return ERR_NONE;
      }
    }
  }
}

/* Load next packet following a particular time index and egg id */

int32 LoadPacket(uint32 tindex, int16 eggid, EggCarton *cart) {
  DBRec db;
  int32 res;

  ResetDatabase(&db);	/* Here we need to be able to open same db as
			   last time. */
  if ((res = OpenDatabase(&db, tindex, eggid)) < 0) return res;
  if ((res = LoadNextPacket(&db, tindex, eggid, cart)) < 0) return res;
  return CloseDatabase(&db);
}

static int32 FileLoadPacket(FILE *fp, EggCarton *cart) {
  char *rbuf;
  int32 res;
  uint16 pksize;

  if (feof(fp)) return ERR_EOF;

  if (fread(&(cart->hdr.type), sizeof(uint16), 1, fp) != 1) {
    if (feof(fp)) return ERR_EOF; else return ERR_CNREAD;
  }
  if (fread(&(cart->hdr.pktsize), sizeof(uint16), 1, fp) != 1) {
    if (feof(fp)) return ERR_EOF; else return ERR_CNREAD;
  }

  /* Since the file packet is in network byte order, we need to
     be sure the length field is in host order before using
     it to allocate and read the balance of the packet from the
     file.  Once that's done, Unpacketize will take responsibility
     for getting all the packet fields into host order. */
  pksize = ntohs(cart->hdr.pktsize);

  rbuf = (char *)malloc(pksize);
  if (!rbuf) return ERR_NOMEM;

  memcpy(rbuf, &(cart->hdr), 2*sizeof(uint16));
  if (fread(rbuf+4, pksize - 4, 1, fp) != 1) return ERR_CNREAD;
  
  res = Unpacketize(cart, rbuf);
  free(rbuf);

  return res;
}

static int32 next_filename(char *fn, uint32 tindex, int16 eggid,
		    int16 *lastind, int16 mustexist) {
  struct stat	statbuf;
  uint32	findex, now;
  int32 	res;
  char		ldf[255];

#ifdef STORAGE_DEBUG
  time_t tindex_val = tindex;
  fprintf(stderr, "next_filename: egg = %d, mustexist = %d, tindex = %u %s",
    eggid, mustexist, tindex, asctime(gmtime(&tindex_val)));
#endif
  if (eggid < 0 && !mustexist) {
#ifdef STORAGE_DEBUG
    fprintf(stderr, "next_filename: egg ID out of range.\n");
#endif
    return ERR_INRANGE;
  }

  /* Can't go back before project began. */
  now = getzulutime(NULL);
  if (tindex < PROJSTART) {
    findex = PROJSTART;
  } else {
    findex = tindex;
  }

  /* Search possible file names until one is found.  We jump by days,
     assuming files don't have a greater granularity than that.  In
     the existence-testing process, we are checking to make sure the
     file name is different each time. */

  if (lastind == NULL) eggid = eggtable[0].id;
  else {
    if (eggid < 0) eggid = eggtable[(*lastind)++].id;
    if (*lastind == numeggs) *lastind = 0;
  }

  strcpy(ldf, fn);
  do {
    res = next_poss_fn(fn, findex, eggid);
    findex += 86400L;
    if (!strcmp(fn, ldf)) continue;
    strcpy(ldf, fn);
    /* Check for file.  If stat fails, reason is ENOENT, isn't it? */
    if (stat(fn, &statbuf) >= 0) {
#ifdef STORAGE_DEBUG
      fprintf(stderr, "next_filename: Found file %s\n", fn);
#endif
	return ERR_NONE;
    }
    if (!mustexist) {
#ifdef STORAGE_DEBUG
        fprintf(stderr, "next_filename: File %s does not exist.\n", fn);
#endif
	return ERR_NOENT;
    }
  } while (findex < now+86400L);

#ifdef STORAGE_DEBUG
      fprintf(stderr, "next_filename: End of file.\n");
#endif
  return ERR_EOF;
}

/* tindex and eggid must be defined here */
static int32 next_poss_fn(char *fn, uint32 tindex, int16 eggid) {
  char		datatmp[255], *sp;
  int16 	eggind;
  struct tm	*tm;

  time_t time_index = tindex;
  tm = gmtime(&time_index);
  strftime(datatmp, 255, datafmt, tm);
#ifdef STORAGE_DEBUG
  fprintf(stderr, "next_poss_fn: egg = %d, tindex = %u %s",
    eggid, tindex, asctime(tm));
#endif

  /* If an %e phrase remains after processing by strftime
     (it should be specified as %%e in the original
     DATAFMT specification), interpolate the egg number
     from this packet. */

  if ((sp = strchr(datatmp, '%')) != NULL) {
    if (sp[1] == 'e') {
      for (eggind = 0;
	   eggind < numeggs && eggtable[eggind].id != eggid;
	   eggind++);
      if (eggind == numeggs) {
#ifdef STORAGE_DEBUG
        fprintf(stderr, "next_poss_fn: Egg number out of range.\n");
#endif
	return ERR_INRANGE;
      }
      *sp = 0;
      strcpy(fn, datatmp);
      strcat(fn, eggtable[eggind].name);
      strcat(fn, sp + 2);
    } else {
      fprintf(stderr, "Bad file format expr: %%%c\n", sp[1]);
      strcpy(fn, datatmp);
      return ERR_INRANGE;
    }
  } else {
    strcpy(fn, datatmp);
  }
#ifdef STORAGE_DEBUG
  fprintf(stderr, "next_poss_fn: File name = %s\n", fn);
#endif
  return ERR_NONE;
}