| /* |
| * libdpkg - Debian packaging suite library routines |
| * dbmodify.c - routines for managing dpkg database updates |
| * |
| * Copyright © 1994,1995 Ian Jackson <ian@chiark.greenend.org.uk> |
| * Copyright © 2001 Wichert Akkerman <wichert@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> |
| |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <time.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| |
| #include <dpkg/i18n.h> |
| #include <dpkg/dpkg.h> |
| #include <dpkg/dpkg-db.h> |
| #include <dpkg/file.h> |
| #include <dpkg/dir.h> |
| #include <dpkg/triglib.h> |
| |
| static bool db_initialized; |
| |
| static enum modstatdb_rw cstatus=-1, cflags=0; |
| static char *lockfile; |
| static char *statusfile, *availablefile; |
| static char *importanttmpfile=NULL; |
| static FILE *importanttmp; |
| static int nextupdate; |
| static char *updatesdir; |
| static int updateslength; |
| static char *updatefnbuf, *updatefnrest; |
| static struct varbuf uvb; |
| |
| static int ulist_select(const struct dirent *de) { |
| const char *p; |
| int l; |
| for (p= de->d_name, l=0; *p; p++, l++) |
| if (!cisdigit(*p)) return 0; |
| if (l > IMPORTANTMAXLEN) |
| ohshit(_("updates directory contains file `%.250s' whose name is too long " |
| "(length=%d, max=%d)"), de->d_name, l, IMPORTANTMAXLEN); |
| if (updateslength == -1) updateslength= l; |
| else if (l != updateslength) |
| ohshit(_("updates directory contains files with different length names " |
| "(both %d and %d)"), l, updateslength); |
| return 1; |
| } |
| |
| static void cleanupdates(void) { |
| struct dirent **cdlist; |
| int cdn, i; |
| |
| parsedb(statusfile, pdb_lax_parser | pdb_weakclassification, NULL); |
| |
| *updatefnrest = '\0'; |
| updateslength= -1; |
| cdn= scandir(updatefnbuf, &cdlist, &ulist_select, alphasort); |
| if (cdn == -1) ohshite(_("cannot scan updates directory `%.255s'"),updatefnbuf); |
| |
| if (cdn) { |
| for (i=0; i<cdn; i++) { |
| strcpy(updatefnrest, cdlist[i]->d_name); |
| parsedb(updatefnbuf, pdb_lax_parser | pdb_weakclassification, |
| NULL); |
| if (cstatus < msdbrw_write) free(cdlist[i]); |
| } |
| |
| if (cstatus >= msdbrw_write) { |
| writedb(statusfile, wdb_must_sync); |
| |
| for (i=0; i<cdn; i++) { |
| strcpy(updatefnrest, cdlist[i]->d_name); |
| if (unlink(updatefnbuf)) |
| ohshite(_("failed to remove incorporated update file %.255s"),updatefnbuf); |
| free(cdlist[i]); |
| } |
| |
| dir_sync_path(updatesdir); |
| } |
| } |
| free(cdlist); |
| |
| nextupdate= 0; |
| } |
| |
| static void createimptmp(void) { |
| int i; |
| |
| onerr_abort++; |
| |
| importanttmp= fopen(importanttmpfile,"w"); |
| if (!importanttmp) |
| ohshite(_("unable to create `%.255s'"), importanttmpfile); |
| setcloexec(fileno(importanttmp),importanttmpfile); |
| for (i=0; i<512; i++) fputs("#padding\n",importanttmp); |
| if (ferror(importanttmp)) |
| ohshite(_("unable to fill %.250s with padding"),importanttmpfile); |
| if (fflush(importanttmp)) |
| ohshite(_("unable to flush %.250s after padding"), importanttmpfile); |
| if (fseek(importanttmp,0,SEEK_SET)) |
| ohshite(_("unable to seek to start of %.250s after padding"), |
| importanttmpfile); |
| |
| onerr_abort--; |
| } |
| |
| static const struct fni { |
| const char *suffix; |
| char **store; |
| } fnis[] = { |
| { LOCKFILE, &lockfile }, |
| { STATUSFILE, &statusfile }, |
| { AVAILFILE, &availablefile }, |
| { UPDATESDIR, &updatesdir }, |
| { UPDATESDIR IMPORTANTTMP, &importanttmpfile }, |
| { NULL, NULL } |
| }; |
| |
| void |
| modstatdb_init(void) |
| { |
| const struct fni *fnip; |
| |
| if (db_initialized) |
| return; |
| |
| for (fnip = fnis; fnip->suffix; fnip++) { |
| free(*fnip->store); |
| *fnip->store = dpkg_db_get_path(fnip->suffix); |
| } |
| |
| updatefnbuf = m_malloc(strlen(updatesdir) + IMPORTANTMAXLEN + 5); |
| strcpy(updatefnbuf, updatesdir); |
| updatefnrest = updatefnbuf + strlen(updatefnbuf); |
| |
| db_initialized = true; |
| } |
| |
| void |
| modstatdb_done(void) |
| { |
| const struct fni *fnip; |
| |
| if (!db_initialized) |
| return; |
| |
| for (fnip = fnis; fnip->suffix; fnip++) { |
| free(*fnip->store); |
| *fnip->store = NULL; |
| } |
| free(updatefnbuf); |
| |
| db_initialized = false; |
| } |
| |
| static int dblockfd = -1; |
| |
| bool |
| modstatdb_is_locked(void) |
| { |
| int lockfd; |
| bool locked; |
| |
| if (dblockfd == -1) { |
| lockfd = open(lockfile, O_RDONLY); |
| if (lockfd == -1) |
| ohshite(_("unable to open lock file %s for testing"), lockfile); |
| } else { |
| lockfd = dblockfd; |
| } |
| |
| locked = file_is_locked(lockfd, lockfile); |
| |
| /* We only close the file if there was no lock open, otherwise we would |
| * release the existing lock on close. */ |
| if (dblockfd == -1) |
| close(lockfd); |
| |
| return locked; |
| } |
| |
| bool |
| modstatdb_can_lock(void) |
| { |
| if (dblockfd >= 0) |
| return true; |
| |
| dblockfd = open(lockfile, O_RDWR | O_CREAT | O_TRUNC, 0660); |
| if (dblockfd == -1) { |
| if (errno == EACCES || errno == EPERM) |
| return false; |
| else |
| ohshite(_("unable to open/create status database lockfile")); |
| } |
| |
| return true; |
| } |
| |
| void |
| modstatdb_lock(void) |
| { |
| if (!modstatdb_can_lock()) |
| ohshit(_("you do not have permission to lock the dpkg status database")); |
| |
| file_lock(&dblockfd, FILE_LOCK_NOWAIT, lockfile, _("dpkg status database")); |
| } |
| |
| void |
| modstatdb_unlock(void) |
| { |
| /* Unlock. */ |
| pop_cleanup(ehflag_normaltidy); |
| |
| dblockfd = -1; |
| } |
| |
| enum modstatdb_rw |
| modstatdb_open(enum modstatdb_rw readwritereq) |
| { |
| modstatdb_init(); |
| |
| cflags = readwritereq & msdbrw_available_mask; |
| readwritereq &= ~msdbrw_available_mask; |
| |
| switch (readwritereq) { |
| case msdbrw_needsuperuser: |
| case msdbrw_needsuperuserlockonly: |
| if (getuid() || geteuid()) |
| ohshit(_("requested operation requires superuser privilege")); |
| /* Fall through. */ |
| case msdbrw_write: case msdbrw_writeifposs: |
| if (access(dpkg_db_get_dir(), W_OK)) { |
| if (errno != EACCES) |
| ohshite(_("unable to access dpkg status area")); |
| else if (readwritereq == msdbrw_write) |
| ohshit(_("operation requires read/write access to dpkg status area")); |
| cstatus= msdbrw_readonly; |
| } else { |
| modstatdb_lock(); |
| cstatus= (readwritereq == msdbrw_needsuperuserlockonly ? |
| msdbrw_needsuperuserlockonly : |
| msdbrw_write); |
| } |
| break; |
| case msdbrw_readonly: |
| cstatus= msdbrw_readonly; break; |
| default: |
| internerr("unknown modstatdb_rw '%d'", readwritereq); |
| } |
| |
| if (cstatus != msdbrw_needsuperuserlockonly) { |
| cleanupdates(); |
| if (cflags >= msdbrw_available_readonly) |
| parsedb(availablefile, |
| pdb_recordavailable | pdb_rejectstatus | pdb_lax_parser, |
| NULL); |
| } |
| |
| if (cstatus >= msdbrw_write) { |
| createimptmp(); |
| varbuf_init(&uvb, 10240); |
| } |
| |
| trig_fixup_awaiters(cstatus); |
| trig_incorporate(cstatus); |
| |
| return cstatus; |
| } |
| |
| void modstatdb_checkpoint(void) { |
| int i; |
| |
| assert(cstatus >= msdbrw_write); |
| writedb(statusfile, wdb_must_sync); |
| |
| for (i=0; i<nextupdate; i++) { |
| sprintf(updatefnrest, IMPORTANTFMT, i); |
| /* Have we made a real mess? */ |
| assert(strlen(updatefnrest) <= IMPORTANTMAXLEN); |
| if (unlink(updatefnbuf)) |
| ohshite(_("failed to remove my own update file %.255s"),updatefnbuf); |
| } |
| |
| dir_sync_path(updatesdir); |
| |
| nextupdate= 0; |
| } |
| |
| void modstatdb_shutdown(void) { |
| if (cflags >= msdbrw_available_write) |
| writedb(availablefile, wdb_dump_available); |
| |
| switch (cstatus) { |
| case msdbrw_write: |
| modstatdb_checkpoint(); |
| /* Tidy up a bit, but don't worry too much about failure. */ |
| fclose(importanttmp); |
| unlink(importanttmpfile); |
| varbuf_destroy(&uvb); |
| /* Fall through. */ |
| case msdbrw_needsuperuserlockonly: |
| modstatdb_unlock(); |
| default: |
| break; |
| } |
| |
| modstatdb_done(); |
| } |
| |
| static void |
| modstatdb_note_core(struct pkginfo *pkg) |
| { |
| assert(cstatus >= msdbrw_write); |
| |
| varbuf_reset(&uvb); |
| varbufrecord(&uvb, pkg, &pkg->installed); |
| |
| if (fwrite(uvb.buf, 1, uvb.used, importanttmp) != uvb.used) |
| ohshite(_("unable to write updated status of `%.250s'"), pkg->name); |
| if (fflush(importanttmp)) |
| ohshite(_("unable to flush updated status of `%.250s'"), pkg->name); |
| if (ftruncate(fileno(importanttmp), uvb.used)) |
| ohshite(_("unable to truncate for updated status of `%.250s'"), pkg->name); |
| if (fsync(fileno(importanttmp))) |
| ohshite(_("unable to fsync updated status of `%.250s'"), pkg->name); |
| if (fclose(importanttmp)) |
| ohshite(_("unable to close updated status of `%.250s'"), pkg->name); |
| sprintf(updatefnrest, IMPORTANTFMT, nextupdate); |
| if (rename(importanttmpfile, updatefnbuf)) |
| ohshite(_("unable to install updated status of `%.250s'"), pkg->name); |
| |
| dir_sync_path(updatesdir); |
| |
| /* Have we made a real mess? */ |
| assert(strlen(updatefnrest) <= IMPORTANTMAXLEN); |
| |
| nextupdate++; |
| |
| if (nextupdate > MAXUPDATES) { |
| modstatdb_checkpoint(); |
| nextupdate = 0; |
| } |
| |
| createimptmp(); |
| } |
| |
| /* |
| * Note: If anyone wants to set some triggers-pending, they must also |
| * set status appropriately, or we will undo it. That is, it is legal |
| * to call this when pkg->status and pkg->trigpend_head disagree and |
| * in that case pkg->status takes precedence and pkg->trigpend_head |
| * will be adjusted. |
| */ |
| void modstatdb_note(struct pkginfo *pkg) { |
| struct trigaw *ta; |
| |
| onerr_abort++; |
| |
| /* Clear pending triggers here so that only code that sets the status |
| * to interesting (for triggers) values has to care about triggers. */ |
| if (pkg->status != stat_triggerspending && |
| pkg->status != stat_triggersawaited) |
| pkg->trigpend_head = NULL; |
| |
| if (pkg->status <= stat_configfiles) { |
| for (ta = pkg->trigaw.head; ta; ta = ta->sameaw.next) |
| ta->aw = NULL; |
| pkg->trigaw.head = pkg->trigaw.tail = NULL; |
| } |
| |
| log_message("status %s %s %s", statusinfos[pkg->status].name, pkg->name, |
| versiondescribe(&pkg->installed.version, vdew_nonambig)); |
| statusfd_send("status: %s: %s", pkg->name, statusinfos[pkg->status].name); |
| |
| if (cstatus >= msdbrw_write) |
| modstatdb_note_core(pkg); |
| |
| if (!pkg->trigpend_head && pkg->othertrigaw_head) { |
| /* Automatically remove us from other packages' Triggers-Awaited. |
| * We do this last because we want to maximize our chances of |
| * successfully recording the status of the package we were |
| * pointed at by our caller, although there is some risk of |
| * leaving us in a slightly odd situation which is cleared up |
| * by the trigger handling logic in deppossi_ok_found. */ |
| trig_clear_awaiters(pkg); |
| } |
| |
| onerr_abort--; |
| } |
| |
| void |
| modstatdb_note_ifwrite(struct pkginfo *pkg) |
| { |
| if (cstatus >= msdbrw_write) |
| modstatdb_note(pkg); |
| } |
| |