blob: 990a488e73791af0453b8aaeaa88b6ad76cf746d [file] [log] [blame]
/*
* dpkg - main program for package management
* filesdb.c - management of database of files installed on system
*
* Copyright © 1995 Ian Jackson <ian@chiark.greenend.org.uk>
* Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <compat.h>
#ifdef HAVE_LINUX_FIEMAP_H
#include <linux/fiemap.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <dpkg/i18n.h>
#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>
#include <dpkg/path.h>
#include <dpkg/dir.h>
#include <dpkg/fdio.h>
#include <dpkg/pkg-array.h>
#include <dpkg/progress.h>
#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_char(&vb, '/');
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; i<BINS; i++)
for (fnn= bins[i]; fnn; fnn= fnn->next) {
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
*/