/* * dpkg - main program for package management * filesdb.c - management of database of files installed on system * * Copyright © 1995 Ian Jackson * Copyright © 2000,2001 Wichert Akkerman * * 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 of the License, 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 this program. If not, see . */ #include #include #ifdef HAVE_LINUX_FIEMAP_H #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filesdb.h" #include "main.h" /*** Package control information database directory routines. ***/ /* * XXX: Strictly speaking these functions do not exactly belong here, and * they should be eventually moved back to a unified on-disk database * handling module in libdpkg. For now this is good enough, as it avoids * pulling unneeded code into the resulting binaries, because all its * users require filesdb anyway. */ static char *infodir; static void pkgadmindir_init(void) { infodir = dpkg_db_get_path(INFODIR); } const char * pkgadmindir(void) { return infodir; } const char * pkgadminfile(struct pkginfo *pkg, const char *filetype) { static struct varbuf vb; varbuf_reset(&vb); varbuf_add_str(&vb, infodir); varbuf_add_str(&vb, pkg->name); varbuf_add_char(&vb, '.'); varbuf_add_str(&vb, filetype); varbuf_end_str(&vb); return vb.buf; } /*** filepackages support for tracking packages owning a file. ***/ #define PERFILEPACKAGESLUMP 10 struct filepackages { struct filepackages *more; /* pkgs is a NULL-pointer-terminated list; anything after the first NULL * is garbage. */ struct pkginfo *pkgs[PERFILEPACKAGESLUMP]; }; struct filepackages_iterator { struct filepackages *pkg_lump; int pkg_idx; }; struct filepackages_iterator * filepackages_iter_new(struct filenamenode *fnn) { struct filepackages_iterator *iter; iter = m_malloc(sizeof(*iter)); iter->pkg_lump = fnn->packages; iter->pkg_idx = 0; return iter; } struct pkginfo * filepackages_iter_next(struct filepackages_iterator *iter) { struct pkginfo *pkg; while (iter->pkg_lump) { pkg = iter->pkg_lump->pkgs[iter->pkg_idx]; if (iter->pkg_idx < PERFILEPACKAGESLUMP && pkg) { iter->pkg_idx++; return pkg; } else { iter->pkg_lump = iter->pkg_lump->more; iter->pkg_idx = 0; } } return NULL; } void filepackages_iter_free(struct filepackages_iterator *iter) { free(iter); } /*** Generic data structures and routines. ***/ static bool allpackagesdone = false; static int nfiles= 0; void ensure_package_clientdata(struct pkginfo *pkg) { if (pkg->clientdata) return; pkg->clientdata = nfmalloc(sizeof(struct perpackagestate)); pkg->clientdata->istobe = itb_normal; pkg->clientdata->color = white; pkg->clientdata->fileslistvalid = false; pkg->clientdata->files = NULL; pkg->clientdata->listfile_phys_offs = 0; pkg->clientdata->trigprocdeferred = NULL; } void note_must_reread_files_inpackage(struct pkginfo *pkg) { allpackagesdone = false; ensure_package_clientdata(pkg); pkg->clientdata->fileslistvalid = false; } static int saidread=0; /** * Erase the files saved in pkg. */ static void pkg_files_blank(struct pkginfo *pkg) { struct fileinlist *current; struct filepackages *packageslump; int search, findlast; /* Anything to empty? */ if (!pkg->clientdata) return; 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] = NULL; /* 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 = NULL; } static struct fileinlist ** pkg_files_add_file(struct pkginfo *pkg, const char *filename, enum fnnflags flags, struct fileinlist **file_tail) { struct fileinlist *newent; struct filepackages *packageslump; int putat = 0; ensure_package_clientdata(pkg); if (file_tail == NULL) file_tail = &pkg->clientdata->files; /* Make sure we're at the end. */ while ((*file_tail) != NULL) { file_tail = &((*file_tail)->next); } /* Create a new node. */ newent = nfmalloc(sizeof(struct fileinlist)); newent->namenode = findnamenode(filename, flags); newent->next = NULL; *file_tail = newent; file_tail = &newent->next; /* Add pkg to newent's package list. */ packageslump = newent->namenode->packages; putat = 0; if (packageslump) { while (putat < PERFILEPACKAGESLUMP && packageslump->pkgs[putat]) putat++; if (putat >= PERFILEPACKAGESLUMP) packageslump = NULL; } 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] = NULL; /* Return the position for the next guy. */ return file_tail; } /** * Load the list of files in this package into memory, or update the * list if it is there but stale. */ void ensure_packagefiles_available(struct pkginfo *pkg) { static int fd; const char *filelistfile; struct fileinlist **lendp; struct stat stat_buf; char *loaded_list, *loaded_list_end, *thisline, *nextline, *ptr; if (pkg->clientdata && pkg->clientdata->fileslistvalid) return; ensure_package_clientdata(pkg); /* Throw away any stale data, if there was any. */ pkg_files_blank(pkg); /* Packages which aren't installed don't have a files list. */ if (pkg->status == stat_notinstalled) { pkg->clientdata->fileslistvalid = true; return; } filelistfile= pkgadminfile(pkg,LISTFILE); onerr_abort++; fd= open(filelistfile,O_RDONLY); if (fd==-1) { 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); warning(_("files list file for package `%.250s' missing, assuming " "package has no files currently installed."), pkg->name); } pkg->clientdata->files = NULL; pkg->clientdata->fileslistvalid = true; return; } push_cleanup(cu_closefd, ehflag_bombout, NULL, 0, 1, &fd); if(fstat(fd, &stat_buf)) ohshite(_("unable to stat files list file for package '%.250s'"), pkg->name); if (stat_buf.st_size) { loaded_list = nfmalloc(stat_buf.st_size); loaded_list_end = loaded_list + stat_buf.st_size; if (fd_read(fd, loaded_list, stat_buf.st_size) < 0) ohshite(_("reading files list for package '%.250s'"), pkg->name); lendp= &pkg->clientdata->files; thisline = loaded_list; while (thisline < loaded_list_end) { if (!(ptr = memchr(thisline, '\n', loaded_list_end - thisline))) ohshit(_("files list file for package '%.250s' is missing final newline"), pkg->name); /* Where to start next time around. */ nextline = ptr + 1; /* Strip trailing ‘/’. */ if (ptr > thisline && ptr[-1] == '/') ptr--; /* Add the file to the list. */ if (ptr == thisline) ohshit(_("files list file for package `%.250s' contains empty filename"),pkg->name); *ptr = '\0'; lendp = pkg_files_add_file(pkg, thisline, fnn_nocopy, lendp); thisline = nextline; } } pop_cleanup(ehflag_normaltidy); /* fd = open() */ if (close(fd)) ohshite(_("error closing files list file for package `%.250s'"),pkg->name); onerr_abort--; pkg->clientdata->fileslistvalid = true; } #if defined(HAVE_LINUX_FIEMAP_H) static int pkg_sorter_by_listfile_phys_offs(const void *a, const void *b) { const struct pkginfo *pa = *(const struct pkginfo **)a; const struct pkginfo *pb = *(const struct pkginfo **)b; /* We can't simply subtract, because the difference may be greater than * INT_MAX. */ if (pa->clientdata->listfile_phys_offs < pb->clientdata->listfile_phys_offs) return -1; else return 1; } static void pkg_files_optimize_load(struct pkg_array *array) { int i; int blocksize = 0; /* Sort packages by the physical location of their list files, so that * scanning them later will minimize disk drive head movements. */ for (i = 0; i < array->n_pkgs; i++) { struct pkginfo *pkg = array->pkgs[i]; struct { struct fiemap fiemap; struct fiemap_extent extent; } fm; const char *listfile; int fd; ensure_package_clientdata(pkg); if (pkg->status == stat_notinstalled || pkg->clientdata->listfile_phys_offs != 0) continue; pkg->clientdata->listfile_phys_offs = -1; listfile = pkgadminfile(pkg, LISTFILE); fd = open(listfile, O_RDONLY); if (fd < 0) continue; if (!blocksize && ioctl(fd, FIGETBSZ, &blocksize) < 0) break; memset(&fm, 0, sizeof(fm)); fm.fiemap.fm_start = 0; fm.fiemap.fm_length = blocksize; fm.fiemap.fm_flags = 0; fm.fiemap.fm_extent_count = 1; if (ioctl(fd, FS_IOC_FIEMAP, (unsigned long)&fm) == 0) pkg->clientdata->listfile_phys_offs = fm.fiemap.fm_extents[0].fe_physical; close(fd); } pkg_array_sort(array, pkg_sorter_by_listfile_phys_offs); } #elif defined(HAVE_POSIX_FADVISE) static void pkg_files_optimize_load(struct pkg_array *array) { int i; /* Ask the kernel to start preloading the list files, so as to get a * boost when later we actually load them. */ for (i = 0; i < array->n_pkgs; i++) { struct pkginfo *pkg = array->pkgs[i]; const char *listfile; int fd; listfile = pkgadminfile(pkg, LISTFILE); fd = open(listfile, O_RDONLY | O_NONBLOCK); if (fd != -1) { posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED); close(fd); } } } #else static void pkg_files_optimize_load(struct pkg_array *array) { } #endif void ensure_allinstfiles_available(void) { struct pkg_array array; struct pkginfo *pkg; struct progress progress; int i; if (allpackagesdone) return; if (saidread<2) { int max = pkg_db_count(); saidread=1; progress_init(&progress, _("(Reading database ... "), max); } pkg_array_init_from_db(&array); pkg_files_optimize_load(&array); for (i = 0; i < array.n_pkgs; i++) { pkg = array.pkgs[i]; ensure_packagefiles_available(pkg); if (saidread == 1) progress_step(&progress); } pkg_array_destroy(&array); allpackagesdone = true; if (saidread==1) { progress_done(&progress); printf(P_("%d file or directory currently installed.)\n", "%d files and directories currently installed.)\n", nfiles), nfiles); saidread=2; } } void ensure_allinstfiles_available_quiet(void) { saidread=2; ensure_allinstfiles_available(); } /* * If mask is nonzero, will not write any file whose filenamenode * has any flag bits set in mask. */ void write_filelist_except(struct pkginfo *pkg, struct fileinlist *list, enum fnnflags mask) { static struct varbuf newvb; const char *listfile; FILE *file; listfile = pkgadminfile(pkg, LISTFILE); varbuf_reset(&newvb); varbuf_add_str(&newvb, listfile); varbuf_add_str(&newvb, NEWDBEXT); varbuf_end_str(&newvb); file= fopen(newvb.buf,"w+"); if (!file) ohshite(_("unable to create updated files list file for package %s"),pkg->name); push_cleanup(cu_closestream, ehflag_bombout, NULL, 0, 1, (void *)file); while (list) { if (!(mask && (list->namenode->flags & mask))) { 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, listfile)) ohshite(_("failed to install updated files list file for package %s"),pkg->name); dir_sync_path(pkgadmindir()); note_must_reread_files_inpackage(pkg); } /* * Initializes 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. */ void reversefilelist_init(struct reversefilelistiter *iterptr, struct fileinlist *files) { struct fileinlist *newent; iterptr->todo = NULL; 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 NULL; ret= todo->namenode; iterptr->todo= todo->next; free(todo); return ret; } /* * 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. */ void reversefilelist_abort(struct reversefilelistiter *iterptr) { while (reversefilelist_next(iterptr)); } struct fileiterator { struct filenamenode *namenode; int nbinn; }; /* This must always be a power of two. If you change it consider changing * the per-character hashing factor (currently 1785 = 137 * 13) too. */ #define BINS (1 << 17) static struct filenamenode *bins[BINS]; struct fileiterator *iterfilestart(void) { struct fileiterator *i; i= m_malloc(sizeof(struct fileiterator)); i->namenode = NULL; i->nbinn= 0; return i; } struct filenamenode *iterfilenext(struct fileiterator *i) { struct filenamenode *r= NULL; while (!i->namenode) { if (i->nbinn >= BINS) return NULL; i->namenode= bins[i->nbinn++]; } r= i->namenode; i->namenode= r->next; return r; } void iterfileend(struct fileiterator *i) { free(i); } void filesdbinit(void) { struct filenamenode *fnn; int i; pkgadmindir_init(); for (i=0; inext) { fnn->flags= 0; fnn->oldhash = NULL; fnn->filestat = NULL; } } static int hash(const char *name) { int v= 0; while (*name) { v *= 1787; v += *name; name++; } return v; } struct filenamenode *findnamenode(const char *name, enum fnnflags flags) { struct filenamenode **pointerp, *newnode; const char *orig_name = name; /* We skip initial slashes and ‘./’ pairs, and add our own single * leading slash. */ name = path_skip_slash_dotslash(name); pointerp= bins + (hash(name) & (BINS-1)); while (*pointerp) { /* XXX: Why is the assert needed? It's checking already added entries. */ assert((*pointerp)->name[0] == '/'); if (!strcmp((*pointerp)->name+1,name)) break; pointerp= &(*pointerp)->next; } if (*pointerp) return *pointerp; if (flags & fnn_nonew) return NULL; newnode= nfmalloc(sizeof(struct filenamenode)); newnode->packages = NULL; if((flags & fnn_nocopy) && name > orig_name && name[-1] == '/') newnode->name = name - 1; else { char *newname= nfmalloc(strlen(name)+2); newname[0]= '/'; strcpy(newname+1,name); newnode->name= newname; } newnode->flags= 0; newnode->next = NULL; newnode->divert = NULL; newnode->statoverride = NULL; newnode->filestat = NULL; newnode->trig_interested = NULL; *pointerp= newnode; nfiles++; return newnode; } /* vi: ts=8 sw=2 */