/* This file is in the public domain. */ #include #include #include #include #include #include "structs.h" #include "repo.h" /* * Implementation of repo.h's interface. In this implementatino, each * repository is a single simple flat text file. */ /* * A REPO_CURSOR. This contains: * * r, a backpointer to the REPO. * * loc, where to start reading for the next possible sample. * * start and stop, the TIMESTAMPs giving its time window. * * eof, a boolean indicating it's run out of samples. * * ts, the current sample's timestmap, when it has a sample. * * lb, la, and ll, which are an elastic buffer into which we read lines * from the data file. * * dataat, which is the offset into lb of the sample's data string, * when the cursor has a sample. * * There is no explicit indication of whether the cursor has a sample; * this is a concept introduced to explain when it's meaningful to * call repo_cursor_stamp and repo_cursor_data, not anything with a * direct representation. */ struct repo_cursor { REPO *r; off_t loc; TIMESTAMP start; TIMESTAMP stop; int eof; TIMESTAMP ts; char *lb; int la; int ll; int dataat; } ; /* * A REPO's private data. In this implementation, this is just a stdio * FILE * open onto the file, plus a struct stat for it for comparison * with other REPO_PRIVs. */ struct repo_priv { FILE *f; struct stat stb; } ; /* * Open a repository. Just do the open(2), error-check, and set up the * corresponding REPO_PRIV. */ REPO_PRIV *repo_open(const char *fn, void (*err)(const char *, ...)) { int fd; REPO_PRIV *p; fd = open(fn,O_RDWR,0666); if (fd < 0) (*err)("%s: %s",fn,strerror(errno)); p = malloc(sizeof(REPO_PRIV)); p->f = fdopen(fd,"r+"); fstat(fd,&p->stb); return(p); } /* * Check of two REPO_PRIVs are open onto the same repository. We just * check if the files are the same by comparing st_dev and st_ino * numbers. */ int repo_same(REPO_PRIV *a, REPO_PRIV *b) { return( (a->stb.st_dev == b->stb.st_dev) && (a->stb.st_ino == b->stb.st_ino) ); } /* * Close down a REPO_PRIV. Pretty simple in this implementation. */ void repo_close(REPO_PRIV *p) { fclose(p->f); free(p); } /* * Free a whole REPO. Nothing noteworthy here. */ void repo_free(REPO *r) { free(r->name); free(r->filename); repo_close(r->priv); free(r); } /* * Free a whoel linked list of REPOs. Nothing noteworthy here. */ void repo_free_chain(REPO *chain) { REPO *r; while (chain) { r = chain; chain = r->link; repo_free(r); } } /* * Look up a REPO by name. * * We go through all mappings, checking their match functions, * remembering the highest priority that matched and the highest * priority that indicated an error. If there was an error and it * wasn't overriddden by a higher-priority non-error, error out; * otherwise, if there were no matches, error out; otherwise, have all * the MAPs at the highest priority map the name and check that they * all match (error if not). Once we have the mapped-to string, look * for the repo and return it (error if not found). */ REPO *repo_lookup(LISTEN *l, const char *name, void (*err)(const char *, ...)) { MAP *m; int maxpri; int errpri; char *mapped; char *mstr; REPO *r; maxpri = -1; errpri = -1; for (m=l->mappings;m;m=m->link) { if (m->pri < maxpri) continue; switch ((*m->ops->match)(m->priv,name)) { case MATCH_NOMATCH: m->flags &= ~MF_MATCHED; break; case MATCH_MATCH: m->flags |= MF_MATCHED; maxpri = m->pri; break; case MATCH_ERROR: errpri = m->pri; break; default: abort(); break; } } if ((errpri >= 0) && (maxpri <= errpri)) { printf("error match\n"); // debugging (*err)("%s: bad repository name",name); } if (maxpri < 0) { printf("no matching mapping\n"); // debugging (*err)("%s: unmatched repository name",name); } mapped = 0; for (m=l->mappings;m;m=m->link) { if ((m->pri != maxpri) || !(m->flags & MF_MATCHED)) continue; mstr = (*m->ops->map)(m->priv,name); printf("mapped to %s\n",mstr); // debugging if (mapped && strcmp(mstr,mapped)) { printf("mapping conflict\n"); free(mapped); free(mstr); (*err)("%s: ambiguous repository name",name); } free(mapped); mapped = mstr; } if (! mapped) abort(); if (l->conf) { for (r=l->conf->repos;r;r=r->link) { if (! strcmp(mapped,r->name)) { printf("repo found\n"); // debugging return(r); } } } else { printf("orphaned LISTEN\n"); // debugging } printf("no repo found\n"); // debugging free(mapped); (*err)("%s: unknown repository name",name); abort(); } /* * Record the data with the associated timestamp to the repository. * Nothing noteworthy here. */ void repo_record(REPO *r, unsigned long long int ts, const char *data, void (*err)(const char *, ...)) { long long int iv; double dv; char *ep; char *ds; switch (r->type) { case DT_INT: iv = strtoll(data,&ep,0); if ((ep == data) || *ep) (*err)("invalid data"); asprintf(&ds,"%lld",iv); break; case DT_FLOAT: dv = strtod(data,&ep); if ((ep == data) || *ep) (*err)("invalid data"); asprintf(&ds,"%.20g",dv); break; default: abort(); break; } flock(fileno(r->priv->f),LOCK_EX); fseek(r->priv->f,0,SEEK_END); fprintf(r->priv->f,"%llu %s\n",ts,ds); fflush(r->priv->f); flock(fileno(r->priv->f),LOCK_UN); } /* * Open a cursor. All we do here is set up the data structure; opening * does not imply an automatic rewind or advance. */ REPO_CURSOR *repo_cursor_open(REPO *r, TIMESTAMP start, TIMESTAMP stop) { REPO_CURSOR *c; printf("repo_cursor_open %s [%llu,%llu)\n",r->name,(unsigned long long int)start,(unsigned long long int)stop); // debugging c = malloc(sizeof(REPO_CURSOR)); c->r = r; c->start = start; c->stop = stop; c->lb = 0; c->la = 0; return(c); } /* * Close down a cursor. Nothing noteworthy here. */ void repo_cursor_close(REPO_CURSOR *c) { printf("repo_cursor_close\n"); // debugging free(c->lb); free(c); } /* * Rewind a cursor. We do this by just setting its location to zero * and advancing. Don't forget to clear its EOF indication at the * same time. */ void repo_cursor_rewind(REPO_CURSOR *c) { printf("repo_cursor_rewind\n"); // debugging c->loc = 0; c->eof = 0; repo_cursor_advance(c); } /* * Return EOF indication. We have an instance variable for this. */ int repo_cursor_eof(REPO_CURSOR *c) { printf("repo_cursor_eof -> %d\n",c->eof); // debugging return(c->eof); } /* * Return sample timestamp. We have an instance variable for this. */ TIMESTAMP repo_cursor_stamp(REPO_CURSOR *c) { printf("repo_cursor_stamp -> %llu\n",(unsigned long long int)c->ts); // debugging return(c->ts); } /* * Return sample data. This is just an offset into lb. */ const char *repo_cursor_data(REPO_CURSOR *c) { printf("repo_cursor_data -> %s\n",c->lb+c->dataat); // debugging return(c->lb+c->dataat); } /* * Advance a cursor. Just keep reading samples until we either hit EOF * or we find one within the appropriate range. */ void repo_cursor_advance(REPO_CURSOR *c) { FILE *f; int ch; char *ep; printf("repo_cursor_advance starting at %llu\n",(unsigned long long int)c->loc); // debugging f = c->r->priv->f; while (1) { fseeko(f,c->loc,SEEK_SET); c->ll = 0; while (1) { ch = getc(f); if (ch == EOF) { c->eof = 1; printf("repo_cursor_advance read to EOF\n"); // debugging return; } if (c->ll >= c->la) c->lb = realloc(c->lb,c->la=c->ll+8); c->lb[c->ll++] = ch; if (ch == '\n') { c->lb[c->ll-1] = '\0'; c->ts = strtoull(c->lb,&ep,10); printf("repo_cursor_advance read %s\n",c->lb); // debugging if ((c->ts >= c->start) && (c->ts < c->stop)) { while (*ep == ' ') ep ++; c->dataat = ep - c->lb; c->loc = ftello(f); printf("repo_cursor_advance returning at %llu\n",(unsigned long long int)c->loc); // debugging return; } c->ll = 0; } } } }