diff options
Diffstat (limited to 'main/filesdb.c')
-rw-r--r-- | main/filesdb.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/main/filesdb.c b/main/filesdb.c new file mode 100644 index 000000000..642af3683 --- /dev/null +++ b/main/filesdb.c @@ -0,0 +1,649 @@ +/* + * dpkg - main program for package management + * filesdb.c - management of database of files installed on system + * + * Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk> + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2, + * or (at your option) any later version. + * + * This is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with dpkg; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/stat.h> + +#include "config.h" +#include "dpkg.h" +#include "dpkg-db.h" + +#include "filesdb.h" +#include "main.h" + +/*** Generic data structures and routines ***/ + +static int allpackagesdone= 0; +static int nfiles= 0; +static struct diversion *diversions= 0; +static FILE *diversionsfile= 0; + +void note_must_reread_files_inpackage(struct pkginfo *pkg) { + allpackagesdone= 0; + ensure_package_clientdata(pkg); + pkg->clientdata->fileslistvalid= 0; +} + +static int saidread=0; + +void ensure_packagefiles_available(struct pkginfo *pkg) { + static struct varbuf fnvb; + static char stdiobuf[8192]; + + FILE *file; + const char *filelistfile; + struct fileinlist **lendp, *newent, *current; + struct filepackages *packageslump; + int search, findlast, putat, l; + char *thefilename; + char linebuf[1024]; + + if (pkg->clientdata && pkg->clientdata->fileslistvalid) return; + ensure_package_clientdata(pkg); + + /* Throw away any the stale data, if there was any. */ + for (current= pkg->clientdata->files; + current; + current= current->next) { + /* For each file that used to be in the package, + * go through looking for this package's entry in the list + * of packages containing this file, and blank it out. + */ + for (packageslump= current->namenode->packages; + packageslump; + packageslump= packageslump->more) + for (search= 0; + search < PERFILEPACKAGESLUMP && packageslump->pkgs[search]; + search++) + if (packageslump->pkgs[search] == pkg) { + /* Hah! Found it. */ + for (findlast= search+1; + findlast < PERFILEPACKAGESLUMP && packageslump->pkgs[findlast]; + findlast++); + findlast--; + /* findlast is now the last occupied entry, which may be the same as + * search. We blank out the entry for this package. We also + * have to copy the last entry into the empty slot, because + * the list is null-pointer-terminated. + */ + packageslump->pkgs[search]= packageslump->pkgs[findlast]; + packageslump->pkgs[findlast]= 0; + /* This may result in an empty link in the list. This is OK. */ + goto xit_search_to_delete_from_perfilenodelist; + } + xit_search_to_delete_from_perfilenodelist: + ; + /* The actual filelist links were allocated using nfmalloc, so + * we shouldn't free them. + */ + } + pkg->clientdata->files= 0; + + /* Packages which aren't installed don't have a files list. */ + if (pkg->status == stat_notinstalled) { + pkg->clientdata->fileslistvalid= 1; return; + } + + filelistfile= pkgadminfile(pkg,LISTFILE); + + onerr_abort++; + + file= fopen(filelistfile,"r"); + + if (!file) { + if (errno != ENOENT) + ohshite("unable to open files list file for package `%.250s'",pkg->name); + onerr_abort--; + if (pkg->status != stat_configfiles) { + if (saidread == 1) putc('\n',stderr); + fprintf(stderr, + DPKG ": serious warning: files list file for package `%.250s' missing," + " assuming package has no files currently installed.\n", pkg->name); + } + pkg->clientdata->files= 0; + pkg->clientdata->fileslistvalid= 1; + return; + } + + push_cleanup(cu_closefile,ehflag_bombout, 0,0, 1,(void*)file); + + if (setvbuf(file,stdiobuf,_IOFBF,sizeof(stdiobuf))) + ohshite("unable to set buffering on `%.250s'",filelistfile); + + lendp= &pkg->clientdata->files; + varbufreset(&fnvb); + while (fgets(linebuf,sizeof(linebuf),file)) { + /* This is a very important loop, and it is therefore rather messy. + * We break the varbuf abstraction even more than usual, and we + * avoid copying where possible. + */ + l= strlen(linebuf); + if (l == 0) ohshit("fgets gave an empty null-terminated string from `%.250s'", + filelistfile); + l--; + if (linebuf[l] != '\n') { + varbufaddstr(&fnvb,linebuf); + continue; + } else if (!fnvb.used && l>0 && linebuf[l-1] != '/') { /* fast path */ + linebuf[l]= 0; + thefilename= linebuf; + } else { + if (l>0 && linebuf[l-1] == '/') l--; /* strip trailing slashes */ + linebuf[l]= 0; + varbufaddstr(&fnvb,linebuf); + varbufaddc(&fnvb,0); + fnvb.used= 0; + thefilename= fnvb.buf; + } + if (!*thefilename) + ohshit("files list file for package `%.250s' contains empty filename",pkg->name); + newent= nfmalloc(sizeof(struct fileinlist)); + newent->namenode= findnamenode(thefilename); + newent->next= 0; + *lendp= newent; + lendp= &newent->next; + } + if (ferror(file)) + ohshite("error reading files list file for package `%.250s'",pkg->name); + pop_cleanup(ehflag_normaltidy); /* file= fopen() */ + if (fclose(file)) + ohshite("error closing files list file for package `%.250s'",pkg->name); + if (fnvb.used) + ohshit("files list file for package `%.250s' is truncated",pkg->name); + + onerr_abort--; + + for (newent= pkg->clientdata->files; newent; newent= newent->next) { + packageslump= newent->namenode->packages; + if (packageslump) { + for (putat= 0; + putat < PERFILEPACKAGESLUMP && packageslump->pkgs[putat]; + putat++); + if (putat >= PERFILEPACKAGESLUMP) packageslump= 0; + } + if (!packageslump) { + packageslump= nfmalloc(sizeof(struct filepackages)); + packageslump->more= newent->namenode->packages; + newent->namenode->packages= packageslump; + putat= 0; + } + packageslump->pkgs[putat]= pkg; + if (++putat < PERFILEPACKAGESLUMP) packageslump->pkgs[putat]= 0; + } + pkg->clientdata->fileslistvalid= 1; +} + +void ensure_allinstfiles_available(void) { + struct pkgiterator *it; + struct pkginfo *pkg; + + if (allpackagesdone) return; + if (saidread<2) { + saidread=1; + printf(f_largemem>0 ? "(Reading database ... " : "(Scanning database ... "); + } + it= iterpkgstart(); + while ((pkg= iterpkgnext(it)) != 0) ensure_packagefiles_available(pkg); + iterpkgend(it); + allpackagesdone= 1; + + if (saidread==1) { + printf("%d files and directories currently installed.)\n",nfiles); + saidread=2; + } +} + +void ensure_allinstfiles_available_quiet(void) { + saidread=2; + ensure_allinstfiles_available(); +} + +void write_filelist_except(struct pkginfo *pkg, struct fileinlist *list, int leaveout) { + /* If leaveout is nonzero, will not write any file whose filenamenode + * has the fnnf_elide_other_lists flag set. + */ + static struct varbuf vb, newvb; + FILE *file; + + varbufreset(&vb); + varbufaddstr(&vb,admindir); + varbufaddstr(&vb,"/" INFODIR); + varbufaddstr(&vb,pkg->name); + varbufaddstr(&vb,"." LISTFILE); + varbufaddc(&vb,0); + + varbufreset(&newvb); + varbufaddstr(&newvb,vb.buf); + varbufaddstr(&newvb,NEWDBEXT); + varbufaddc(&newvb,0); + + file= fopen(newvb.buf,"w+"); + if (!file) + ohshite("unable to create updated files list file for package %s",pkg->name); + push_cleanup(cu_closefile,ehflag_bombout, 0,0, 1,(void*)file); + while (list) { + if (!(leaveout && (list->namenode->flags & fnnf_elide_other_lists))) { + fputs(list->namenode->name,file); + putc('\n',file); + } + list= list->next; + } + if (ferror(file)) + ohshite("failed to write to updated files list file for package %s",pkg->name); + if (fflush(file)) + ohshite("failed to flush updated files list file for package %s",pkg->name); + if (fsync(fileno(file))) + ohshite("failed to sync updated files list file for package %s",pkg->name); + pop_cleanup(ehflag_normaltidy); /* file= fopen() */ + if (fclose(file)) + ohshite("failed to close updated files list file for package %s",pkg->name); + if (rename(newvb.buf,vb.buf)) + ohshite("failed to install updated files list file for package %s",pkg->name); + + note_must_reread_files_inpackage(pkg); +} + +void reversefilelist_init(struct reversefilelistiter *iterptr, + struct fileinlist *files) { + /* Initialises an iterator that appears to go through the file + * list `files' in reverse order, returning the namenode from + * each. What actually happens is that we walk the list here, + * building up a reverse list, and then peel it apart one + * entry at a time. + */ + struct fileinlist *newent; + + iterptr->todo= 0; + while (files) { + newent= m_malloc(sizeof(struct fileinlist)); + newent->namenode= files->namenode; + newent->next= iterptr->todo; + iterptr->todo= newent; + files= files->next; + } +} + +struct filenamenode *reversefilelist_next(struct reversefilelistiter *iterptr) { + struct filenamenode *ret; + struct fileinlist *todo; + + todo= iterptr->todo; + if (!todo) return 0; + ret= todo->namenode; + iterptr->todo= todo->next; + free(todo); + return ret; +} + +void reversefilelist_abort(struct reversefilelistiter *iterptr) { + /* Clients must call this function to clean up the reversefilelistiter + * if they wish to break out of the iteration before it is all done. + * Calling this function is not necessary if reversefilelist_next has + * been called until it returned 0. + */ + while (reversefilelist_next(iterptr)); +} + +void ensure_diversions(void) { + static struct varbuf vb; + + struct stat stab1, stab2; + char linebuf[MAXDIVERTFILENAME]; + FILE *file; + struct diversion *ov, *oicontest, *oialtname; + int l; + + varbufreset(&vb); + varbufaddstr(&vb,admindir); + varbufaddstr(&vb,"/" DIVERSIONSFILE); + varbufaddc(&vb,0); + + onerr_abort++; + + file= fopen(vb.buf,"r"); + if (!file) { + if (errno != ENOENT) ohshite("failed to open diversions file"); + if (!diversionsfile) { onerr_abort--; return; } + } else if (diversionsfile) { + if (fstat(fileno(diversionsfile),&stab1)) + ohshite("failed to fstat previous diversions file"); + if (fstat(fileno(file),&stab2)) + ohshite("failed to fstat diversions file"); + if (stab1.st_dev == stab2.st_dev && stab1.st_ino == stab2.st_ino) { + fclose(file); onerr_abort--; return; + } + } + if (diversionsfile) fclose(diversionsfile); + diversionsfile= file; + + for (ov= diversions; ov; ov= ov->next) { + ov->useinstead->divert= 0; + ov->camefrom->divert= 0; + } + if (!file) { onerr_abort--; return; } + + while (fgets(linebuf,sizeof(linebuf),file)) { + oicontest= nfmalloc(sizeof(struct diversion)); + oialtname= nfmalloc(sizeof(struct diversion)); + + l= strlen(linebuf); + if (l == 0) ohshit("fgets gave an empty string from diversions [i]"); + if (linebuf[--l] != '\n') ohshit("diversions file has too-long line or EOF [i]"); + linebuf[l]= 0; + oialtname->camefrom= findnamenode(linebuf); + + if (!fgets(linebuf,sizeof(linebuf),file)) + if (ferror(file)) ohshite("read error in diversions [ii]"); + else ohshit("unexpected EOF in diversions [ii]"); + l= strlen(linebuf); + if (l == 0) ohshit("fgets gave an empty string from diversions [ii]"); + if (linebuf[--l] != '\n') ohshit("diversions file has too-long line or EOF [ii]"); + linebuf[l]= 0; + oicontest->useinstead= findnamenode(linebuf); + + if (!fgets(linebuf,sizeof(linebuf),file)) + if (ferror(file)) ohshite("read error in diversions [iii]"); + else ohshit("unexpected EOF in diversions [iii]"); + l= strlen(linebuf); + if (l == 0) ohshit("fgets gave an empty string from diversions [iii]"); + if (linebuf[--l] != '\n') ohshit("diversions file has too-long line or EOF [ii]"); + linebuf[l]= 0; + + oicontest->pkg= oialtname->pkg= + strcmp(linebuf,":") ? findpackage(linebuf) : 0; + + if (oialtname->camefrom->divert || oicontest->useinstead->divert) + ohshit("conflicting diversions involving `%.250s' or `%.250s'", + oicontest->camefrom->name, oicontest->useinstead->name); + + oialtname->camefrom->divert= oicontest; + oicontest->useinstead->divert= oialtname; + } + if (ferror(file)) ohshite("read error in diversions [i]"); + + diversionsfile= file; + onerr_abort--; +} + +/*** Data structures common to both in-core databases ***/ + +struct fileiterator { + union { + struct { + struct filenamenode *current; + } low; + struct { + struct filenamenode *namenode; + int nbinn; + } high; + } u; +}; + +/*** Data structures for fast, large memory usage database ***/ + +#define BINS (1 << 13) + /* This must always be a power of two. If you change it + * consider changing the per-character hashing factor (currently + * 1785 = 137*13) too. + */ + +static struct filenamenode *bins[BINS]; + +/*** Data structures for low-memory-footprint in-core files database ***/ + +struct fdirents { + struct fdirents *more; + struct { const char *component; struct fdirnode *go; } entries[1]; + /* first one has one entry, then two, then four, &c */ +}; + +struct fdirnode { + struct filenamenode *here; + struct fdirents *contents; +}; + +static struct fdirnode fdirroot; +static struct filenamenode *allfiles; + +struct filenamesblock { + struct filenamesblock *next; + char *data; +}; + +static struct filenamesblock *namesarea= 0; +static int namesarealeft= 0; + +/*** Code for both. This is rather hacky, sorry ... ***/ + +struct fileiterator *iterfilestart(void) { + struct fileiterator *i; + i= m_malloc(sizeof(struct fileiterator)); + switch (f_largemem) { + case 1: + i->u.high.namenode= 0; + i->u.high.nbinn= 0; + break; + case -1: + i->u.low.current= allfiles; + break; + default: + internerr("iterfilestart no f_largemem"); + } + return i; +} + +struct filenamenode *iterfilenext(struct fileiterator *i) { + struct filenamenode *r; + switch (f_largemem) { + case 1: + while (!i->u.high.namenode) { + if (i->u.high.nbinn >= BINS) return 0; + i->u.high.namenode= bins[i->u.high.nbinn++]; + } + r= i->u.high.namenode; + i->u.high.namenode= r->next; + break; + case -1: + if (!i->u.low.current) return 0; + r= i->u.low.current; + i->u.low.current= i->u.low.current->next; + break; + default: + internerr("iterfilenext no f_largemem"); + } + return r; +} + +void iterfileend(struct fileiterator *i) { + free(i); +} + +void filesdbinit(void) { + struct filenamenode *fnn; + struct sysinfo info; + int i; + + if (!f_largemem) { + f_largemem= -1; + if (!sysinfo(&info)) { + if (info.freeram + (info.sharedram>>2) + (info.bufferram>>2) >= 4096*1024 || + info.totalram >= 6144) + f_largemem= 1; + } + } + + switch (f_largemem) { + case 1: + for (i=0; i<BINS; i++) + for (fnn= bins[i]; fnn; fnn= fnn->next) { + fnn->flags= 0; + fnn->oldhash= 0; + } + break; + case -1: + for (fnn= allfiles; + fnn; + fnn= fnn->next) { + fnn->flags= 0; + fnn->oldhash= 0; + } + break; + default: + internerr("filesdbinit no f_largemem"); + } +} + +static struct filenamenode *findnamenode_high(const char *name); +static struct filenamenode *findnamenode_low(const char *name); + +struct filenamenode *findnamenode(const char *name) { + switch (f_largemem) { + case 1: + return findnamenode_high(name); + case -1: + return findnamenode_low(name); + default: + internerr("findnamenode no f_largemem"); + } +} + +/*** Code for low-memory-footprint in-core files database ***/ + +static struct filenamenode *findnamenode_low(const char *name) { + struct fdirnode *traverse; + struct fdirents *ents, **addto; + const char *nameleft, *slash; + char *p; + struct filenamesblock *newblock; + int n, i, nentrieshere, alloc; + + /* We skip initial slashes and ./ pairs, and add our own single leading slash. */ + name= skip_slash_dotslash(name); + + nameleft= name; + traverse= &fdirroot; + while (nameleft) { + slash= strchr(nameleft,'/'); + if (slash) { + n= (int)(slash-nameleft); slash++; + } else { + n= strlen(nameleft); + } + ents= traverse->contents; addto= &traverse->contents; i= 0; nentrieshere= 1; + for (;;) { + if (!ents) break; + if (!ents->entries[i].component) break; + if (!strncmp(ents->entries[i].component,nameleft,n) && + !ents->entries[i].component[n]) { + break; + } + i++; + if (i < nentrieshere) continue; + addto= &ents->more; + ents= ents->more; + nentrieshere += nentrieshere; + i=0; + } + if (!ents) { + ents= nfmalloc(sizeof(struct fdirents) + + sizeof(ents->entries[0])*(nentrieshere-1)); + i=0; + ents->entries[i].component= 0; + ents->more= 0; + *addto= ents; + } + if (!ents->entries[i].component) { + p= nfmalloc(n+1); + memcpy(p,nameleft,n); p[n]= 0; + ents->entries[i].component= p; + ents->entries[i].go= nfmalloc(sizeof(struct fdirnode)); + ents->entries[i].go->here= 0; + ents->entries[i].go->contents= 0; + if (i+1 < nentrieshere) + ents->entries[i+1].component= 0; + } + traverse= ents->entries[i].go; + nameleft= slash; + } + if (traverse->here) return traverse->here; + + traverse->here= nfmalloc(sizeof(struct filenamenode)); + traverse->here->packages= 0; + traverse->here->flags= 0; + traverse->here->divert= 0; + + n= strlen(name)+2; + if (namesarealeft < n) { + newblock= m_malloc(sizeof(struct filenamesblock)); + alloc= 256*1024; + if (alloc<n) alloc= n; + newblock->data= m_malloc(alloc); + newblock->next= namesarea; + namesarea= newblock; + namesarealeft= alloc; + } + namesarealeft-= n; + p= namesarea->data+namesarealeft; + traverse->here->name= p; *p++= '/'; strcpy(p,name); + + traverse->here->next= allfiles; + allfiles= traverse->here; + nfiles++; + return traverse->here; +} + +/*** Code for high memory usage fast database ***/ + +static int hash(const char *name) { + int v= 0; + while (*name) { v *= 1785; v += *name; name++; } + return v; +} + +struct filenamenode *findnamenode_high(const char *name) { + struct filenamenode **pointerp, *newnode; + + /* We skip initial slashes and ./ pairs, and add our own single leading slash. */ + name= skip_slash_dotslash(name); + + pointerp= bins + (hash(name) & (BINS-1)); + while (*pointerp) { + assert((*pointerp)->name[0] == '/'); + if (!strcmp((*pointerp)->name+1,name)) break; + pointerp= &(*pointerp)->next; + } + if (*pointerp) return *pointerp; + + newnode= nfmalloc(sizeof(struct filenamenode)); + newnode->packages= 0; + newnode->name= nfmalloc(strlen(name)+2); + newnode->name[0]= '/'; strcpy(newnode->name+1,name); + newnode->flags= 0; + newnode->next= 0; + newnode->divert= 0; + *pointerp= newnode; + nfiles++; + + return newnode; +} |