devalloc.c revision 38cb303e93cd6529fb20fa80f324e15529ff8b53
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <synch.h>
#include <strings.h>
#include <string.h>
#include <libintl.h>
#include <errno.h>
#include <auth_list.h>
#include <bsm/devices.h>
#include <bsm/devalloc.h>
#define DA_DEFS "/etc/security/tsol/devalloc_defaults"
extern int _readbufline(char *, int, char *, int, int *);
extern char *strtok_r(char *, const char *, char **);
extern char *_strtok_escape(char *, char *, char **);
extern int getdaon(void);
extern int da_matchname(devalloc_t *, char *);
extern int da_match(devalloc_t *, da_args *);
extern int dmap_matchname(devmap_t *, char *);
extern int dm_match(devmap_t *, da_args *);
/*
* The following structure is for recording old entries to be retained.
* We read the entries from the database into a linked list in memory,
* then turn around and write them out again.
*/
typedef struct strentry {
struct strentry *se_next;
char se_str[4096 + 1];
} strentry_t;
/*
* da_check_longindevperm -
* reads /etc/logindevperm and checks if specified device is in the file.
* returns 1 if specified device found in /etc/logindevperm, else returns 0
*/
int
da_check_logindevperm(char *devname)
{
int ret = 0;
int fd = -1;
int nlen, plen, slen, lineno, fsize;
char line[MAX_CANON];
char *field_delims = " \t\n";
char *fbuf = NULL;
char *ptr, *device;
char *lasts = NULL;
FILE *fp;
struct stat f_stat;
/*
* check if /etc/logindevperm exists and get its size
*/
if ((fd = open(LOGINDEVPERM, O_RDONLY)) == -1)
return (0);
if (fstat(fd, &f_stat) != 0) {
(void) close(fd);
return (0);
}
fsize = f_stat.st_size;
if ((fbuf = (char *)malloc(fsize)) == NULL) {
(void) close(fd);
return (0);
}
if ((fp = fdopen(fd, "rF")) == NULL) {
free(fbuf);
(void) close(fd);
return (0);
}
/*
* read and parse /etc/logindevperm
*/
plen = nlen = lineno = 0;
while (fgets(line, MAX_CANON, fp) != NULL) {
lineno++;
if ((ptr = strchr(line, '#')) != NULL)
*ptr = '\0'; /* handle comments */
if (strtok_r(line, field_delims, &lasts) == NULL)
continue; /* ignore blank lines */
if (strtok_r(NULL, field_delims, &lasts) == NULL)
/* invalid entry */
continue;
if ((ptr = strtok_r(NULL, field_delims, &lasts)) == NULL)
/* empty device list */
continue;
nlen = strlen(ptr) + 1; /* +1 terminator */
nlen += (plen + 1);
if (plen == 0)
slen = snprintf(fbuf, nlen, "%s", ptr);
else
slen = snprintf(fbuf + plen, nlen - plen, ":%s", ptr);
if (slen >= fsize) {
fbuf[0] = '\0';
(void) fclose(fp);
return (slen);
}
plen += slen;
}
(void) fclose(fp);
/*
* check if devname exists in /etc/logindevperm
*/
device = strtok_r(fbuf, ":", &lasts);
while (device != NULL) {
/*
* device and devname may be one of these types -
* /dev/xx
* /dev/xx*
* /dev/dir/xx
* /dev/dir/xx*
* /dev/dir/"*"
*/
if (strcmp(device, devname) == 0) {
/* /dev/xx, /dev/dir/xx */
free(fbuf);
return (1);
}
if ((ptr = strrchr(device, KV_WILDCHAR)) != NULL) {
/* all wildcard types */
*ptr = '\0';
if (strncmp(device, devname, strlen(device)) == 0) {
free(fbuf);
return (1);
}
}
device = strtok_r(NULL, ":", &lasts);
}
return (ret);
}
/*
* _da_read_file -
* establishes readers/writer lock on fname; reads in the file if its
* contents changed since the last time we read it.
* returns size of buffer read, or -1 on failure.
*/
int
_da_read_file(char *fname, char **fbuf, time_t *ftime, rwlock_t *flock,
int flag)
{
int fd = -1;
int fsize = 0;
time_t newtime;
struct stat f_stat;
if (flag & DA_FORCE)
*ftime = 0;
/* check the size and the time stamp on the file */
if (rw_rdlock(flock) != 0)
return (-1);
if (stat(fname, &f_stat) != 0) {
(void) rw_unlock(flock);
return (-1);
}
fsize = f_stat.st_size;
newtime = f_stat.st_mtime;
(void) rw_unlock(flock);
while (newtime > *ftime) {
/*
* file has been modified since we last read it; or this
* is a forced read.
* read file into the buffer with rw lock.
*/
if (rw_wrlock(flock) != 0)
return (-1);
if ((fd = open(fname, O_RDONLY)) == -1) {
(void) rw_unlock(flock);
return (-1);
}
if (*fbuf != NULL) {
free(*fbuf);
*fbuf = NULL;
}
if ((*fbuf = malloc(fsize)) == NULL) {
(void) rw_unlock(flock);
(void) close(fd);
return (-1);
}
if (read(fd, *fbuf, fsize) < fsize) {
free(*fbuf);
(void) rw_unlock(flock);
(void) close(fd);
return (-1);
}
(void) rw_unlock(flock);
/*
* verify that the file did not change just after we read it.
*/
if (rw_rdlock(flock) != 0) {
free(*fbuf);
(void) close(fd);
return (-1);
}
if (stat(fname, &f_stat) != 0) {
free(*fbuf);
(void) rw_unlock(flock);
(void) close(fd);
return (-1);
}
fsize = f_stat.st_size;
newtime = f_stat.st_mtime;
(void) rw_unlock(flock);
(void) close(fd);
*ftime = newtime;
}
return (fsize);
}
/*
* _update_zonename -
* add/remove current zone's name to the given devalloc_t.
*/
void
_update_zonename(da_args *dargs, devalloc_t *dap)
{
int i, j;
int oldsize, newsize;
int has_zonename = 0;
char *zonename;
kva_t *newkva, *oldkva;
kv_t *newdata, *olddata;
devinfo_t *devinfo;
devinfo = dargs->devinfo;
oldkva = dap->da_devopts;
if (oldkva == NULL) {
if (dargs->optflag & DA_REMOVE_ZONE)
return;
if (dargs->optflag & DA_ADD_ZONE) {
newkva = _str2kva(devinfo->devopts, KV_ASSIGN,
KV_TOKEN_DELIMIT);
if (newkva != NULL)
dap->da_devopts = newkva;
return;
}
}
newsize = oldsize = oldkva->length;
if (kva_match(oldkva, DAOPT_ZONE))
has_zonename = 1;
if (dargs->optflag & DA_ADD_ZONE) {
if ((zonename = index(devinfo->devopts, '=')) == NULL)
return;
zonename++;
if (has_zonename) {
(void) _insert2kva(oldkva, DAOPT_ZONE, zonename);
return;
}
newsize += 1;
} else if (dargs->optflag & DA_REMOVE_ZONE) {
if (has_zonename) {
newsize -= 1;
if (newsize == 0) {
/*
* If zone name was the only key/value pair,
* put 'reserved' in the empty slot.
*/
_kva_free(oldkva);
dap->da_devopts = NULL;
return;
}
} else {
return;
}
}
newkva = _new_kva(newsize);
newkva->length = 0;
newdata = newkva->data;
olddata = oldkva->data;
for (i = 0, j = 0; i < oldsize; i++) {
if ((dargs->optflag & DA_REMOVE_ZONE) &&
(strcmp(olddata[i].key, DAOPT_ZONE) == 0))
continue;
newdata[j].key = strdup(olddata[i].key);
newdata[j].value = strdup(olddata[i].value);
newkva->length++;
j++;
}
if (dargs->optflag & DA_ADD_ZONE) {
newdata[j].key = strdup(DAOPT_ZONE);
newdata[j].value = strdup(zonename);
newkva->length++;
}
_kva_free(oldkva);
dap->da_devopts = newkva;
}
/*
* _dmap2str -
* converts a device_map entry into a printable string
* returns 0 on success, -1 on error.
*/
/*ARGSUSED*/
static int
_dmap2str(da_args *dargs, devmap_t *dmp, char *buf, int size, const char *sep)
{
int length;
length = snprintf(buf, size, "%s%s", dmp->dmap_devname, sep);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s%s",
dmp->dmap_devtype, sep);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s\n",
dmp->dmap_devlist);
if (length >= size)
return (-1);
return (0);
}
/*
* _dmap2strentry -
* calls dmap2str to break given devmap_t into printable entry.
* returns pointer to decoded entry, NULL on error.
*/
static strentry_t *
_dmap2strentry(da_args *dargs, devmap_t *devmapp)
{
strentry_t *sep;
if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
return (NULL);
if (_dmap2str(dargs, devmapp, sep->se_str, sizeof (sep->se_str),
KV_TOKEN_DELIMIT"\\\n\t") != 0) {
free(sep);
return (NULL);
}
return (sep);
}
/*
* fix_optstr -
* removes trailing ':' from buf.
*/
void
fix_optstr(char *buf)
{
char *p = NULL;
if (p = rindex(buf, ':'))
*p = ';';
}
/*
* _da2str -
* converts a device_allocate entry into a printable string
* returns 0 on success, -1 on error.
*/
static int
_da2str(da_args *dargs, devalloc_t *dap, char *buf, int size, const char *sep,
const char *osep)
{
int length;
int matching_entry = 0;
char **dnames;
if (dargs->optflag & DA_UPDATE &&
(dargs->optflag & DA_ADD_ZONE ||
dargs->optflag & DA_REMOVE_ZONE) &&
dargs->devnames) {
for (dnames = dargs->devnames; *dnames != NULL; dnames++) {
if (da_matchname(dap, *dnames)) {
matching_entry = 1;
break;
}
}
}
length = snprintf(buf, size, "%s%s", dap->da_devname, sep);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s%s",
dap->da_devtype, sep);
if (length >= size)
return (-1);
if (matching_entry)
_update_zonename(dargs, dap);
if ((dap->da_devopts == NULL) || ((dap->da_devopts->length == 1) &&
(strcmp(dap->da_devopts->data->key, DA_RESERVED) == 0))) {
length += snprintf(buf + length, size - length, "%s%s",
DA_RESERVED, sep);
} else {
if (_kva2str(dap->da_devopts, buf + length, size - length,
KV_ASSIGN, (char *)osep) != 0)
return (-1);
length = strlen(buf);
}
if (dap->da_devopts)
fix_optstr(buf);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s%s",
DA_RESERVED, sep);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s%s",
dap->da_devauth ? dap->da_devauth : DA_ANYUSER, sep);
if (length >= size)
return (-1);
length += snprintf(buf + length, size - length, "%s\n",
dap->da_devexec ? dap->da_devexec : "");
if (length >= size)
return (-1);
return (0);
}
/*
* _da2strentry -
* calls da2str to break given devalloc_t into printable entry.
* returns pointer to decoded entry, NULL on error.
*/
static strentry_t *
_da2strentry(da_args *dargs, devalloc_t *dap)
{
strentry_t *sep;
if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
return (NULL);
if (_da2str(dargs, dap, sep->se_str, sizeof (sep->se_str),
KV_DELIMITER "\\\n\t", KV_TOKEN_DELIMIT "\\\n\t") != 0) {
free(sep);
return (NULL);
}
return (sep);
}
/*
* _def2str
* converts da_defs_t into a printable string.
* returns 0 on success, -1 on error.
*/
static int
_def2str(da_defs_t *da_defs, char *buf, int size, const char *sep)
{
int length;
length = snprintf(buf, size, "%s%s", da_defs->devtype, sep);
if (length >= size)
return (-1);
if (da_defs->devopts) {
if (_kva2str(da_defs->devopts, buf + length, size - length,
KV_ASSIGN, KV_DELIMITER) != 0)
return (-1);
length = strlen(buf);
}
if (length >= size)
return (-1);
return (0);
}
/*
* _def2strentry
* calls _def2str to break given da_defs_t into printable entry.
* returns pointer decoded entry, NULL on error.
*/
static strentry_t *
_def2strentry(da_defs_t *da_defs)
{
strentry_t *sep;
if ((sep = (strentry_t *)malloc(sizeof (strentry_t))) == NULL)
return (NULL);
if (_def2str(da_defs, sep->se_str, sizeof (sep->se_str),
KV_TOKEN_DELIMIT) != 0) {
free(sep);
return (NULL);
}
return (sep);
}
/*
* _build_defattrs
* cycles through all defattr entries, stores them in memory. removes
* entries with the given search_key (device type).
* returns 0 if given entry not found, 1 if given entry removed, 2 on
* error.
*/
static int
_build_defattrs(da_args *dargs, strentry_t **head_defent)
{
int rc = 0;
da_defs_t *da_defs;
strentry_t *tail_str, *tmp_str;
setdadefent();
while ((da_defs = getdadefent()) != NULL) {
rc = !(strcmp(da_defs->devtype, dargs->devinfo->devtype));
if (rc && dargs->optflag & DA_ADD &&
!(dargs->optflag & DA_FORCE)) {
/*
* During DA_ADD, we keep an existing entry unless
* we have DA_FORCE set to override that entry.
*/
dargs->optflag |= DA_NO_OVERRIDE;
rc = 0;
}
if (rc == 0) {
tmp_str = _def2strentry(da_defs);
if (tmp_str == NULL) {
freedadefent(da_defs);
enddadefent();
return (2);
}
/* retaining defattr entry: tmp_str->se_str */
tmp_str->se_next = NULL;
if (*head_defent == NULL) {
*head_defent = tail_str = tmp_str;
} else {
tail_str->se_next = tmp_str;
tail_str = tmp_str;
}
}
freedadefent(da_defs);
}
enddadefent();
return (rc);
}
/*
* _build_lists -
* cycles through all the entries, stores them in memory. removes entries
* with the given search_key (device name or type).
* returns 0 if given entry not found, 1 if given entry removed, 2 on
* error.
*/
static int
_build_lists(da_args *dargs, strentry_t **head_devallocp,
strentry_t **head_devmapp)
{
int rc = 0;
devalloc_t *devallocp;
devmap_t *devmapp;
strentry_t *tail_str;
strentry_t *tmp_str;
if (dargs->optflag & DA_MAPS_ONLY)
goto dmap_only;
/* build device_allocate */
setdaent();
while ((devallocp = getdaent()) != NULL) {
rc = da_match(devallocp, dargs);
if (rc && dargs->optflag & DA_ADD &&
!(dargs->optflag & DA_FORCE)) {
/*
* During DA_ADD, we keep an existing entry unless
* we have DA_FORCE set to override that entry.
*/
dargs->optflag |= DA_NO_OVERRIDE;
rc = 0;
}
if (rc == 0) {
tmp_str = _da2strentry(dargs, devallocp);
if (tmp_str == NULL) {
freedaent(devallocp);
enddaent();
return (2);
}
/* retaining devalloc entry: tmp_str->se_str */
tmp_str->se_next = NULL;
if (*head_devallocp == NULL) {
*head_devallocp = tail_str = tmp_str;
} else {
tail_str->se_next = tmp_str;
tail_str = tmp_str;
}
}
freedaent(devallocp);
}
enddaent();
dmap_only:
if (dargs->optflag & DA_ALLOC_ONLY)
return (rc);
/* build device_maps */
rc = 0;
setdmapent();
while ((devmapp = getdmapent()) != NULL) {
rc = dm_match(devmapp, dargs);
if (rc && dargs->optflag & DA_ADD &&
!(dargs->optflag & DA_FORCE)) {
/*
* During DA_ADD, we keep an existing entry unless
* we have DA_FORCE set to override that entry.
*/
dargs->optflag |= DA_NO_OVERRIDE;
rc = 0;
}
if (rc == 0) {
tmp_str = _dmap2strentry(dargs, devmapp);
if (tmp_str == NULL) {
freedmapent(devmapp);
enddmapent();
return (2);
}
/* retaining devmap entry: tmp_str->se_str */
tmp_str->se_next = NULL;
if (*head_devmapp == NULL) {
*head_devmapp = tail_str = tmp_str;
} else {
tail_str->se_next = tmp_str;
tail_str = tmp_str;
}
}
freedmapent(devmapp);
}
enddmapent();
return (rc);
}
/*
* _write_defattrs
* writes current entries to devalloc_defaults.
*/
static void
_write_defattrs(FILE *fp, strentry_t *head_defent)
{
strentry_t *tmp_str;
for (tmp_str = head_defent; tmp_str != NULL;
tmp_str = tmp_str->se_next) {
(void) fputs(tmp_str->se_str, fp);
(void) fputs("\n", fp);
}
}
/*
* _write_device_allocate -
* writes current entries in the list to device_allocate.
*/
static void
_write_device_allocate(char *odevalloc, FILE *dafp, strentry_t *head_devallocp)
{
int is_on = -1;
strentry_t *tmp_str;
struct stat dastat;
(void) fseek(dafp, (off_t)0, SEEK_SET);
/*
* if the devalloc on/off string existed before,
* put it back before anything else.
* we need to check for the string only if the file
* exists.
*/
if (stat(odevalloc, &dastat) == 0) {
is_on = da_is_on();
if (is_on == 0)
(void) fputs(DA_OFF_STR, dafp);
else if (is_on == 1)
(void) fputs(DA_ON_STR, dafp);
}
tmp_str = head_devallocp;
while (tmp_str) {
(void) fputs(tmp_str->se_str, dafp);
(void) fputs("\n", dafp);
tmp_str = tmp_str->se_next;
}
}
/*
* _write_device_maps -
* writes current entries in the list to device_maps.
*/
static void
_write_device_maps(FILE *dmfp, strentry_t *head_devmapp)
{
strentry_t *tmp_str;
(void) fseek(dmfp, (off_t)0, SEEK_SET);
tmp_str = head_devmapp;
while (tmp_str) {
(void) fputs(tmp_str->se_str, dmfp);
(void) fputs("\n", dmfp);
tmp_str = tmp_str->se_next;
}
}
/*
* _write_new_defattrs
* writes the new entry to devalloc_defaults.
* returns 0 on success, -1 on error.
*/
static int
_write_new_defattrs(FILE *fp, da_args *dargs)
{
int count;
char *tok = NULL, *tokp = NULL;
char *lasts;
devinfo_t *devinfo = dargs->devinfo;
if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
return (-1);
if (!devinfo->devopts)
return (0);
(void) fprintf(fp, "%s%s", (devinfo->devtype ? devinfo->devtype : ""),
KV_TOKEN_DELIMIT);
if ((tokp = (char *)malloc(strlen(devinfo->devopts))) != NULL) {
(void) strcpy(tokp, devinfo->devopts);
if ((tok = strtok_r(tokp, KV_DELIMITER, &lasts)) != NULL) {
(void) fprintf(fp, "%s", tok);
count = 1;
}
while ((tok = strtok_r(NULL, KV_DELIMITER, &lasts)) != NULL) {
if (count)
(void) fprintf(fp, "%s", KV_DELIMITER);
(void) fprintf(fp, "%s", tok);
count++;
}
} else {
(void) fprintf(fp, "%s", devinfo->devopts);
}
return (0);
}
/*
* _write_new_entry -
* writes the new devalloc_t to device_allocate or the new devmap_t to
* device_maps.
* returns 0 on success, -1 on error.
*/
static int
_write_new_entry(FILE *fp, da_args *dargs, int flag)
{
int count;
char *tok = NULL, *tokp = NULL;
char *lasts;
devinfo_t *devinfo = dargs->devinfo;
if (flag & DA_MAPS_ONLY)
goto dmap_only;
if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
return (-1);
(void) fprintf(fp, "%s%s\\\n\t",
(devinfo->devname ? devinfo->devname : ""), KV_DELIMITER);
(void) fprintf(fp, "%s%s\\\n\t",
(devinfo->devtype ? devinfo->devtype : ""), KV_DELIMITER);
if (devinfo->devopts == NULL) {
(void) fprintf(fp, "%s%s\\\n\t", DA_RESERVED,
KV_DELIMITER);
} else {
if ((tokp = (char *)malloc(strlen(devinfo->devopts))) != NULL) {
(void) strcpy(tokp, devinfo->devopts);
if ((tok = strtok_r(tokp, KV_TOKEN_DELIMIT, &lasts)) !=
NULL) {
(void) fprintf(fp, "%s", tok);
count = 1;
}
while ((tok = strtok_r(NULL, KV_TOKEN_DELIMIT,
&lasts)) != NULL) {
if (count)
(void) fprintf(fp, "%s",
KV_TOKEN_DELIMIT "\\\n\t");
(void) fprintf(fp, "%s", tok);
count++;
}
if (count)
(void) fprintf(fp, "%s",
KV_DELIMITER "\\\n\t");
} else {
(void) fprintf(fp, "%s%s", devinfo->devopts,
KV_DELIMITER "\\\n\t");
}
}
(void) fprintf(fp, "%s%s\\\n\t", DA_RESERVED, KV_DELIMITER);
(void) fprintf(fp, "%s%s\\\n\t",
(devinfo->devauths ? devinfo->devauths : DA_ANYUSER),
KV_DELIMITER);
(void) fprintf(fp, "%s\n",
(devinfo->devexec ? devinfo->devexec : KV_DELIMITER));
dmap_only:
if (flag & DA_ALLOC_ONLY)
return (0);
if (fseek(fp, (off_t)0, SEEK_END) == (off_t)-1)
return (-1);
(void) fprintf(fp, "%s%s\\\n",
(devinfo->devname ? devinfo->devname : ""), KV_TOKEN_DELIMIT);
(void) fprintf(fp, "\t%s%s\\\n",
(devinfo->devtype ? devinfo->devtype : ""), KV_TOKEN_DELIMIT);
(void) fprintf(fp, "\t%s\n",
(devinfo->devlist ? devinfo->devlist : KV_TOKEN_DELIMIT));
return (0);
}
/*
* _da_lock_devdb -
* locks the database files; lock can be either broken explicitly by
* closing the fd of the lock file, or it expires automatically at process
* termination.
* returns fd of the lock file or -1 on error.
*/
int
_da_lock_devdb(char *rootdir)
{
int lockfd = -1;
int ret;
int count = 0;
int retry = 10;
int retry_sleep;
uint_t seed;
char *lockfile;
char path[MAXPATHLEN];
int size = sizeof (path);
if (rootdir == NULL) {
lockfile = DA_DB_LOCK;
} else {
path[0] = '\0';
if (snprintf(path, size, "%s%s", rootdir, DA_DB_LOCK) >= size)
return (-1);
lockfile = path;
}
if ((lockfd = open(lockfile, O_RDWR | O_CREAT, 0600)) == -1)
/* cannot open lock file */
return (-1);
(void) fchown(lockfd, DA_UID, DA_GID);
if (lseek(lockfd, (off_t)0, SEEK_SET) == -1) {
/* cannot position lock file */
(void) close(lockfd);
return (-1);
}
errno = 0;
while (retry > 0) {
count++;
seed = (uint_t)gethrtime();
ret = lockf(lockfd, F_TLOCK, 0);
if (ret == 0) {
(void) utime(lockfile, NULL);
return (lockfd);
}
if ((errno != EACCES) && (errno != EAGAIN)) {
/* cannot set lock */
(void) close(lockfd);
return (-1);
}
retry--;
retry_sleep = rand_r(&seed)/((RAND_MAX + 2)/3) + count;
(void) sleep(retry_sleep);
errno = 0;
}
return (-1);
}
/*
* da_open_devdb -
* opens one or both database files - device_allocate, device_maps - in
* the specified mode.
* locks the database files; lock is either broken explicitly by the
* caller by closing the lock file fd, or it expires automatically at
* process termination.
* writes the file pointer of opened file in the input args - dafp, dmfp.
* returns fd of the lock file on success, -2 if database file does not
* exist, -1 on other errors.
*/
int
da_open_devdb(char *rootdir, FILE **dafp, FILE **dmfp, int flag)
{
int oflag = 0;
int fda = -1;
int fdm = -1;
int lockfd = -1;
char *fname;
char *fmode;
char path[MAXPATHLEN];
FILE *devfile;
if ((dafp == NULL) && (dmfp == NULL))
return (-1);
if (flag & DA_RDWR) {
oflag = DA_RDWR;
fmode = "r+F";
} else if (flag & DA_RDONLY) {
oflag = DA_RDONLY;
fmode = "rF";
}
if ((lockfd = _da_lock_devdb(rootdir)) == -1)
return (-1);
if ((dafp == NULL) || (flag & DA_MAPS_ONLY))
goto dmap_only;
path[0] = '\0';
/*
* open the device allocation file
*/
if (rootdir == NULL) {
fname = DEVALLOC;
} else {
if (snprintf(path, sizeof (path), "%s%s", rootdir,
DEVALLOC) >= sizeof (path)) {
if (lockfd != -1)
(void) close(lockfd);
return (-1);
}
fname = path;
}
if ((fda = open(fname, oflag, DA_DBMODE)) == -1) {
if (lockfd != -1)
(void) close(lockfd);
return ((errno == ENOENT) ? -2 : -1);
}
if ((devfile = fdopen(fda, fmode)) == NULL) {
(void) close(fda);
if (lockfd != -1)
(void) close(lockfd);
return (-1);
}
*dafp = devfile;
(void) fchmod(fda, DA_DBMODE);
if ((flag & DA_ALLOC_ONLY))
goto out;
dmap_only:
path[0] = '\0';
/*
* open the device map file
*/
if (rootdir == NULL) {
fname = DEVMAP;
} else {
if (snprintf(path, sizeof (path), "%s%s", rootdir,
DEVMAP) >= sizeof (path)) {
(void) close(fda);
if (lockfd != -1)
(void) close(lockfd);
return (-1);
}
fname = path;
}
if ((fdm = open(fname, oflag, DA_DBMODE)) == -1) {
if (lockfd != -1)
(void) close(lockfd);
return ((errno == ENOENT) ? -2 : -1);
}
if ((devfile = fdopen(fdm, fmode)) == NULL) {
(void) close(fdm);
(void) close(fda);
if (lockfd != -1)
(void) close(lockfd);
return (-1);
}
*dmfp = devfile;
(void) fchmod(fdm, DA_DBMODE);
out:
return (lockfd);
}
/*
* _record_on_off -
* adds either DA_ON_STR or DA_OFF_STR to device_allocate
* returns 0 on success, -1 on error.
*/
static int
_record_on_off(da_args *dargs, FILE *tafp, FILE *dafp)
{
int dafd;
int nsize;
int nitems = 1;
int actionlen;
int str_found = 0;
int len = 0, nlen = 0, plen = 0;
char *ptr = NULL;
char *actionstr;
char *nbuf = NULL;
char line[MAX_CANON];
struct stat dastat;
if (dargs->optflag & DA_ON)
actionstr = DA_ON_STR;
else
actionstr = DA_OFF_STR;
actionlen = strlen(actionstr);
dafd = fileno(dafp);
if (fstat(dafd, &dastat) == -1)
return (-1);
/* check the old device_allocate for on/off string */
ptr = fgets(line, MAX_CANON, dafp);
if (ptr != NULL) {
if ((strcmp(line, DA_ON_STR) == 0) ||
(strcmp(line, DA_OFF_STR) == 0)) {
str_found = 1;
nsize = dastat.st_size;
}
}
if (!ptr || !str_found) {
/*
* the file never had either the on or the off string;
* make room for it.
*/
str_found = 0;
nsize = dastat.st_size + actionlen + 1;
}
if ((nbuf = (char *)malloc(nsize)) == NULL)
return (-1);
nbuf[0] = '\0';
/* put the on/off string */
(void) strcpy(nbuf, actionstr);
nlen = strlen(nbuf);
plen = nlen;
if (ptr && !str_found) {
/* now put the first line that we read in fgets */
nlen = plen + strlen(line) + 1;
len = snprintf(nbuf + plen, nlen - plen, "%s", line);
if (len >= nsize) {
free(nbuf);
return (-1);
}
plen += len;
}
/* now get the rest of the old file */
while (fgets(line, MAX_CANON, dafp) != NULL) {
nlen = plen + strlen(line) + 1;
len = snprintf(nbuf + plen, nlen - plen, "%s", line);
if (len >= nsize) {
free(nbuf);
return (-1);
}
plen += len;
}
len = strlen(nbuf) + 1;
if (len < nsize)
nbuf[len] = '\n';
/* write the on/off str + the old device_allocate to the temp file */
if (fwrite(nbuf, nsize, nitems, tafp) < nitems) {
free(nbuf);
return (-1);
}
free(nbuf);
return (0);
}
/*
* da_update_defattrs -
* writes default attributes to devalloc_defaults
* returns 0 on success, -1 on error.
*/
int
da_update_defattrs(da_args *dargs)
{
int rc = 0, lockfd = 0, tmpfd = 0;
char *defpath = DEFATTRS;
char *tmpdefpath = TMPATTRS;
FILE *tmpfp = NULL;
struct stat dstat;
strentry_t *head_defent = NULL;
if (dargs == NULL)
return (0);
if ((lockfd = _da_lock_devdb(NULL)) == -1)
return (-1);
if ((tmpfd = open(tmpdefpath, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
(void) close(lockfd);
return (-1);
}
(void) fchown(tmpfd, DA_UID, DA_GID);
if ((tmpfp = fdopen(tmpfd, "r+")) == NULL) {
(void) close(tmpfd);
(void) unlink(tmpdefpath);
(void) close(lockfd);
return (-1);
}
/*
* examine all entries, remove an old one if required, check
* if a new one needs to be added.
*/
if (stat(defpath, &dstat) == 0) {
if ((rc = _build_defattrs(dargs, &head_defent)) != 0) {
if (rc == 1) {
(void) close(tmpfd);
(void) unlink(tmpdefpath);
(void) close(lockfd);
return (rc);
}
}
}
/*
* write back any existing entries.
*/
_write_defattrs(tmpfp, head_defent);
if (dargs->optflag & DA_ADD && !(dargs->optflag & DA_NO_OVERRIDE)) {
/* add new entries */
rc = _write_new_defattrs(tmpfp, dargs);
(void) fclose(tmpfp);
} else {
(void) fclose(tmpfp);
}
if (rename(tmpdefpath, defpath) != 0) {
rc = -1;
(void) unlink(tmpdefpath);
}
(void) close(lockfd);
return (rc);
}
/*
* da_update_device -
* writes devices entries to device_allocate and device_maps.
* returns 0 on success, -1 on error.
*/
int
da_update_device(da_args *dargs)
{
int rc;
int tafd = -1, tmfd = -1;
int lockfd = -1;
char *rootdir = NULL;
char *apathp = NULL, *mpathp = NULL;
char *dapathp = NULL, *dmpathp = NULL;
char apath[MAXPATHLEN], mpath[MAXPATHLEN];
char dapath[MAXPATHLEN], dmpath[MAXPATHLEN];
FILE *tafp = NULL, *tmfp = NULL, *dafp = NULL;
struct stat dastat;
devinfo_t *devinfo;
strentry_t *head_devmapp = NULL;
strentry_t *head_devallocp = NULL;
if (dargs == NULL)
return (0);
rootdir = dargs->rootdir;
devinfo = dargs->devinfo;
/*
* adding/removing entries should be done in both
* device_allocate and device_maps. updates can be
* done in both or either of the files.
*/
if (dargs->optflag & DA_ADD || dargs->optflag & DA_REMOVE) {
if (dargs->optflag & DA_ALLOC_ONLY ||
dargs->optflag & DA_MAPS_ONLY)
return (0);
}
/*
* name, type and list are required fields for adding a new
* device.
*/
if ((dargs->optflag & DA_ADD) &&
((devinfo->devname == NULL) ||
(devinfo->devtype == NULL) ||
(devinfo->devlist == NULL))) {
return (-1);
}
if (rootdir != NULL) {
if (snprintf(apath, sizeof (apath), "%s%s", rootdir,
TMPALLOC) >= sizeof (apath))
return (-1);
apathp = apath;
if (snprintf(dapath, sizeof (dapath), "%s%s", rootdir,
DEVALLOC) >= sizeof (dapath))
return (-1);
dapathp = dapath;
if (!(dargs->optflag & DA_ALLOC_ONLY)) {
if (snprintf(mpath, sizeof (mpath), "%s%s", rootdir,
TMPMAP) >= sizeof (mpath))
return (-1);
mpathp = mpath;
if (snprintf(dmpath, sizeof (dmpath), "%s%s", rootdir,
DEVMAP) >= sizeof (dmpath))
return (-1);
dmpathp = dmpath;
}
} else {
apathp = TMPALLOC;
dapathp = DEVALLOC;
mpathp = TMPMAP;
dmpathp = DEVMAP;
}
if (dargs->optflag & DA_MAPS_ONLY)
goto dmap_only;
/*
* Check if we are here just to record on/off status of
* device_allocation.
*/
if (dargs->optflag & DA_ON || dargs->optflag & DA_OFF)
lockfd = da_open_devdb(dargs->rootdir, &dafp, NULL,
DA_RDONLY|DA_ALLOC_ONLY);
else
lockfd = _da_lock_devdb(rootdir);
if (lockfd == -1)
return (-1);
if ((tafd = open(apathp, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
(void) close(lockfd);
(void) fclose(dafp);
return (-1);
}
(void) fchown(tafd, DA_UID, DA_GID);
if ((tafp = fdopen(tafd, "r+")) == NULL) {
(void) close(tafd);
(void) unlink(apathp);
(void) fclose(dafp);
(void) close(lockfd);
return (-1);
}
/*
* We don't need to parse the file if we are here just to record
* on/off status of device_allocation.
*/
if (dargs->optflag & DA_ON || dargs->optflag & DA_OFF) {
if (_record_on_off(dargs, tafp, dafp) == -1) {
(void) close(tafd);
(void) unlink(apathp);
(void) fclose(dafp);
(void) close(lockfd);
return (-1);
}
(void) fclose(dafp);
goto out;
}
/*
* examine all the entries, remove an old one if forced to,
* and check that they are suitable for updating.
* we need to do this only if the file exists already.
*/
if (stat(dapathp, &dastat) == 0) {
if ((rc = _build_lists(dargs, &head_devallocp,
&head_devmapp)) != 0) {
if (rc != 1) {
(void) close(tafd);
(void) unlink(apathp);
(void) close(lockfd);
return (rc);
}
}
}
/*
* write back any existing devalloc entries, along with
* the devalloc on/off string.
*/
_write_device_allocate(dapathp, tafp, head_devallocp);
if (dargs->optflag & DA_ALLOC_ONLY)
goto out;
dmap_only:
if ((tmfd = open(mpathp, O_RDWR|O_CREAT, DA_DBMODE)) == -1) {
(void) close(tafd);
(void) unlink(apathp);
(void) close(lockfd);
return (-1);
}
(void) fchown(tmfd, DA_UID, DA_GID);
if ((tmfp = fdopen(tmfd, "r+")) == NULL) {
(void) close(tafd);
(void) unlink(apathp);
(void) close(tmfd);
(void) unlink(mpathp);
(void) close(lockfd);
return (-1);
}
/* write back any existing devmap entries */
if (head_devmapp != NULL)
_write_device_maps(tmfp, head_devmapp);
out:
if (dargs->optflag & DA_ADD && !(dargs->optflag & DA_NO_OVERRIDE)) {
/* add any new entries */
rc = _write_new_entry(tafp, dargs, DA_ALLOC_ONLY);
(void) fclose(tafp);
if (rc == 0)
rc = _write_new_entry(tmfp, dargs, DA_MAPS_ONLY);
(void) fclose(tmfp);
} else {
if (tafp)
(void) fclose(tafp);
if (tmfp)
(void) fclose(tmfp);
}
rc = 0;
if (!(dargs->optflag & DA_MAPS_ONLY)) {
if (rename(apathp, dapathp) != 0) {
rc = -1;
(void) unlink(apathp);
}
}
if (!(dargs->optflag & DA_ALLOC_ONLY)) {
if (rename(mpathp, dmpathp) != 0) {
rc = -1;
(void) unlink(mpathp);
}
}
(void) close(lockfd);
return (rc);
}
/*
* da_add_list -
* adds new /dev link name to the linked list of devices.
* returns 0 if link added successfully, -1 on error.
*/
int
da_add_list(devlist_t *dlist, char *link, int new_instance, int flag)
{
int instance;
int nlen, plen;
int new_entry = 0;
char *dtype, *dexec, *tname, *kval;
char *minstr = NULL, *maxstr = NULL;
char dname[DA_MAXNAME];
kva_t *kva;
deventry_t *dentry = NULL, *nentry = NULL, *pentry = NULL;
da_defs_t *da_defs;
if (dlist == NULL || link == NULL)
return (-1);
dname[0] = '\0';
if (flag & DA_AUDIO) {
dentry = dlist->audio;
tname = DA_AUDIO_NAME;
dtype = DA_AUDIO_TYPE;
dexec = DA_DEFAULT_AUDIO_CLEAN;
} else if (flag & DA_CD) {
dentry = dlist->cd;
tname = DA_CD_NAME;
dtype = DA_CD_TYPE;
dexec = DA_DEFAULT_DISK_CLEAN;
} else if (flag & DA_FLOPPY) {
dentry = dlist->floppy;
tname = DA_FLOPPY_NAME;
dtype = DA_FLOPPY_TYPE;
dexec = DA_DEFAULT_DISK_CLEAN;
} else if (flag & DA_TAPE) {
dentry = dlist->tape;
tname = DA_TAPE_NAME;
dtype = DA_TAPE_TYPE;
dexec = DA_DEFAULT_TAPE_CLEAN;
} else if (flag & DA_RMDISK) {
dentry = dlist->rmdisk;
tname = DA_RMDISK_NAME;
dtype = DA_RMDISK_TYPE;
dexec = DA_DEFAULT_DISK_CLEAN;
} else {
return (-1);
}
for (nentry = dentry; nentry != NULL; nentry = nentry->next) {
pentry = nentry;
(void) sscanf(nentry->devinfo.devname, "%*[a-z]%d", &instance);
if (nentry->devinfo.instance == new_instance)
/*
* Add the new link name to the list of links
* that the device 'dname' has.
*/
break;
}
if (nentry == NULL) {
/*
* Either this is the first entry ever, or no matching entry
* was found. Create a new one and add to the list.
*/
if (dentry == NULL) /* first entry ever */
instance = 0;
else /* no matching entry */
instance++;
(void) snprintf(dname, sizeof (dname), "%s%d", tname, instance);
if ((nentry = (deventry_t *)malloc(sizeof (deventry_t))) ==
NULL)
return (-1);
if (pentry != NULL)
pentry->next = nentry;
new_entry = 1;
nentry->devinfo.devname = strdup(dname);
nentry->devinfo.devtype = dtype;
nentry->devinfo.devauths = DEFAULT_DEV_ALLOC_AUTH;
nentry->devinfo.devexec = dexec;
nentry->devinfo.instance = new_instance;
/*
* Look for default label range, authorizations and cleaning
* program in devalloc_defaults. If label range is not
* specified in devalloc_defaults, assume it to be admin_low
* to admin_high.
*/
minstr = DA_DEFAULT_MIN;
maxstr = DA_DEFAULT_MAX;
setdadefent();
if (da_defs = getdadeftype(nentry->devinfo.devtype)) {
kva = da_defs->devopts;
if ((kval = kva_match(kva, DAOPT_MINLABEL)) != NULL)
minstr = strdup(kval);
if ((kval = kva_match(kva, DAOPT_MAXLABEL)) != NULL)
maxstr = strdup(kval);
if ((kval = kva_match(kva, DAOPT_AUTHS)) != NULL)
nentry->devinfo.devauths = strdup(kval);
if ((kval = kva_match(kva, DAOPT_CSCRIPT)) != NULL)
nentry->devinfo.devexec = strdup(kval);
freedadefent(da_defs);
}
enddadefent();
kval = NULL;
nlen = strlen(DAOPT_MINLABEL) + strlen(KV_ASSIGN) +
strlen(minstr) + strlen(KV_TOKEN_DELIMIT) +
strlen(DAOPT_MAXLABEL) + strlen(KV_ASSIGN) + strlen(maxstr)
+ 1; /* +1 for terminator */
if (kval = (char *)malloc(nlen))
(void) snprintf(kval, nlen, "%s%s%s%s%s%s%s",
DAOPT_MINLABEL, KV_ASSIGN, minstr, KV_TOKEN_DELIMIT,
DAOPT_MAXLABEL, KV_ASSIGN, maxstr);
nentry->devinfo.devopts = kval;
nentry->devinfo.devlist = NULL;
nentry->next = NULL;
}
nlen = strlen(link) + 1; /* +1 terminator */
if (nentry->devinfo.devlist) {
plen = strlen(nentry->devinfo.devlist);
nlen = nlen + plen + 1; /* +1 for blank to separate entries */
} else {
plen = 0;
}
if ((nentry->devinfo.devlist =
(char *)realloc(nentry->devinfo.devlist, nlen)) == NULL) {
if (new_entry) {
nentry->devinfo.devname = NULL;
free(nentry->devinfo.devname);
nentry = NULL;
free(nentry);
if (pentry != NULL)
pentry->next = NULL;
}
return (-1);
}
if (plen == 0)
(void) snprintf(nentry->devinfo.devlist, nlen, "%s", link);
else
(void) snprintf(nentry->devinfo.devlist + plen, nlen - plen,
" %s", link);
if (pentry == NULL) {
/*
* This is the first entry of this device type.
*/
if (flag & DA_AUDIO)
dlist->audio = nentry;
else if (flag & DA_CD)
dlist->cd = nentry;
else if (flag & DA_FLOPPY)
dlist->floppy = nentry;
else if (flag & DA_TAPE)
dlist->tape = nentry;
else if (flag & DA_RMDISK)
dlist->rmdisk = nentry;
}
return (0);
}
/*
* da_remove_list -
* removes a /dev link name from the linked list of devices.
* returns type of device if link for that device removed
* successfully, else returns -1 on error.
* if all links for a device are removed, stores that device
* name in devname.
*/
int
da_remove_list(devlist_t *dlist, char *link, int type, char *devname, int size)
{
int flag;
int remove_dev = 0;
int nlen, plen, slen;
char *lasts, *lname, *oldlist;
struct stat rmstat;
deventry_t *dentry, *current, *prev;
if (type != NULL)
flag = type;
else if (link == NULL)
return (-1);
else if (strstr(link, DA_AUDIO_NAME) || strstr(link, DA_SOUND_NAME))
flag = DA_AUDIO;
else if (strstr(link, "dsk") || strstr(link, "rdsk") ||
strstr(link, "sr") || strstr(link, "rsr"))
flag = DA_CD;
else if (strstr(link, "fd") || strstr(link, "rfd") ||
strstr(link, "diskette") || strstr(link, "rdiskette"))
flag = DA_FLOPPY;
else if (strstr(link, DA_TAPE_NAME))
flag = DA_TAPE;
else
flag = DA_RMDISK;
switch (type) {
case DA_AUDIO:
dentry = dlist->audio;
break;
case DA_CD:
dentry = dlist->cd;
break;
case DA_FLOPPY:
dentry = dlist->floppy;
break;
case DA_TAPE:
dentry = dlist->tape;
break;
case DA_RMDISK:
dentry = dlist->rmdisk;
break;
default:
return (-1);
}
if ((type != NULL) && (link == NULL)) {
for (current = dentry, prev = dentry; current != NULL;
current = current->next) {
oldlist = strdup(current->devinfo.devlist);
for (lname = strtok_r(oldlist, " ", &lasts);
lname != NULL;
lname = strtok_r(NULL, " ", &lasts)) {
if (stat(lname, &rmstat) != 0) {
remove_dev = 1;
goto remove_dev;
}
}
prev = current;
}
return (-1);
}
for (current = dentry, prev = dentry; current != NULL;
current = current->next) {
plen = strlen(current->devinfo.devlist);
nlen = strlen(link);
if (plen == nlen) {
if (strcmp(current->devinfo.devlist, link) == 0) {
/* last name in the list */
remove_dev = 1;
break;
}
}
if (strstr(current->devinfo.devlist, link)) {
nlen = plen - nlen + 1;
oldlist = strdup(current->devinfo.devlist);
if ((current->devinfo.devlist =
(char *)realloc(current->devinfo.devlist,
nlen)) == NULL) {
free(oldlist);
return (-1);
}
current->devinfo.devlist[0] = '\0';
nlen = plen = slen = 0;
for (lname = strtok_r(oldlist, " ", &lasts);
lname != NULL;
lname = strtok_r(NULL, " ", &lasts)) {
if (strcmp(lname, link) == 0)
continue;
nlen = strlen(lname) + plen + 1;
if (plen == 0) {
slen =
snprintf(current->devinfo.devlist,
nlen, "%s", lname);
} else {
slen =
snprintf(current->devinfo.devlist +
plen, nlen - plen, " %s", lname);
}
plen = plen + slen + 1;
}
free(oldlist);
break;
}
prev = current;
}
remove_dev:
if (remove_dev == 1) {
(void) strlcpy(devname, current->devinfo.devname, size);
free(current->devinfo.devname);
free(current->devinfo.devlist);
current->devinfo.devname = current->devinfo.devlist = NULL;
prev->next = current->next;
free(current);
current = NULL;
}
if ((remove_dev == 1) && (prev->devinfo.devname == NULL)) {
if (prev->next) {
/*
* what we removed above was the first entry
* in the list. make the next entry to be the
* first.
*/
current = prev->next;
} else {
/*
* the matching entry was the only entry in the list
* for this type.
*/
current = NULL;
}
if (flag & DA_AUDIO)
dlist->audio = current;
else if (flag & DA_CD)
dlist->cd = current;
else if (flag & DA_FLOPPY)
dlist->floppy = current;
else if (flag & DA_TAPE)
dlist->tape = current;
else if (flag & DA_RMDISK)
dlist->rmdisk = current;
}
return (flag);
}
/*
* da_is_on -
* checks if device allocation feature is turned on.
* returns 1 if on, 0 if off, -1 if status string not
* found in device_allocate.
*/
int
da_is_on()
{
return (getdaon());
}
/*
* da_print_device -
* debug routine to print device entries.
*/
void
da_print_device(int flag, devlist_t *devlist)
{
deventry_t *entry, *dentry;
devinfo_t *devinfo;
if (flag & DA_AUDIO)
dentry = devlist->audio;
else if (flag & DA_CD)
dentry = devlist->cd;
else if (flag & DA_FLOPPY)
dentry = devlist->floppy;
else if (flag & DA_TAPE)
dentry = devlist->tape;
else if (flag & DA_RMDISK)
dentry = devlist->rmdisk;
else
return;
for (entry = dentry; entry != NULL; entry = entry->next) {
devinfo = &(entry->devinfo);
(void) fprintf(stdout, "name: %s\n", devinfo->devname);
(void) fprintf(stdout, "type: %s\n", devinfo->devtype);
(void) fprintf(stdout, "auth: %s\n", devinfo->devauths);
(void) fprintf(stdout, "exec: %s\n", devinfo->devexec);
(void) fprintf(stdout, "list: %s\n\n", devinfo->devlist);
}
}