pkgdbmerg.c revision 62224350e5355e6834f7deb9d8a7d062a50cb7c2
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <pkgstrct.h>
#include <sys/stat.h>
#include <locale.h>
#include <libintl.h>
#include <pkginfo.h>
#include <instzones_api.h>
#include <pkglib.h>
#include <libinst.h>
#include <messages.h>
/* merg() return codes */
#define MRG_SAME 0
#define MRG_DIFFERENT 1
#define MRG_REPLACE 2
/* typechg() return codes */
#define TYPE_OK 0
#define TYPE_WARNING 1
#define TYPE_IGNORED 2
#define TYPE_REPLACE 3
#define TYPE_FATAL 4
/* message pool */
#define ERR_OUTPUT "unable to update package database"
#define ERR_PINFO "missing pinfo structure for <%s>"
#define INFO_PROCESS " %2ld%% of information processed; continuing ..."
#define WRN_NOTFILE "WARNING: %s <no longer a regular file>"
#define WRN_NOTSYMLN "WARNING: %s <no longer a symbolic link>"
#define WRN_NOTLINK "WARNING: %s <no longer a linked file>"
#define WRN_NOTDIR "WARNING: %s <no longer a directory>"
#define WRN_NOTCHAR "WARNING: %s <no longer a character special device>"
#define WRN_NOTBLOCK "WARNING: %s <no longer a block special device>"
#define WRN_NOTPIPE "WARNING: %s <no longer a named pipe>"
#define WRN_TOEXCL "WARNING: cannot convert %s to an exclusive directory."
#define WRN_ODDVERIFY "WARNING: quick verify disabled for class %s."
#define MSG_TYPIGN "Object type change ignored."
#define MSG_TYPE_ERR "Package attempts fatal object type change."
extern char *pkginst;
extern int nosetuid, nocnflct, otherstoo;
/* pkgobjmap.c */
extern int cp_cfent(struct cfent *cf_ent, struct cfextra *el_ent);
/* setlist.c */
extern void cl_def_dverify(int idx);
char dbst = '\0'; /* usually set by installf() or removef() */
int files_installed(void); /* return number of files installed. */
static int errflg = 0;
static int eptnum;
static int installed; /* # of files, already properly installed. */
static struct pinfo *pkgpinfo = (struct pinfo *)0;
static int is_setuid(struct cfent *ent);
static int is_setgid(struct cfent *ent);
static int merg(struct cfextra *el_ent, struct cfent *cf_ent);
static int do_like_ent(VFP_T *vfpo, struct cfextra *el_ent,
struct cfent *cf_ent, int ctrl);
static int do_new_ent(VFP_T *vfpo, struct cfextra *el_ent, int ctrl);
static int typechg(struct cfent *el_ent, struct cfent *cf_ent,
struct mergstat *mstat);
static void set_change(struct cfextra *el_ent);
static void chgclass(struct cfent *cf_ent, struct pinfo *pinfo);
static void output(VFP_T *vfpo, struct cfent *ent, struct pinfo *pinfo);
/*
* This scans the extlist (pkgmap) and matches them to the database, copying
* out the modified contents to the file at tmpfp. It updates the mergstat
* structures and deals with administrative defaults regarding setuid and
* conflict.
*/
int
pkgdbmerg(PKGserver server, VFP_T *tmpvfp, struct cfextra **extlist)
{
static struct cfent cf_ent; /* scratch area */
struct cfextra *el_ent; /* extlist entry under review */
int n;
int changed;
int assume_ok = 0;
cf_ent.pinfo = (NULL);
errflg = 0;
installed = changed = 0;
vfpRewind(tmpvfp);
for (eptnum = 0; (el_ent = extlist[eptnum]) != NULL; eptnum++) {
/*
* If there's an entry in the extlist at this position,
* process that entry.
*/
/* Metafiles don't get merged. */
if ((el_ent->cf_ent.ftype == 'i') ||
(el_ent->cf_ent.ftype == 'n')) {
continue;
}
/*
* Copy cfextra structure for duplicated paths.
* This is not just an optimization, it is
* necessary for correct operation of algorithm.
*/
if ((eptnum > 0) && (strncmp(el_ent->cf_ent.path,
extlist[eptnum-1]->cf_ent.path, PATH_MAX) == 0)) {
memcpy(extlist[eptnum], extlist[eptnum-1],
sizeof (struct cfextra));
continue;
}
/*
* Normally dbst comes to us from installf() or
* removef() in order to specify their special
* database status codes. They cannot implement a
* quick verify (it just doesn't make sense). For
* that reason, we can test to see if we already have
* a special database status. If we don't (it's from
* pkgadd) then we can test to see if this is calling
* for a quick verify wherein we assume the install
* will work and fix it if it doesn't. In that case
* we set our own dbst to be ENTRY_OK.
*/
if (dbst == '\0') {
if (cl_dvfy(el_ent->cf_ent.pkg_class_idx) ==
QKVERIFY) {
assume_ok = 1;
}
} else {
/*
* If we DO end up with an installf/quick
* verify combination, we fix that by simply
* denying the quick verify for this class.
* This forces everything to come out alright
* by forcing the standard assumptions as
* regards package database for the rest of
* the load.
*/
if (cl_dvfy(el_ent->cf_ent.pkg_class_idx) ==
QKVERIFY) {
logerr(gettext(WRN_ODDVERIFY),
cl_nam(el_ent->cf_ent.pkg_class_idx));
/*
* Set destination verification to
* default.
*/
cl_def_dverify(el_ent->cf_ent.pkg_class_idx);
}
}
/*
* Comply with administrative requirements regarding
* setuid/setgid processes.
*/
if (is_setuid(&(el_ent->cf_ent))) {
el_ent->mstat.setuid = 1;
}
if (is_setgid(&(el_ent->cf_ent))) {
el_ent->mstat.setgid = 1;
}
/*
* If setuid/setgid processes are not allowed, reset
* those bits.
*/
if (nosetuid && (el_ent->mstat.setgid ||
el_ent->mstat.setuid)) {
el_ent->cf_ent.ainfo.mode &= ~(S_ISUID | S_ISGID);
}
/* Search package database for this entry. */
n = srchcfile(&cf_ent, el_ent->cf_ent.path, server);
/*
* If there was an error, note it and return an error
* flag.
*/
if (n < 0) {
char *errstr = getErrstr();
progerr(ERR_CFBAD);
logerr(gettext("pathname: %s"),
(cf_ent.path && *cf_ent.path) ?
cf_ent.path : "Unknown");
logerr(gettext("problem: %s"),
(errstr && *errstr) ? errstr : "Unknown");
return (-1);
/*
* If there was a match, then merge them into a
* single entry.
*/
} else if (n == 1) {
/*
* If this package is overwriting a setuid or
* setgid process, set the status bits so we
* can inform the administrator.
*/
if (is_setuid(&cf_ent)) {
el_ent->mstat.osetuid = 1;
}
if (is_setgid(&cf_ent)) {
el_ent->mstat.osetgid = 1;
}
/*
* Detect if a symlink has changed to directory
* If so mark all the files/dir supposed to be
* iniside this dir, so that they are not miss
* understood by do_new_ent later as already
* installed.
*/
if ((cf_ent.ftype == 's') &&
(el_ent->cf_ent.ftype == 'd')) {
int i;
int plen = strlen(el_ent->cf_ent.path);
for (i = eptnum + 1; extlist[i]; i++) {
if (strncmp(el_ent->cf_ent.path,
extlist[i]->cf_ent.path,
plen) != 0)
break;
extlist[i]->mstat.parentsyml2dir
= 1;
}
}
if (do_like_ent(tmpvfp, el_ent, &cf_ent, assume_ok)) {
changed++;
}
} else {
/*
* The file doesn't exist in the database.
*/
if (do_new_ent(tmpvfp, el_ent, assume_ok)) {
changed++;
}
}
}
return (errflg ? -1 : changed);
}
/*
* Merge a new entry with an installed package object of the same name and
* insert that object into the package database. Obey administrative defaults
* as regards conflicting files.
*/
static int
do_like_ent(VFP_T *vfpo, struct cfextra *el_ent, struct cfent *cf_ent, int ctrl)
{
int stflag, ignore, changed, mrg_result;
ignore = changed = 0;
/*
* Construct the record defining the current package. If there are
* other packages involved, this will be appended to the existing
* list. If this is an update of the same package, it will get merged
* with the existing record. If this is a preloaded record (like from
* a dryrun file), it will keep it's current pinfo pointer and will
* pass it on to the record from the contents file - because on the
* final continuation, the contents file will be wrong.
*/
if (el_ent->mstat.preloaded) {
struct pinfo *pkginfo;
/* Contents file is not to be trusted for this list. */
pkginfo = cf_ent->pinfo;
/* Free the potentially bogus list. */
while (pkginfo) {
struct pinfo *next;
next = pkginfo->next;
free(pkginfo);
pkginfo = next;
}
cf_ent->pinfo = el_ent->cf_ent.pinfo;
}
pkgpinfo = eptstat(cf_ent, pkginst, DUP_ENTRY);
stflag = pkgpinfo->status;
if (otherstoo)
el_ent->mstat.shared = 1;
/* If it's marked for erasure, make it official */
if (el_ent->cf_ent.ftype == RM_RDY) {
if (!errflg) {
pkgpinfo = eptstat(cf_ent, pkginst, RM_RDY);
/*
* Get copy of status character in case the object is
* "shared" by a server, in which case we need to
* maintain the shared status after the entry is
* written to the package database with RM_RDY
* status. This is needed to support the `removef'
* command.
*/
stflag = pkgpinfo->status;
pkgpinfo->status = RM_RDY;
if (putcvfpfile(cf_ent, vfpo)) {
progerr(gettext(ERR_OUTPUT));
quit(99);
}
/*
* If object is provided by a server, allocate an
* info block and set the status to indicate this.
* This is needed to support the `removef' command.
*/
if (stflag == SERVED_FILE) {
el_ent->cf_ent.pinfo =
(struct pinfo *)calloc(1,
sizeof (struct pinfo));
el_ent->cf_ent.pinfo->next = NULL;
el_ent->cf_ent.pinfo->status = SERVED_FILE;
}
}
return (1);
}
/*
* If there is no package associated with it, there's something
* very wrong.
*/
if (!pkgpinfo) {
progerr(gettext(ERR_PINFO), cf_ent->path);
quit(99);
}
/*
* Do not allow installation if nocnflct is set and other packages
* reference this pathname. The cp_cfent() function below writes the
* information from the installed file over the new entry, so the
* package database will be unchanged.
*
* By the way, ftype "e" is often shared and that's OK, so ftype
* "e" doesn't count here.
*/
if ((nocnflct && el_ent->mstat.shared && el_ent->cf_ent.ftype != 'e')) {
/*
* First set the attrchg and contchg entries for proper
* messaging in the install phase.
*/
set_change(el_ent);
/*
* Now overwrite the new entry with the entry for the
* currently installed object.
*/
if (cp_cfent(cf_ent, el_ent) == 0)
quit(99);
ignore++;
} else {
mrg_result = merg(el_ent, cf_ent);
switch (mrg_result) {
case MRG_SAME:
break;
case MRG_DIFFERENT:
changed++;
break;
case MRG_REPLACE:
/*
* We'll pick one or the other later. For now, cf_ent
* will have the fault value and el_ent will retain
* the other value. This is the only state that allows
* the database and the pkgmap to differ.
*/
el_ent->mstat.contchg = 1; /* subject to change */
ignore++;
break;
default:
break;
}
}
/* el_ent structure now contains updated entry */
if (!el_ent->mstat.contchg && !ignore) {
/*
* We know the DB entry matches the pkgmap, so now we need to
* see if the actual object matches the pkgmap.
*/
set_change(el_ent);
}
if (!errflg) {
if (ctrl == 1) { /* quick verify assumes OK */
/*
* The pkgpinfo entry is already correctly
* constructed. Look into dropping this soon.
*/
pkgpinfo = eptstat(&(el_ent->cf_ent), pkginst,
ENTRY_OK);
if (stflag != DUP_ENTRY) {
changed++;
}
/*
* We could trust the prior pkginfo entry, but things
* could have changed and we need to update the
* fs_tab[] anyway. We check for a server object
* here.
*/
if (is_served(el_ent->server_path,
&(el_ent->fsys_value)))
pkgpinfo->status = SERVED_FILE;
} else {
if (!ignore && el_ent->mstat.contchg) {
pkgpinfo =
eptstat(&(el_ent->cf_ent), pkginst,
(dbst ? dbst : CONFIRM_CONT));
} else if (!ignore && el_ent->mstat.attrchg) {
pkgpinfo =
eptstat(&(el_ent->cf_ent), pkginst,
(dbst ? dbst : CONFIRM_ATTR));
} else if (!ignore && el_ent->mstat.shared) {
pkgpinfo =
eptstat(&(el_ent->cf_ent), pkginst,
dbst);
changed++;
} else if (stflag != DUP_ENTRY) {
pkgpinfo = eptstat(&(el_ent->cf_ent),
pkginst, '\0');
if (stflag != ENTRY_OK) {
changed++;
}
}
}
if (mrg_result == MRG_REPLACE) {
/*
* Put the original package database entry back into
* the package database for now.
*/
output(vfpo, cf_ent, pkgpinfo);
} else {
/* Put the merged entry into the package database. */
output(vfpo, &(el_ent->cf_ent), pkgpinfo);
}
}
if (pkgpinfo->aclass[0] != '\0') {
(void) strcpy(el_ent->cf_ent.pkg_class, pkgpinfo->aclass);
}
/*
* If a sym link entry exists in the contents file and
* and the destination of the link does not exist on the the system
* then the contents file needs to be updated appropriately so a
* subsequent invocation of "installf -f" will create the destination.
*/
if (el_ent->mstat.contchg && pkgpinfo->status == INST_RDY) {
changed++;
}
if (!(el_ent->mstat.preloaded))
el_ent->cf_ent.pinfo = NULL;
/*
* If no change during the merg and we don't have a case where types
* were different in odd ways, count this as installed.
*/
if (!el_ent->mstat.attrchg && !el_ent->mstat.contchg &&
!el_ent->mstat.replace)
installed++;
return (changed);
}
/* Insert an entirely new entry into the package database. */
static int
do_new_ent(VFP_T *vfpo, struct cfextra *el_ent, int ctrl)
{
struct pinfo *pinfo;
char *tp;
int changed = 0;
if (el_ent->cf_ent.ftype == RM_RDY) {
return (0);
}
tp = el_ent->server_path;
/*
* Check the file/dir existence only if any of the parent directory
* of the file/dir has not changed from symbolic link to directory.
* At this time we are only doing a dry run, the symlink is not yet
* replaced, so if this is done directly then access will result in
* incorrect information in case a file with the same attr and cont
* exists in the link target.
*/
if ((!el_ent->mstat.parentsyml2dir) && (access(tp, F_OK) == 0)) {
/*
* Path exists, and although its not referenced by any
* package we make it look like it is so it appears as a
* conflicting file in case the user doesn't want it
* installed. We set the rogue flag to distinguish this from
* package object conflicts if the administrator is queried
* about this later. Note that noconflict means NO conflict
* at the file level. Even rogue files count.
*/
el_ent->mstat.shared = 1;
el_ent->mstat.rogue = 1;
set_change(el_ent);
} else {
/* since path doesn't exist, we're changing everything */
el_ent->mstat.rogue = 0;
el_ent->mstat.contchg = 1;
el_ent->mstat.attrchg = 1;
}
if (el_ent->cf_ent.ainfo.mode == WILDCARD) {
if (el_ent->cf_ent.ftype == 'd') {
el_ent->cf_ent.ainfo.mode = DEFAULT_MODE;
} else {
el_ent->cf_ent.ainfo.mode = DEFAULT_MODE_FILE;
}
logerr(WRN_SET_DEF_MODE, el_ent->cf_ent.path,
(int)el_ent->cf_ent.ainfo.mode);
}
if (strcmp(el_ent->cf_ent.ainfo.owner, DB_UNDEFINED_ENTRY) == 0)
(void) strcpy(el_ent->cf_ent.ainfo.owner,
DEFAULT_OWNER);
if (strcmp(el_ent->cf_ent.ainfo.group, DB_UNDEFINED_ENTRY) == 0)
(void) strcpy(el_ent->cf_ent.ainfo.group,
DEFAULT_GROUP);
/*
* Do not allow installation if nocnflct is set and this pathname is
* already in place. Since this entry is new (not associated with a
* package), we don't issue anything to the database we're building.
*/
if (nocnflct && el_ent->mstat.shared) {
return (0);
}
if (!errflg) {
if (el_ent->mstat.preloaded) {
/* Add this package to the already established list. */
pinfo = eptstat(&(el_ent->cf_ent), pkginst, DUP_ENTRY);
} else {
el_ent->cf_ent.npkgs = 1;
pinfo = (struct pinfo *)calloc(1,
sizeof (struct pinfo));
if (!pinfo) {
progerr(gettext(ERR_MEMORY), errno);
quit(99);
}
el_ent->cf_ent.pinfo = pinfo;
(void) strcpy(pinfo->pkg, pkginst);
}
if (ctrl == 1) { /* quick verify assumes OK */
pinfo->status = dbst ? dbst : ENTRY_OK;
/*
* The entry won't be verified, but the entry in the
* database isn't necessarily ENTRY_OK. If this is
* coming from a server, we need to note that
* instead.
*/
if (is_served(el_ent->server_path,
&(el_ent->fsys_value)))
pinfo->status = SERVED_FILE;
} else {
pinfo->status = dbst ? dbst : CONFIRM_CONT;
}
output(vfpo, &(el_ent->cf_ent), pinfo);
changed++;
free(pinfo);
el_ent->cf_ent.pinfo = NULL;
}
if (!el_ent->mstat.attrchg && !el_ent->mstat.contchg) {
installed++;
}
return (changed);
}
int
files_installed(void)
{
return (installed);
}
/*
* This function determines if there is a difference between the file on
* the disk and the file to be laid down. It set's mstat flags attrchg
* and contchg accordingly.
*/
static void
set_change(struct cfextra *el_ent)
{
int n;
char *tp;
tp = el_ent->server_path;
if ((el_ent->cf_ent.ftype == 'f') || (el_ent->cf_ent.ftype == 'e') ||
(el_ent->cf_ent.ftype == 'v')) {
if (cverify(0, &(el_ent->cf_ent.ftype), tp,
&(el_ent->cf_ent.cinfo), 1)) {
el_ent->mstat.contchg = 1;
} else if (!el_ent->mstat.contchg && !el_ent->mstat.attrchg) {
if (averify(0, &(el_ent->cf_ent.ftype), tp,
&(el_ent->cf_ent.ainfo)))
el_ent->mstat.attrchg = 1;
}
} else if (!el_ent->mstat.attrchg &&
((el_ent->cf_ent.ftype == 'd') ||
(el_ent->cf_ent.ftype == 'x') ||
(el_ent->cf_ent.ftype == 'c') ||
(el_ent->cf_ent.ftype == 'b') ||
(el_ent->cf_ent.ftype == 'p'))) {
n = averify(0, &(el_ent->cf_ent.ftype), tp,
&(el_ent->cf_ent.ainfo));
if (n == VE_ATTR)
el_ent->mstat.attrchg = 1;
else if (n && (n != VE_EXIST)) {
el_ent->mstat.contchg = 1;
}
} else if (!el_ent->mstat.attrchg &&
((el_ent->cf_ent.ftype == 's') ||
(el_ent->cf_ent.ftype == 'l'))) {
n = averify(0, &(el_ent->cf_ent.ftype), tp,
&(el_ent->cf_ent.ainfo));
if (n == VE_ATTR)
el_ent->mstat.attrchg = 1;
else if (n && (n == VE_EXIST)) {
el_ent->mstat.contchg = 1;
}
}
}
static int
is_setuid(struct cfent *ent)
{
return (((ent->ftype == 'f') || (ent->ftype == 'v') ||
(ent->ftype == 'e')) &&
(ent->ainfo.mode != BADMODE) &&
(ent->ainfo.mode != WILDCARD) &&
(ent->ainfo.mode & S_ISUID));
}
static int
is_setgid(struct cfent *ent)
{
return (((ent->ftype == 'f') || (ent->ftype == 'v') ||
(ent->ftype == 'e')) && (ent->ainfo.mode != BADMODE) &&
(ent->ainfo.mode != WILDCARD) &&
(ent->ainfo.mode & S_ISGID) &&
(ent->ainfo.mode & (S_IEXEC|S_IXUSR|S_IXOTH)));
}
char *types[] = {
"fev", /* type 1, regular files */
"s", /* type 2, symbolic links */
"l", /* type 3, linked files */
"dx", /* type 4, directories */
"c", /* type 5, character special devices */
"b", /* type 6, block special devices */
"p", /* type 7, named pipes */
NULL
};
/*
* This determines if the ftype of the file on the disk and the file to be
* laid down are close enough. If they aren't, this either returns an error
* or displays a warning. This returns :
* TYPE_OK they're identical or close enough
* TYPE_WARNING they're pretty close (probably no problem)
* TYPE_IGNORED the type change was not allowed
* TYPE_REPLACE to be reviewed later - in endofclass() maybe
* TYPE_FATAL something awful happened
*/
static int
typechg(struct cfent *el_ent, struct cfent *cf_ent, struct mergstat *mstat)
{
int i, etype, itype, retcode;
/* If they are identical, return OK */
if (cf_ent->ftype == el_ent->ftype)
return (TYPE_OK);
/*
* If package database entry is ambiguous, set it to the new entity's
* ftype
*/
if (cf_ent->ftype == BADFTYPE) {
cf_ent->ftype = el_ent->ftype;
return (TYPE_OK); /* do nothing; not really different */
}
/* If the new entity is ambiguous, wait for the verify */
if (el_ent->ftype == BADFTYPE)
return (TYPE_OK);
/*
* If we're trying to convert an existing regular directory to an
* exclusive directory, this is very dangerous. We will continue, but
* we will deny the conversion.
*/
if (el_ent->ftype == 'x' && cf_ent->ftype == 'd') {
logerr(gettext(WRN_TOEXCL), el_ent->path);
return (TYPE_IGNORED);
}
etype = itype = 0;
/* Set etype to that of the new entity */
for (i = 0; types[i]; ++i) {
if (strchr(types[i], el_ent->ftype)) {
etype = i+1;
break;
}
}
/* Set itype to that in the package database. */
for (i = 0; types[i]; ++i) {
if (strchr(types[i], cf_ent->ftype)) {
itype = i+1;
break;
}
}
if (itype == etype) {
/* same basic object type */
return (TYPE_OK);
}
retcode = TYPE_WARNING;
/*
* If a simple object (like a file) is overwriting a directory, mark
* it for full inspection during installation.
*/
if (etype != 4 && itype == 4) {
mstat->dir2nondir = 1;
retcode = TYPE_REPLACE;
}
/* allow change, but warn user of possible problems */
switch (itype) {
case 1:
logerr(gettext(WRN_NOTFILE), el_ent->path);
break;
case 2:
logerr(gettext(WRN_NOTSYMLN), el_ent->path);
break;
case 3:
logerr(gettext(WRN_NOTLINK), el_ent->path);
break;
case 4:
logerr(gettext(WRN_NOTDIR), el_ent->path);
break;
case 5:
logerr(gettext(WRN_NOTCHAR), el_ent->path);
break;
case 6:
logerr(gettext(WRN_NOTBLOCK), el_ent->path);
break;
case 7:
logerr(gettext(WRN_NOTPIPE), el_ent->path);
break;
default:
break;
}
return (retcode);
}
/*
* This function takes el_ent (the entry from the pkgmap) and cf_ent (the
* entry from the package database) and merge them into el_ent. The rules
* are still being figured out, but the comments should make the approach
* pretty clear.
*
* RETURN CODES:
* MRG_DIFFERENT The two entries are different and el_ent now contains
* the intended new entry to be installed.
* MRG_SAME The two entries were identical and the old database
* entry will be replaced unchanged.
* MRG_REPLACE One or the other entry will be used but the decision
* has to be made at install time.
*/
static int
merg(struct cfextra *el_ent, struct cfent *cf_ent)
{
int n, changed = 0;
/*
* We need to change the original entry to make it look like the new
* entry (the eptstat() routine has already added appropriate package
* information, but not about 'aclass' which may represent a change
* in class from the previous installation.
*
* NOTE: elent->cf_ent.pinfo (the list of associated packages) is NULL
* upon entry to this function.
*/
el_ent->cf_ent.pinfo = cf_ent->pinfo;
if (dbst == INST_RDY && el_ent->cf_ent.ftype == '?') {
el_ent->cf_ent.ftype = cf_ent->ftype;
}
/*
* Evaluate the ftype change. Usually the ftype won't change. If it
* does it may be easy (s -> f), not allowed (d -> x), so complex we
* can't figure it 'til later (d -> s) or fatal (a hook for later).
*/
if (cf_ent->ftype != el_ent->cf_ent.ftype) {
n = typechg(&(el_ent->cf_ent), cf_ent, &(el_ent->mstat));
switch (n) {
case TYPE_OK:
break;
/* This is an allowable change. */
case TYPE_WARNING:
el_ent->mstat.contchg = 1;
break;
/* Not allowed, but leaving it as is is OK. */
case TYPE_IGNORED:
logerr(gettext(MSG_TYPIGN));
if (cp_cfent(cf_ent, el_ent) == 0)
quit(99);
return (MRG_SAME);
/* Future analysis will reveal if this is OK. */
case TYPE_REPLACE:
el_ent->mstat.replace = 1;
return (MRG_REPLACE);
/* Kill it before it does any damage. */
case TYPE_FATAL:
logerr(gettext(MSG_TYPE_ERR));
quit(99);
default:
break;
}
changed++;
}
/* Evaluate and merge the class. */
if (strcmp(cf_ent->pkg_class, el_ent->cf_ent.pkg_class)) {
/*
* we always allow a class change as long as we have
* consistent ftypes, which at this point we must
*/
changed++;
if (strcmp(cf_ent->pkg_class, "?")) {
(void) strcpy(pkgpinfo->aclass,
el_ent->cf_ent.pkg_class);
(void) strcpy(el_ent->cf_ent.pkg_class,
cf_ent->pkg_class);
chgclass(&(el_ent->cf_ent), pkgpinfo);
}
}
/*
* Evaluate and merge based upon the ftype of the intended package
* database entry.
*/
if (((el_ent->cf_ent.ftype == 's') || (el_ent->cf_ent.ftype == 'l'))) {
/* If both have link sources, then they need to be merged. */
if (cf_ent->ainfo.local && el_ent->cf_ent.ainfo.local) {
/*
* If both sources are identical, the merge is
* already done.
*/
if (strcmp(cf_ent->ainfo.local,
el_ent->cf_ent.ainfo.local) != NULL) {
changed++;
/*
* Otherwise, if the pkgmap entry is
* ambiguous, it will inherit the database
* entry.
*/
if (strcmp(el_ent->cf_ent.ainfo.local,
"?") == NULL) {
(void) strlcpy(
el_ent->cf_ent.ainfo.local,
cf_ent->ainfo.local,
PATH_MAX);
} else {
el_ent->mstat.contchg = 1;
}
}
}
return (changed ? MRG_DIFFERENT : MRG_SAME);
} else if (el_ent->cf_ent.ftype == 'e') {
/*
* The contents of edittable files are assumed to be changing
* since some class action script will be doing the work and
* we have no way of evaluating what it will actually do.
*/
el_ent->mstat.contchg = 1;
changed++;
} else if (((el_ent->cf_ent.ftype == 'f') ||
(el_ent->cf_ent.ftype == 'v'))) {
/*
* For regular files, Look at content information; a BADCONT
* in any el_ent field indicates the contents are unknown --
* since cf_ent is guaranteed to have a valid entry here (bad
* assumption?) this function will recognize this as a
* change. The ambiguous el_ent values will be evaluated and
* set later.
*/
/*
* for type f/v files, if the file is in an area that is
* inherited from the global zone, that area is read only
* and the object cannot be changed - ignore any settings
* in the current package database that may be present for
* any existing object because they are irrelevant - since
* the object is in a read-only area shared from the global
* zone, accept that file's actual attributes as being correct.
*/
if (z_path_is_inherited(el_ent->cf_ent.path,
el_ent->cf_ent.ftype, get_inst_root()) == B_TRUE) {
echoDebug(DBG_PKGDBMRG_INHERITED, el_ent->cf_ent.path);
} else if (cf_ent->cinfo.size != el_ent->cf_ent.cinfo.size) {
changed++;
el_ent->mstat.contchg = 1;
} else if (cf_ent->cinfo.modtime !=
el_ent->cf_ent.cinfo.modtime) {
changed++;
el_ent->mstat.contchg = 1;
} else if (cf_ent->cinfo.cksum != el_ent->cf_ent.cinfo.cksum) {
changed++;
el_ent->mstat.contchg = 1;
}
} else if (((el_ent->cf_ent.ftype == 'c') ||
(el_ent->cf_ent.ftype == 'b'))) {
/*
* For devices, if major or minor numbers are identical the
* merge is trivial. If the el_ent value is ambiguous (BAD),
* the cf_ent value is inherited. Otherwise, the el_ent value
* is preserved.
*/
if (cf_ent->ainfo.major != el_ent->cf_ent.ainfo.major) {
changed++;
if (el_ent->cf_ent.ainfo.major == BADMAJOR) {
el_ent->cf_ent.ainfo.major =
cf_ent->ainfo.major;
} else {
el_ent->mstat.contchg = 1;
}
}
if (cf_ent->ainfo.minor != el_ent->cf_ent.ainfo.minor) {
changed++;
if (el_ent->cf_ent.ainfo.minor == BADMINOR)
el_ent->cf_ent.ainfo.minor =
cf_ent->ainfo.minor;
else
el_ent->mstat.contchg = 1;
}
}
/*
* For mode, owner and group follow the same rules as above - if
* ambiguous, inherit, otherwise keep the new one.
*/
if (cf_ent->ainfo.mode != el_ent->cf_ent.ainfo.mode) {
changed++; /* attribute info is changing */
if (el_ent->cf_ent.ainfo.mode == BADMODE) {
el_ent->cf_ent.ainfo.mode = cf_ent->ainfo.mode;
} else if (el_ent->cf_ent.ainfo.mode == WILDCARD) {
/*
* If pkgmap has a '?' set for mode, use the mode from
* the pkg DB (contents file).
*/
el_ent->cf_ent.ainfo.mode = cf_ent->ainfo.mode;
el_ent->mstat.attrchg = 0;
} else {
el_ent->mstat.attrchg = 1;
}
}
if (strcmp(cf_ent->ainfo.owner, el_ent->cf_ent.ainfo.owner) != 0) {
changed++; /* attribute info is changing */
if (strcmp(el_ent->cf_ent.ainfo.owner, BADOWNER) == 0)
(void) strcpy(el_ent->cf_ent.ainfo.owner,
cf_ent->ainfo.owner);
else
el_ent->mstat.attrchg = 1;
}
if (strcmp(cf_ent->ainfo.group, el_ent->cf_ent.ainfo.group) != 0) {
changed++; /* attribute info is changing */
if (strcmp(el_ent->cf_ent.ainfo.group, BADGROUP) == 0)
(void) strcpy(el_ent->cf_ent.ainfo.group,
cf_ent->ainfo.group);
else
el_ent->mstat.attrchg = 1;
}
return (changed ? MRG_DIFFERENT : MRG_SAME);
}
/*
* This puts the current entry into the package database in the appropriate
* intermediate format for this stage of the installation. This also assures
* the correct format for the various package object ftypes, stripping the
* link name before storing a regular file and stuff like that.
*/
static void
output(VFP_T *vfpo, struct cfent *ent, struct pinfo *pinfo)
{
short svvolno;
char *svpt;
/* output without volume information */
svvolno = ent->volno;
ent->volno = 0;
pinfo->editflag = 0;
if (((ent->ftype == 's') || (ent->ftype == 'l'))) {
if (putcvfpfile(ent, vfpo)) {
progerr(gettext(ERR_OUTPUT));
quit(99);
}
} else {
/* output without local pathname */
svpt = ent->ainfo.local;
ent->ainfo.local = NULL;
if (putcvfpfile(ent, vfpo)) {
progerr(gettext(ERR_OUTPUT));
quit(99);
}
ent->ainfo.local = svpt;
/*
* If this entry represents a file which is being edited, we
* need to store in memory the fact that it is an edittable
* file so that when we audit it after installation we do not
* worry about its contents; we do this by resetting the ftype
* to 'e' in the memory array which is later used to control
* the audit
*/
if (pinfo->editflag)
ent->ftype = 'e';
}
/* restore volume information */
ent->volno = svvolno;
}
static void
chgclass(struct cfent *cf_ent, struct pinfo *pinfo)
{
struct pinfo *pp;
char *oldclass, newclass[CLSSIZ+1];
int newcnt, oldcnt;
/*
* we use this routine to minimize the use of the aclass element by
* optimizing the use of the cf_ent->pkg_class element
*/
(void) strlcpy(newclass, pinfo->aclass, sizeof (newclass));
newcnt = 1;
oldclass = cf_ent->pkg_class;
oldcnt = 0;
/*
* count the number of times the newclass will be used and see if it
* exceeds the number of times the oldclass is referenced
*/
pp = cf_ent->pinfo;
while (pp) {
if (pp->aclass[0] != '\0') {
if (strcmp(pp->aclass, newclass) == 0)
newcnt++;
else if (strcmp(pp->aclass, oldclass) == 0)
oldcnt++;
}
pp = pp->next;
}
if (newcnt > oldcnt) {
pp = cf_ent->pinfo;
while (pp) {
if (pp->aclass[0] == '\0') {
(void) strcpy(pp->aclass, oldclass);
} else if (strcmp(pp->aclass, newclass) == 0) {
pp->aclass[0] = '\0';
}
pp = pp->next;
}
(void) strcpy(cf_ent->pkg_class, newclass);
}
}