/*
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pkgstrct.h>
#include <unistd.h>
#include <pkglib.h>
#include <libintl.h>
#include "libadm.h"
#include "libinst.h"
#include "dryrun.h"
#define HDR_FSUSAGE "#name remote_name writeable bfree bused ifree iused"
#define ERR_NOCREAT "cannot create %s."
#define ERR_NOOPEN "cannot open %s."
#define ERR_NOWRITE "cannot write to %s."
#define ERR_NOREAD "cannot read from %s."
#define ERR_FSFAIL "cannot construct filesystem table entry."
#define ERR_BADTYPE "cannot record %s dryrun from %s continuation file."
#define ERR_NOCONT "cannot install from continue file due to error " \
"stacking."
#define ISUMASC_SUFFIX ".isum.asc"
#define FSASC_SUFFIX ".fs.asc"
#define IPOASC_SUFFIX ".ipo.asc"
#define IBIN_SUFFIX ".inst.bin"
#define MALCOUNT 5 /* package entries to allocate in a block */
#define PKGNAMESIZE 32 /* package entries to allocate in a block */
extern struct cfextra **extlist;
extern char *pkginst;
static struct cfextra **extptr;
static int dryrun_mode = 0;
static int continue_mode = 0;
static int this_exitcode = 0;
/* The dryrun and continuation filenames */
static char *dryrun_sumasc;
static char *dryrun_fsasc;
static char *dryrun_poasc;
static char *dryrun_bin;
static char *continue_bin;
/* These tell us if the actual files are initialized yet. */
static int dryrun_initialized = 0;
static int continue_initialized = 0;
static int this_type; /* type of transaction from main.c */
static int pkg_handle = -1;
static int tot_pkgs;
/* Their associated file pointers */
static FILE *fp_dra;
static int fd_drb;
static int fd_cnb;
struct dr_pkg_entry {
char pkginst[PKGNAMESIZE + 2];
struct dr_pkg_entry *next;
};
static struct drinfo {
unsigned partial_set:1; /* 1 if a partial installation was detected. */
unsigned partial:1; /* 1 if a partial installation was detected. */
unsigned runlevel_set:1;
unsigned runlevel:1; /* 1 if runlevel test returned an error. */
unsigned pkgfiles_set:1;
unsigned pkgfiles:1;
unsigned depend_set:1;
unsigned depend:1;
unsigned space_set:1;
unsigned space:1;
unsigned conflict_set:1;
unsigned conflict:1;
unsigned setuid_set:1;
unsigned setuid:1;
unsigned priv_set:1;
unsigned priv:1;
unsigned pkgdirs_set:1;
unsigned pkgdirs:1;
unsigned reqexit_set:1;
unsigned checkexit_set:1;
int type; /* type of operation */
int reqexit; /* request script exit code */
int checkexit; /* checkinstall script exit code */
int exitcode; /* overall program exit code. */
struct dr_pkg_entry *packages; /* pointer to the list of packages */
int total_ext_recs; /* total extlist entries */
int total_fs_recs; /* total fs_tab entries */
int total_pkgs; /* total number of dryrun packages */
int do_not_continue; /* error stacking is likely */
} dr_info;
static char *exitmsg; /* the last meaningful message printed */
/*
* In the event that live continue (continue from a dryrun source only)
* becomes a feature, it will be necessary to keep track of those events such
* as multiply edited files and files dependent upon multiple class action
* scripts that will lead to "tolerance stacking". Calling this function
* states that we've lost the level of precision necessary for a live
* continue.
*/
void
set_continue_not_ok(void)
{
dr_info.do_not_continue = 1;
}
int
continue_is_ok(void)
{
return (!dr_info.do_not_continue);
}
static void
wr_OK(FILE *fp, char *parameter, int set, int value)
{
(void) fprintf(fp, "%s=%s\n", parameter,
(set ? (value ? "OK" : "NOT_OK") : "NOT_TESTED"));
}
static void
add_pkg_to_list(char *pkgname)
{
struct dr_pkg_entry **pkg_entry;
if (pkg_handle == -1) {
if ((pkg_handle = bl_create(MALCOUNT,
sizeof (struct dr_pkg_entry), "package dryrun")) == -1)
return;
}
pkg_entry = &(dr_info.packages);
while (*pkg_entry != NULL)
pkg_entry = &((*pkg_entry)->next);
/* LINTED pointer cast may result in improper alignment */
*pkg_entry = (struct dr_pkg_entry *)bl_next_avail(pkg_handle);
dr_info.total_pkgs++;
(void) snprintf((*pkg_entry)->pkginst, PKGNAMESIZE, "%s%s",
(pkgname ? pkgname : ""), ((this_exitcode == 0) ? "" : "-"));
}
static void
write_pkglist_ascii(void)
{
struct dr_pkg_entry *pkg_entry;
(void) fprintf(fp_dra, "PKG_LIST=\"");
pkg_entry = dr_info.packages;
while (pkg_entry) {
(void) fprintf(fp_dra, " %s", pkg_entry->pkginst);
pkg_entry = pkg_entry->next;
}
(void) fprintf(fp_dra, "\"\n");
}
static int
write_string(int fd, char *string)
{
int string_size;
if (string)
string_size = strlen(string) + 1;
else
string_size = 0;
if (write(fd, &string_size, sizeof (string_size)) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return (0);
}
if (string_size > 0) {
if (write(fd, string, string_size) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return (0);
}
}
return (1);
}
static char *
read_string(int fd, char *buffer)
{
size_t string_size;
if (read(fd, &(string_size), sizeof (string_size)) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (NULL);
}
if (string_size != 0) {
if (read(fd, buffer, string_size) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (NULL);
}
} else {
return (NULL);
}
return (buffer);
}
static void
write_dryrun_ascii()
{
int n;
char *fs_mntpt, *src_name;
if ((fp_dra = fopen(dryrun_sumasc, "wb")) == NULL) {
progerr(gettext(ERR_NOOPEN), dryrun_sumasc);
return;
}
(void) fprintf(fp_dra, "DR_TYPE=%s\n", (dr_info.type == REMOVE_TYPE ?
"REMOVE" : "INSTALL"));
(void) fprintf(fp_dra, "PKG_INSTALL_ROOT=%s\n", (((get_inst_root()) &&
(strcmp(get_inst_root(), "/") != 0)) ?
get_inst_root() : ""));
write_pkglist_ascii();
wr_OK(fp_dra, "CONTINUE", 1, !(dr_info.do_not_continue));
wr_OK(fp_dra, "PARTIAL", dr_info.partial_set, dr_info.partial);
wr_OK(fp_dra, "RUNLEVEL", dr_info.runlevel_set, dr_info.runlevel);
(void) fprintf(fp_dra, "REQUESTEXITCODE=%d\n", dr_info.reqexit);
(void) fprintf(fp_dra, "CHECKINSTALLEXITCODE=%d\n", dr_info.checkexit);
wr_OK(fp_dra, "PKGFILES", dr_info.pkgfiles_set, dr_info.pkgfiles);
wr_OK(fp_dra, "DEPEND", dr_info.depend_set, dr_info.depend);
wr_OK(fp_dra, "SPACE", dr_info.space_set, dr_info.space);
wr_OK(fp_dra, "CONFLICT", dr_info.conflict_set, dr_info.conflict);
wr_OK(fp_dra, "SETUID", dr_info.setuid_set, dr_info.setuid);
wr_OK(fp_dra, "PRIV", dr_info.priv_set, dr_info.priv);
wr_OK(fp_dra, "PKGDIRS", dr_info.pkgdirs_set, dr_info.pkgdirs);
(void) fprintf(fp_dra, "EXITCODE=%d\n", dr_info.exitcode);
(void) fprintf(fp_dra, "ERRORMSG=%s\n", (exitmsg ? exitmsg : "NONE"));
(void) fclose(fp_dra);
if ((fp_dra = fopen(dryrun_fsasc, "wb")) == NULL) {
progerr(gettext(ERR_NOOPEN), dryrun_fsasc);
return;
}
(void) fprintf(fp_dra, "%s\nFSUSAGE=\\\n\"\\\n", HDR_FSUSAGE);
for (n = 0; fs_mntpt = get_fs_name_n(n); n++) {
int bfree, bused;
bfree = get_blk_free_n(n);
bused = get_blk_used_n(n);
if (bfree || bused) {
(void) fprintf(fp_dra, "%s %s %s %d %d %lu %lu \\\n",
fs_mntpt,
((src_name = get_source_name_n(n)) ?
src_name : "none?"),
(is_fs_writeable_n(n) ? "TRUE" : "FALSE"),
bfree,
bused,
get_inode_free_n(n),
get_inode_used_n(n));
}
}
dr_info.total_fs_recs = n;
(void) fprintf(fp_dra, "\"\n");
(void) fclose(fp_dra);
if ((fp_dra = fopen(dryrun_poasc, "wb")) == NULL) {
progerr(gettext(ERR_NOOPEN), dryrun_poasc);
return;
}
dr_info.total_ext_recs = 0;
(void) fprintf(fp_dra, "WOULD_INSTALL=\\\n\"\\\n");
for (n = 0; extptr && extptr[n]; n++) {
/*
* Write it out if it's a successful change or it is from the
* prior dryrun file (meaning it was a change back then).
*/
if ((this_exitcode == 0 &&
(extptr[n]->mstat.contchg || extptr[n]->mstat.attrchg)) ||
extptr[n]->mstat.preloaded) {
(void) fprintf(fp_dra, "%c %s \\\n",
extptr[n]->cf_ent.ftype,
extptr[n]->client_path);
/* Count it, if it's going into the dryrun file. */
if (extptr[n]->cf_ent.ftype != 'i')
dr_info.total_ext_recs++;
}
}
(void) fprintf(fp_dra, "\"\n");
(void) fclose(fp_dra);
}
/*
* This writes out a dryrun file.
*/
static void
write_dryrun_bin()
{
struct fstable *fs_entry;
struct pinfo *pkginfo;
struct dr_pkg_entry *pkg_entry;
int n;
int fsentry_size = sizeof (struct fstable);
int extentry_size = sizeof (struct cfextra);
int pinfoentry_size = sizeof (struct pinfo);
if ((fd_drb = open(dryrun_bin,
O_RDWR | O_APPEND | O_TRUNC)) == -1) {
progerr(gettext(ERR_NOOPEN), dryrun_bin);
return;
}
/* Write the dryrun info table. */
if (write(fd_drb, &dr_info, sizeof (struct drinfo)) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return;
}
/* Write out the package instance list. */
pkg_entry = dr_info.packages;
while (pkg_entry) {
if (write(fd_drb, pkg_entry->pkginst, PKGNAMESIZE) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return;
}
pkg_entry = pkg_entry->next;
}
/* Write out the fstable records. */
for (n = 0; n < dr_info.total_fs_recs; n++) {
fs_entry = get_fs_entry(n);
if (write(fd_drb, fs_entry, fsentry_size) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return;
}
if (!write_string(fd_drb, fs_entry->name))
return;
if (!write_string(fd_drb, fs_entry->fstype))
return;
if (!write_string(fd_drb, fs_entry->remote_name))
return;
}
/* Write out the package objects and their attributes. */
for (n = 0; extptr && extptr[n]; n++) {
/* Don't save metafiles. */
if (extptr[n]->cf_ent.ftype == 'i')
continue;
/*
* If it's a new package object (not left over from the
* continuation file) and it indicates no changes to the
* system, skip it. Only files that will change the system
* are stored.
*/
if (extptr[n]->mstat.preloaded == 0 &&
!(this_exitcode == 0 &&
(extptr[n]->mstat.contchg || extptr[n]->mstat.attrchg)))
continue;
if (write(fd_drb, extptr[n], extentry_size) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return;
}
if (!write_string(fd_drb, extptr[n]->cf_ent.path))
return;
if (!write_string(fd_drb, extptr[n]->cf_ent.ainfo.local))
return;
extptr[n]->cf_ent.pinfo = eptstat(&(extptr[n]->cf_ent),
pkginst, CONFIRM_CONT);
/*
* Now all of the entries about the various packages that own
* this entry.
*/
pkginfo = extptr[n]->cf_ent.pinfo;
do {
if (write(fd_drb, pkginfo,
pinfoentry_size) == -1) {
progerr(gettext(ERR_NOWRITE), dryrun_bin);
return;
}
pkginfo = pkginfo->next; /* May be several */
} while (pkginfo);
}
(void) close(fd_drb);
}
static void
init_drinfo(void) {
if (dr_info.partial != 0)
dr_info.partial_set = 0;
if (dr_info.runlevel != 0)
dr_info.runlevel_set = 0;
if (dr_info.pkgfiles != 0)
dr_info.pkgfiles_set = 0;
if (dr_info.depend != 0)
dr_info.depend_set = 0;
if (dr_info.space != 0)
dr_info.space_set = 0;
if (dr_info.conflict != 0)
dr_info.conflict_set = 0;
if (dr_info.setuid != 0)
dr_info.setuid_set = 0;
if (dr_info.priv != 0)
dr_info.priv_set = 0;
if (dr_info.pkgdirs != 0)
dr_info.pkgdirs_set = 0;
if (dr_info.reqexit == 0)
dr_info.reqexit_set = 0;
if (dr_info.checkexit == 0)
dr_info.checkexit_set = 0;
dr_info.packages = NULL;
tot_pkgs = dr_info.total_pkgs;
dr_info.total_pkgs = 0;
}
/*
* This function reads in the various continuation file data in order to seed
* the internal data structures.
*/
static boolean_t
read_continue_bin(void)
{
int n;
int fsentry_size = sizeof (struct fstable);
int extentry_size = sizeof (struct cfextra);
int pinfoentry_size = sizeof (struct pinfo);
pkgobjinit();
if (!init_pkgobjspace())
return (B_FALSE);
if ((fd_cnb = open(continue_bin, O_RDONLY)) == -1) {
progerr(gettext(ERR_NOOPEN), continue_bin);
return (B_FALSE);
}
/* Read the dryrun info structure. */
if (read(fd_cnb, &dr_info, sizeof (struct drinfo)) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (B_FALSE);
}
init_drinfo();
if (this_type != dr_info.type) {
progerr(gettext(ERR_BADTYPE),
(this_type == REMOVE_TYPE) ?
"a remove" : "an install",
(dr_info.type == REMOVE_TYPE) ?
"a remove" : "an install");
return (B_FALSE);
}
/* Read in the dryrun package records. */
for (n = 0; n < tot_pkgs; n++) {
char pkg_name[PKGNAMESIZE];
if (read(fd_cnb, &pkg_name, PKGNAMESIZE) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (B_FALSE);
}
add_pkg_to_list(pkg_name);
}
/* Read in the fstable records. */
for (n = 0; n < dr_info.total_fs_recs; n++) {
struct fstable fs_entry;
char name[PATH_MAX], remote_name[PATH_MAX];
char fstype[200];
if (read(fd_cnb, &fs_entry, fsentry_size) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (B_FALSE);
}
if (read_string(fd_cnb, &name[0]) == NULL)
return (B_FALSE);
if (read_string(fd_cnb, &fstype[0]) == NULL)
return (B_FALSE);
if (read_string(fd_cnb, &remote_name[0]) == NULL)
return (B_FALSE);
if (load_fsentry(&fs_entry, name, fstype, remote_name)) {
progerr(gettext(ERR_FSFAIL));
return (B_FALSE);
}
}
/* Read in the package objects and their attributes. */
for (n = 0; n < dr_info.total_ext_recs; n++) {
struct cfextra ext_entry;
struct pinfo pinfo_area, *pinfo_ptr;
char path[PATH_MAX], local[PATH_MAX], *local_ptr;
if (read(fd_cnb, &ext_entry, extentry_size) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (B_FALSE);
}
/*
* If the previous dryrun replaced a directory with a
* non-directory and we're going into *another* dryrun, we're
* stacking errors and continuation should not be permitted.
*/
if (ext_entry.mstat.dir2nondir && dryrun_mode)
dr_info.do_not_continue = 1;
/*
* Since we just read this from a continuation file; it is,
* by definition, preloaded.
*/
ext_entry.mstat.preloaded = 1;
if (read_string(fd_cnb, &path[0]) == NULL)
return (B_FALSE);
local_ptr = read_string(fd_cnb, &local[0]);
ext_entry.cf_ent.pinfo = NULL;
/*
* Now all of the entries about the various packages that own
* this entry.
*/
do {
if (read(fd_cnb, &pinfo_area, pinfoentry_size) == -1) {
progerr(gettext(ERR_NOREAD), continue_bin);
return (B_FALSE);
}
pinfo_ptr = eptstat(&(ext_entry.cf_ent),
pinfo_area.pkg, CONFIRM_CONT);
if (pinfo_ptr->next) {
pinfo_ptr = pinfo_ptr->next;
} else {
pinfo_ptr = NULL;
}
} while (pinfo_ptr);
seed_pkgobjmap(&ext_entry, path, local_ptr);
}
(void) close(fd_cnb);
/*
* Return as reading is done, so pkginstall doesn't
* read the same info from the system.
*/
return (B_TRUE);
}
int
in_dryrun_mode(void)
{
return (dryrun_mode);
}
void
set_dryrun_mode(void)
{
dryrun_mode = 1;
}
int
in_continue_mode(void)
{
return (continue_mode);
}
void
set_continue_mode(void)
{
continue_mode = 1;
}
/*
* Initialize a dryrun file by assigning it a name and creating it
* empty.
*/
static int
init_drfile(char **targ_ptr, char *path)
{
int n;
char *targ_file;
*targ_ptr = strdup(path);
targ_file = *targ_ptr;
if (access(targ_file, W_OK) == 0) {
(void) unlink(targ_file);
}
n = open(targ_file, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0644);
if (n < 0) {
progerr(gettext(ERR_NOCREAT), targ_file);
return (0);
} else {
(void) close(n);
}
return (1);
}
/*
* Initialize all required dryrun files and see that the target directory is
* present. If all goes well, we're in dryrun mode. If it doesn't, we're not.
*/
void
init_dryrunfile(char *dr_dir)
{
char temp_path[PATH_MAX];
char *dot_pos = (temp_path+strlen(dr_dir)+7);
/* First create or confirm the directory. */
if (isdir(dr_dir) != 0) {
(void) mkpath(dr_dir);
}
(void) snprintf(temp_path, sizeof (temp_path), "%s/dryrun", dr_dir);
(void) strcpy(dot_pos, ISUMASC_SUFFIX);
if (!init_drfile(&dryrun_sumasc, temp_path))
return;
(void) strcpy(dot_pos, FSASC_SUFFIX);
if (!init_drfile(&dryrun_fsasc, temp_path))
return;
(void) strcpy(dot_pos, IPOASC_SUFFIX);
if (!init_drfile(&dryrun_poasc, temp_path))
return;
(void) strcpy(dot_pos, IBIN_SUFFIX);
if (!init_drfile(&dryrun_bin, temp_path))
return;
dryrun_initialized = 1;
}
void
init_contfile(char *cn_dir)
{
char temp_path[PATH_MAX];
/* First confirm the directory. */
if (isdir(cn_dir) != 0)
return; /* no continuation directory */
(void) snprintf(temp_path, sizeof (temp_path),
"%s/dryrun%s", cn_dir, IBIN_SUFFIX);
continue_bin = strdup(temp_path);
if (access(continue_bin, W_OK) != 0) {
free(continue_bin);
return;
}
continue_initialized = 1;
}
void
set_dr_exitmsg(char *value)
{
exitmsg = value;
}
void
set_dr_info(int type, int value)
{
switch (type) {
case PARTIAL:
if (dr_info.partial_set == 0) {
dr_info.partial_set = 1;
dr_info.partial = (value ? 1 : 0);
}
break;
case RUNLEVEL:
if (dr_info.runlevel_set == 0) {
dr_info.runlevel_set = 1;
dr_info.runlevel = (value ? 1 : 0);
}
break;
case PKGFILES:
if (dr_info.pkgfiles_set == 0) {
dr_info.pkgfiles_set = 1;
dr_info.pkgfiles = (value ? 1 : 0);
}
break;
case DEPEND:
if (dr_info.depend_set == 0) {
dr_info.depend_set = 1;
dr_info.depend = (value ? 1 : 0);
}
break;
case SPACE:
if (dr_info.space_set == 0) {
dr_info.space_set = 1;
dr_info.space = (value ? 1 : 0);
}
break;
case CONFLICT:
if (dr_info.conflict_set == 0) {
dr_info.conflict_set = 1;
dr_info.conflict = (value ? 1 : 0);
}
break;
case SETUID:
if (dr_info.setuid_set == 0) {
dr_info.setuid_set = 1;
dr_info.setuid = (value ? 1 : 0);
}
break;
case PRIV:
if (dr_info.priv_set == 0) {
dr_info.priv_set = 1;
dr_info.priv = (value ? 1 : 0);
}
break;
case PKGDIRS:
if (dr_info.pkgdirs_set == 0) {
dr_info.pkgdirs_set = 1;
dr_info.pkgdirs = (value ? 1 : 0);
}
break;
case REQUESTEXITCODE:
if (dr_info.reqexit_set == 0) {
dr_info.reqexit_set = 1;
dr_info.reqexit = value;
}
break;
case CHECKEXITCODE:
if (dr_info.checkexit_set == 0) {
dr_info.checkexit_set = 1;
dr_info.checkexit = value;
}
break;
case EXITCODE:
if (dr_info.exitcode == 0) {
dr_info.exitcode = value;
}
this_exitcode = value;
break;
/* default to install if the value is kookie. */
case DR_TYPE:
if (value == REMOVE_TYPE)
this_type = REMOVE_TYPE;
else
this_type = INSTALL_TYPE;
break;
}
}
void
write_dryrun_file(struct cfextra **extlist)
{
extptr = extlist;
if (dryrun_initialized) {
dr_info.type = this_type;
add_pkg_to_list(pkginst);
write_dryrun_ascii();
write_dryrun_bin();
if (dryrun_mode) {
free(dryrun_sumasc);
free(dryrun_fsasc);
free(dryrun_poasc);
free(dryrun_bin);
}
}
}
/*
* Name: read_continuation
* Description: If continuation is initialised, reads the
* continuation binary file. The path for the
* same is freed, if set, as this is the last
* chance to do so.
* Sets: Error condition, through the pointer passed
* if read failed.
* Returns: B_TRUE - if the continuation binary file
* from previous dryrun is read successfully.
* B_FALSE - if either continuation is not initialised
* or read was not successful.
*/
boolean_t
read_continuation(int *error)
{
boolean_t ret = B_FALSE;
*error = 0;
if (continue_initialized) {
if (!read_continue_bin()) {
continue_mode = 0;
free(continue_bin);
*error = -1;
return (ret);
}
if (continue_mode) {
free(continue_bin);
}
ret = B_TRUE;
}
return (ret);
}