#
# 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.
#
PROG= dlmgmtd
OBJS= dlmgmt_main.o dlmgmt_door.o dlmgmt_util.o dlmgmt_db.o
SRCS= $(OBJS:.o=.c)
SVCMETHOD= svc-dlmgmtd
MANIFEST= dlmgmt.xml
CFGFILES= datalink.conf
include ../Makefile.cmd
ROOTMANIFESTDIR= $(ROOTSVCNETWORK)
ROOTCFGDIR= $(ROOTETC)/dladm
ROOTCFGFILES= $(CFGFILES:%=$(ROOTCFGDIR)/%)
$(ROOTCFGDIR)/datalink.conf : FILEMODE= 644
LDLIBS += -ldladm -ldlpi -lavl -lnvpair -lsysevent -lcontract
CERRWARN += $(CNOWARN_UNINIT)
#
# Instrument dlmgmtd with CTF data to ease debugging.
#
CTFCONVERT_HOOK = && $(CTFCONVERT_O)
CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS)
$(OBJS) : CFLAGS += $(CTF_FLAGS)
.KEEP_STATE:
all: $(PROG)
$(PROG): $(OBJS)
$(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(CTFMERGE_HOOK)
$(POST_PROCESS)
install: all $(ROOTSBINPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) $(ROOTCFGDIR) \
$(ROOTCFGFILES)
check: $(CHKMANIFEST)
clean:
$(RM) $(OBJS)
$(ROOTCFGDIR):
$(INS.dir)
# Hammerhead: order-only prerequisite so $< is the file, not the directory
$(ROOTCFGDIR)/%: % | $(ROOTCFGDIR)
$(INS.file)
include ../Makefile.targ
#
# 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 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# DO NOT EDIT OR PARSE THIS FILE!
#
# Use the dladm(8) command to change the contents of this file.
data-link management daemon
/*
* 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2023 Oxide Computer Company
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dlmgmt_impl.h"
typedef enum dlmgmt_db_op {
DLMGMT_DB_OP_WRITE,
DLMGMT_DB_OP_DELETE,
DLMGMT_DB_OP_READ
} dlmgmt_db_op_t;
typedef struct dlmgmt_db_req_s {
struct dlmgmt_db_req_s *ls_next;
dlmgmt_db_op_t ls_op;
char ls_link[MAXLINKNAMELEN];
datalink_id_t ls_linkid;
zoneid_t ls_zoneid;
uint32_t ls_flags; /* Either DLMGMT_ACTIVE or */
/* DLMGMT_PERSIST, not both. */
} dlmgmt_db_req_t;
/*
* List of pending db updates (e.g., because of a read-only filesystem).
*/
static dlmgmt_db_req_t *dlmgmt_db_req_head = NULL;
static dlmgmt_db_req_t *dlmgmt_db_req_tail = NULL;
/*
* rewrite_needed is set to B_TRUE by process_link_line() if it encounters a
* line with an old format. This will cause the file being read to be
* re-written with the current format.
*/
static boolean_t rewrite_needed;
static int dlmgmt_db_update(dlmgmt_db_op_t, const char *,
dlmgmt_link_t *, uint32_t);
static int dlmgmt_process_db_req(dlmgmt_db_req_t *);
static int dlmgmt_process_db_onereq(dlmgmt_db_req_t *, boolean_t);
static void *dlmgmt_db_update_thread(void *);
static boolean_t process_link_line(char *, dlmgmt_link_t *);
static int process_db_write(dlmgmt_db_req_t *, FILE *, FILE *);
static int process_db_read(dlmgmt_db_req_t *, FILE *);
static void generate_link_line(dlmgmt_link_t *, boolean_t, char *);
#define BUFLEN(lim, ptr) (((lim) > (ptr)) ? ((lim) - (ptr)) : 0)
#define MAXLINELEN 1024
typedef void db_walk_func_t(dlmgmt_link_t *);
/*
* Translator functions to go from dladm_datatype_t to character strings.
* Each function takes a pointer to a buffer, the size of the buffer,
* the name of the attribute, and the value to be written. The functions
* return the number of bytes written to the buffer. If the buffer is not big
* enough to hold the string representing the value, then nothing is written
* and 0 is returned.
*/
typedef size_t write_func_t(char *, size_t, char *, void *);
/*
* Translator functions to read from a NULL terminated string buffer into
* something of the given DLADM_TYPE_*. The functions each return the number
* of bytes read from the string buffer. If there is an error reading data
* from the buffer, then 0 is returned. It is the caller's responsibility
* to free the data allocated by these functions.
*/
typedef size_t read_func_t(char *, void **);
typedef struct translator_s {
const char *type_name;
write_func_t *write_func;
read_func_t *read_func;
} translator_t;
/*
* Translator functions, defined later but declared here so that
* the translator table can be defined.
*/
static write_func_t write_str, write_boolean, write_uint64;
static read_func_t read_str, read_boolean, read_int64;
/*
* Translator table, indexed by dladm_datatype_t.
*/
static translator_t translators[] = {
{ "string", write_str, read_str },
{ "boolean", write_boolean, read_boolean },
{ "int", write_uint64, read_int64 }
};
static size_t ntranslators = sizeof (translators) / sizeof (translator_t);
#define LINK_PROPERTY_DELIMINATOR ";"
#define LINK_PROPERTY_TYPE_VALUE_SEP ","
#define BASE_PROPERTY_LENGTH(t, n) (strlen(translators[(t)].type_name) +\
strlen(LINK_PROPERTY_TYPE_VALUE_SEP) +\
strlen(LINK_PROPERTY_DELIMINATOR) +\
strlen((n)))
#define GENERATE_PROPERTY_STRING(buf, length, conv, name, type, val) \
(snprintf((buf), (length), "%s=%s%s" conv "%s", (name), \
translators[(type)].type_name, \
LINK_PROPERTY_TYPE_VALUE_SEP, (val), LINK_PROPERTY_DELIMINATOR))
/*
* Name of the cache file to keep the active mapping
*/
char cachefile[MAXPATHLEN];
#define DLMGMT_PERSISTENT_DB_PATH "/etc/dladm/datalink.conf"
#define DLMGMT_MAKE_FILE_DB_PATH(buffer, persistent) \
(void) snprintf((buffer), MAXPATHLEN, "%s", \
(persistent) ? DLMGMT_PERSISTENT_DB_PATH : cachefile);
typedef struct zopen_arg {
const char *zopen_modestr;
int *zopen_pipe;
int zopen_fd;
} zopen_arg_t;
typedef struct zrename_arg {
const char *zrename_newname;
} zrename_arg_t;
typedef union zfoparg {
zopen_arg_t zfop_openarg;
zrename_arg_t zfop_renamearg;
} zfoparg_t;
typedef struct zfcbarg {
boolean_t zfarg_inglobalzone; /* is callback in global zone? */
zoneid_t zfarg_finglobalzone; /* is file in global zone? */
const char *zfarg_filename;
zfoparg_t *zfarg_oparg;
} zfarg_t;
#define zfarg_openarg zfarg_oparg->zfop_openarg
#define zfarg_renamearg zfarg_oparg->zfop_renamearg
/* zone file callback */
typedef int zfcb_t(zfarg_t *);
/*
* Execute an operation on filename relative to zoneid's zone root. If the
* file is in the global zone, then the zfcb() callback will simply be called
* directly. If the file is in a non-global zone, then zfcb() will be called
* both from the global zone's context, and from the non-global zone's context
* (from a fork()'ed child that has entered the non-global zone). This is
* done to allow the callback to communicate with itself if needed (e.g. to
* pass back the file descriptor of an opened file).
*/
static int
dlmgmt_zfop(const char *filename, zoneid_t zoneid, zfcb_t *zfcb,
zfoparg_t *zfoparg)
{
int ctfd;
int err;
pid_t childpid;
siginfo_t info;
zfarg_t zfarg;
ctid_t ct;
if (zoneid != GLOBAL_ZONEID) {
/*
* We need to access a file that isn't in the global zone.
* Accessing non-global zone files from the global zone is
* unsafe (due to symlink attacks), we'll need to fork a child
* that enters the zone in question and executes the callback
* that will operate on the file.
*
* Before we proceed with this zone tango, we need to create a
* new process contract for the child, as required by
* zone_enter().
*/
errno = 0;
ctfd = open64("/system/contract/process/template", O_RDWR);
if (ctfd == -1)
return (errno);
if ((err = ct_tmpl_set_critical(ctfd, 0)) != 0 ||
(err = ct_tmpl_set_informative(ctfd, 0)) != 0 ||
(err = ct_pr_tmpl_set_fatal(ctfd, CT_PR_EV_HWERR)) != 0 ||
(err = ct_pr_tmpl_set_param(ctfd, CT_PR_PGRPONLY)) != 0 ||
(err = ct_tmpl_activate(ctfd)) != 0) {
(void) close(ctfd);
return (err);
}
childpid = fork();
switch (childpid) {
case -1:
(void) ct_tmpl_clear(ctfd);
(void) close(ctfd);
return (err);
case 0:
(void) ct_tmpl_clear(ctfd);
(void) close(ctfd);
/*
* Elevate our privileges as zone_enter() requires all
* privileges.
*/
if ((err = dlmgmt_elevate_privileges()) != 0)
_exit(err);
if (zone_enter(zoneid) == -1)
_exit(errno);
if ((err = dlmgmt_drop_privileges()) != 0)
_exit(err);
break;
default:
if (contract_latest(&ct) == -1)
ct = -1;
(void) ct_tmpl_clear(ctfd);
(void) close(ctfd);
if (waitid(P_PID, childpid, &info, WEXITED) == -1) {
(void) contract_abandon_id(ct);
return (errno);
}
(void) contract_abandon_id(ct);
if (info.si_status != 0)
return (info.si_status);
}
}
zfarg.zfarg_inglobalzone = (zoneid == GLOBAL_ZONEID || childpid != 0);
zfarg.zfarg_finglobalzone = (zoneid == GLOBAL_ZONEID);
zfarg.zfarg_filename = filename;
zfarg.zfarg_oparg = zfoparg;
err = zfcb(&zfarg);
if (!zfarg.zfarg_inglobalzone)
_exit(err);
return (err);
}
static int
dlmgmt_zopen_cb(zfarg_t *zfarg)
{
struct strrecvfd recvfd;
boolean_t newfile = B_FALSE;
boolean_t inglobalzone = zfarg->zfarg_inglobalzone;
zoneid_t finglobalzone = zfarg->zfarg_finglobalzone;
const char *filename = zfarg->zfarg_filename;
const char *modestr = zfarg->zfarg_openarg.zopen_modestr;
int *p = zfarg->zfarg_openarg.zopen_pipe;
struct stat statbuf;
int oflags;
mode_t mode;
int fd = -1;
int err;
/* We only ever open a file for reading or writing, not both. */
oflags = (modestr[0] == 'r') ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC;
mode = (modestr[0] == 'r') ? 0 : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
/* Open the file if we're in the same zone as the file. */
if (inglobalzone == finglobalzone) {
/*
* First determine if we will be creating the file as part of
* opening it. If so, then we'll need to ensure that it has
* the proper ownership after having opened it.
*/
if (oflags & O_CREAT) {
if (stat(filename, &statbuf) == -1) {
if (errno == ENOENT)
newfile = B_TRUE;
else
return (errno);
}
}
if ((fd = open(filename, oflags, mode)) == -1)
return (errno);
if (newfile) {
if (chown(filename, UID_DLADM, GID_NETADM) == -1) {
err = errno;
(void) close(fd);
return (err);
}
}
}
/*
* If we're not in the global zone, send the file-descriptor back to
* our parent in the global zone.
*/
if (!inglobalzone) {
assert(!finglobalzone);
assert(fd != -1);
return (ioctl(p[1], I_SENDFD, fd) == -1 ? errno : 0);
}
/*
* At this point, we know we're in the global zone. If the file was
* in a non-global zone, receive the file-descriptor from our child in
* the non-global zone.
*/
if (!finglobalzone) {
if (ioctl(p[0], I_RECVFD, &recvfd) == -1)
return (errno);
fd = recvfd.fd;
}
zfarg->zfarg_openarg.zopen_fd = fd;
return (0);
}
static int
dlmgmt_zunlink_cb(zfarg_t *zfarg)
{
if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
return (0);
return (unlink(zfarg->zfarg_filename) == 0 ? 0 : errno);
}
static int
dlmgmt_zrename_cb(zfarg_t *zfarg)
{
if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
return (0);
return (rename(zfarg->zfarg_filename,
zfarg->zfarg_renamearg.zrename_newname) == 0 ? 0 : errno);
}
/*
* Same as fopen(3C), except that it opens the file relative to zoneid's zone
* root.
*/
static FILE *
dlmgmt_zfopen(const char *filename, const char *modestr, zoneid_t zoneid,
int *err)
{
int p[2];
zfoparg_t zfoparg;
FILE *fp = NULL;
if (zoneid != GLOBAL_ZONEID && pipe(p) == -1) {
*err = errno;
return (NULL);
}
zfoparg.zfop_openarg.zopen_modestr = modestr;
zfoparg.zfop_openarg.zopen_pipe = p;
*err = dlmgmt_zfop(filename, zoneid, dlmgmt_zopen_cb, &zfoparg);
if (zoneid != GLOBAL_ZONEID) {
(void) close(p[0]);
(void) close(p[1]);
}
if (*err == 0) {
fp = fdopen(zfoparg.zfop_openarg.zopen_fd, modestr);
if (fp == NULL) {
*err = errno;
(void) close(zfoparg.zfop_openarg.zopen_fd);
}
}
return (fp);
}
/*
* Same as rename(2), except that old and new are relative to zoneid's zone
* root.
*/
static int
dlmgmt_zrename(const char *old, const char *new, zoneid_t zoneid)
{
zfoparg_t zfoparg;
zfoparg.zfop_renamearg.zrename_newname = new;
return (dlmgmt_zfop(old, zoneid, dlmgmt_zrename_cb, &zfoparg));
}
/*
* Same as unlink(2), except that filename is relative to zoneid's zone root.
*/
static int
dlmgmt_zunlink(const char *filename, zoneid_t zoneid)
{
return (dlmgmt_zfop(filename, zoneid, dlmgmt_zunlink_cb, NULL));
}
static size_t
write_str(char *buffer, size_t buffer_length, char *name, void *value)
{
char *ptr = value;
size_t data_length = strnlen(ptr, buffer_length);
/*
* Strings are assumed to be NULL terminated. In order to fit in
* the buffer, the string's length must be less then buffer_length.
* If the value is empty, there's no point in writing it, in fact,
* we shouldn't even see that case.
*/
if (data_length + BASE_PROPERTY_LENGTH(DLADM_TYPE_STR, name) ==
buffer_length || data_length == 0)
return (0);
/*
* Since we know the string will fit in the buffer, snprintf will
* always return less than buffer_length, so we can just return
* whatever snprintf returns.
*/
return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%s",
name, DLADM_TYPE_STR, ptr));
}
static size_t
write_boolean(char *buffer, size_t buffer_length, char *name, void *value)
{
boolean_t *ptr = value;
/*
* Booleans are either zero or one, so we only need room for two
* characters in the buffer.
*/
if (buffer_length <= 1 + BASE_PROPERTY_LENGTH(DLADM_TYPE_BOOLEAN, name))
return (0);
return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%d",
name, DLADM_TYPE_BOOLEAN, *ptr));
}
static size_t
write_uint64(char *buffer, size_t buffer_length, char *name, void *value)
{
uint64_t *ptr = value;
/*
* Limit checking for uint64_t is a little trickier.
*/
if (snprintf(NULL, 0, "%lld", *ptr) +
BASE_PROPERTY_LENGTH(DLADM_TYPE_UINT64, name) >= buffer_length)
return (0);
return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%lld",
name, DLADM_TYPE_UINT64, *ptr));
}
static size_t
read_str(char *buffer, void **value)
{
char *ptr = calloc(MAXLINKATTRVALLEN, sizeof (char));
ssize_t len;
if (ptr == NULL || (len = strlcpy(ptr, buffer, MAXLINKATTRVALLEN))
>= MAXLINKATTRVALLEN) {
free(ptr);
return (0);
}
*(char **)value = ptr;
/* Account for NULL terminator */
return (len + 1);
}
static size_t
read_boolean(char *buffer, void **value)
{
boolean_t *ptr = calloc(1, sizeof (boolean_t));
if (ptr == NULL)
return (0);
*ptr = atoi(buffer);
*(boolean_t **)value = ptr;
return (sizeof (boolean_t));
}
static size_t
read_int64(char *buffer, void **value)
{
int64_t *ptr = calloc(1, sizeof (int64_t));
if (ptr == NULL)
return (0);
*ptr = (int64_t)atoll(buffer);
*(int64_t **)value = ptr;
return (sizeof (int64_t));
}
static dlmgmt_db_req_t *
dlmgmt_db_req_alloc(dlmgmt_db_op_t op, const char *linkname,
datalink_id_t linkid, zoneid_t zoneid, uint32_t flags, int *err)
{
dlmgmt_db_req_t *req;
if ((req = calloc(1, sizeof (dlmgmt_db_req_t))) == NULL) {
*err = errno;
} else {
req->ls_op = op;
if (linkname != NULL)
(void) strlcpy(req->ls_link, linkname, MAXLINKNAMELEN);
req->ls_linkid = linkid;
req->ls_zoneid = zoneid;
req->ls_flags = flags;
}
return (req);
}
/*
* Update the db entry with name "entryname" using information from "linkp".
*/
static int
dlmgmt_db_update(dlmgmt_db_op_t op, const char *entryname, dlmgmt_link_t *linkp,
uint32_t flags)
{
dlmgmt_db_req_t *req;
int err;
/* It is either a persistent request or an active request, not both. */
assert((flags == DLMGMT_PERSIST) || (flags == DLMGMT_ACTIVE));
if ((req = dlmgmt_db_req_alloc(op, entryname, linkp->ll_linkid,
linkp->ll_zoneid, flags, &err)) == NULL) {
return (err);
}
/*
* If this is a transient link, then use the global zone cache file.
* This is in order to allow recovery from a dlmgmtd failure that
* leaves a zone in a 'down' state. In that state it is not possible
* to read the zone's cache file (since it is always done from a sub
* process running in the zone's context). As a result, datalinks would
* otherwise remain stuck in the zone.
*/
if (flags == DLMGMT_ACTIVE && linkp->ll_transient)
req->ls_zoneid = GLOBAL_ZONEID;
/*
* If the return error is EINPROGRESS, this request is handled
* asynchronously; return success.
*/
err = dlmgmt_process_db_req(req);
if (err != EINPROGRESS)
free(req);
else
err = 0;
return (err);
}
#define DLMGMT_DB_OP_STR(op) \
(((op) == DLMGMT_DB_OP_READ) ? "read" : \
(((op) == DLMGMT_DB_OP_WRITE) ? "write" : "delete"))
#define DLMGMT_DB_CONF_STR(flag) \
(((flag) == DLMGMT_ACTIVE) ? "active" : \
(((flag) == DLMGMT_PERSIST) ? "persistent" : ""))
static int
dlmgmt_process_db_req(dlmgmt_db_req_t *req)
{
pthread_t tid;
boolean_t writeop;
int err;
/*
* If there are already pending "write" requests, queue this request in
* the pending list. Note that this function is called while the
* dlmgmt_rw_lock is held, so it is safe to access the global variables.
*/
writeop = (req->ls_op != DLMGMT_DB_OP_READ);
if (writeop && (req->ls_flags == DLMGMT_PERSIST) &&
(dlmgmt_db_req_head != NULL)) {
dlmgmt_db_req_tail->ls_next = req;
dlmgmt_db_req_tail = req;
return (EINPROGRESS);
}
err = dlmgmt_process_db_onereq(req, writeop);
if (err != EINPROGRESS && err != 0 && err != ENOENT) {
/*
* Log the error unless the request processing is still in
* progress or if the configuration file hasn't been created
* yet (ENOENT).
*/
dlmgmt_log(LOG_WARNING, "dlmgmt_process_db_onereq() %s "
"operation on %s configuration failed: %s",
DLMGMT_DB_OP_STR(req->ls_op),
DLMGMT_DB_CONF_STR(req->ls_flags), strerror(err));
}
if (err == EINPROGRESS) {
assert(req->ls_flags == DLMGMT_PERSIST);
assert(writeop && dlmgmt_db_req_head == NULL);
dlmgmt_db_req_tail = dlmgmt_db_req_head = req;
err = pthread_create(&tid, NULL, dlmgmt_db_update_thread, NULL);
if (err == 0)
return (EINPROGRESS);
}
return (err);
}
static int
dlmgmt_process_db_onereq(dlmgmt_db_req_t *req, boolean_t writeop)
{
int err = 0;
FILE *fp, *nfp = NULL;
char file[MAXPATHLEN];
char newfile[MAXPATHLEN];
DLMGMT_MAKE_FILE_DB_PATH(file, (req->ls_flags == DLMGMT_PERSIST));
fp = dlmgmt_zfopen(file, "r", req->ls_zoneid, &err);
/*
* Note that it is not an error if the file doesn't exist. If we're
* reading, we treat this case the same way as an empty file. If
* we're writing, the file will be created when we open the file for
* writing below.
*/
if (fp == NULL && !writeop)
return (err);
if (writeop) {
(void) snprintf(newfile, MAXPATHLEN, "%s.new", file);
nfp = dlmgmt_zfopen(newfile, "w", req->ls_zoneid, &err);
if (nfp == NULL) {
/*
* EROFS can happen at boot when the file system is
* read-only. Return EINPROGRESS so that the caller
* can add this request to the pending request list
* and start a retry thread.
*/
err = (errno == EROFS ? EINPROGRESS : errno);
goto done;
}
}
if (writeop) {
if ((err = process_db_write(req, fp, nfp)) == 0)
err = dlmgmt_zrename(newfile, file, req->ls_zoneid);
} else {
err = process_db_read(req, fp);
}
done:
if (nfp != NULL) {
(void) fclose(nfp);
if (err != 0)
(void) dlmgmt_zunlink(newfile, req->ls_zoneid);
}
(void) fclose(fp);
return (err);
}
/*ARGSUSED*/
static void *
dlmgmt_db_update_thread(void *arg)
{
dlmgmt_db_req_t *req;
dlmgmt_table_lock(B_TRUE);
assert(dlmgmt_db_req_head != NULL);
while ((req = dlmgmt_db_req_head) != NULL) {
assert(req->ls_flags == DLMGMT_PERSIST);
if (dlmgmt_process_db_onereq(req, B_TRUE) == EINPROGRESS) {
/*
* The filesystem is still read only. Go to sleep and
* try again.
*/
dlmgmt_table_unlock();
(void) sleep(5);
dlmgmt_table_lock(B_TRUE);
continue;
}
/*
* The filesystem is no longer read only. Continue processing
* and remove the request from the pending list.
*/
dlmgmt_db_req_head = req->ls_next;
if (dlmgmt_db_req_tail == req) {
assert(dlmgmt_db_req_head == NULL);
dlmgmt_db_req_tail = NULL;
}
free(req);
}
dlmgmt_table_unlock();
return (NULL);
}
static int
parse_linkprops(char *buf, dlmgmt_link_t *linkp)
{
boolean_t found_type = B_FALSE;
dladm_datatype_t type = DLADM_TYPE_STR;
int i, len;
char *curr;
char attr_name[MAXLINKATTRLEN];
size_t attr_buf_len = 0;
void *attr_buf = NULL;
curr = buf;
len = strlen(buf);
attr_name[0] = '\0';
for (i = 0; i < len; i++) {
char c = buf[i];
boolean_t match = (c == '=' ||
(c == ',' && !found_type) || c == ';');
boolean_t rename = B_FALSE;
/*
* Move to the next character if there is no match and
* if we have not reached the last character.
*/
if (!match && i != len - 1)
continue;
if (match) {
/*
* NUL-terminate the string pointed to by 'curr'.
*/
buf[i] = '\0';
if (*curr == '\0')
goto parse_fail;
}
if (attr_name[0] != '\0' && found_type) {
/*
* We get here after we have processed the "="
* pattern. The pattern we are now interested in is
* ";".
*/
if (c == '=')
goto parse_fail;
if (strcmp(attr_name, "linkid") == 0) {
if (read_int64(curr, &attr_buf) == 0)
goto parse_fail;
linkp->ll_linkid =
(datalink_class_t)*(int64_t *)attr_buf;
} else if (strcmp(attr_name, "name") == 0) {
if (read_str(curr, &attr_buf) == 0)
goto parse_fail;
(void) snprintf(linkp->ll_link,
MAXLINKNAMELEN, "%s", attr_buf);
} else if (strcmp(attr_name, "class") == 0) {
if (read_int64(curr, &attr_buf) == 0)
goto parse_fail;
linkp->ll_class =
(datalink_class_t)*(int64_t *)attr_buf;
} else if (strcmp(attr_name, "media") == 0) {
if (read_int64(curr, &attr_buf) == 0)
goto parse_fail;
linkp->ll_media =
(uint32_t)*(int64_t *)attr_buf;
} else if (strcmp(attr_name, "zone") == 0) {
if (read_str(curr, &attr_buf) == 0)
goto parse_fail;
linkp->ll_zoneid = getzoneidbyname(attr_buf);
if (linkp->ll_zoneid == -1) {
if (errno == EFAULT)
abort();
/*
* If we can't find the zone, assign the
* link to the GZ and mark it for being
* renamed.
*/
linkp->ll_zoneid = 0;
rename = B_TRUE;
}
} else if (strcmp(attr_name, "transient") == 0) {
if (read_boolean(curr, &attr_buf) == 0)
goto parse_fail;
linkp->ll_transient = *(boolean_t *)attr_buf;
} else {
attr_buf_len = translators[type].read_func(curr,
&attr_buf);
if (attr_buf_len == 0)
goto parse_fail;
if (linkattr_set(&(linkp->ll_head), attr_name,
attr_buf, attr_buf_len, type) != 0) {
free(attr_buf);
goto parse_fail;
}
}
free(attr_buf);
attr_name[0] = '\0';
found_type = B_FALSE;
} else if (attr_name[0] != '\0') {
/*
* Non-zero length attr_name and found_type of false
* indicates that we have not found the type for this
* attribute. The pattern now is ",;", we
* want the part of the pattern.
*/
for (type = 0; type < ntranslators; type++) {
if (strcmp(curr,
translators[type].type_name) == 0) {
found_type = B_TRUE;
break;
}
}
if (!found_type)
goto parse_fail;
} else {
/*
* A zero length attr_name indicates we are looking
* at the beginning of a link attribute.
*/
if (c != '=')
goto parse_fail;
(void) snprintf(attr_name, MAXLINKATTRLEN, "%s", curr);
}
/*
* The zone that this link belongs to has died, we are
* reparenting it to the GZ and renaming it to avoid name
* collisions.
*/
if (rename) {
(void) snprintf(linkp->ll_link, MAXLINKNAMELEN,
"SUNWorphan%u", (uint16_t)(gethrtime() / 1000));
}
curr = buf + i + 1;
}
/* Correct any erroneous IPTUN datalink class constant in the file */
if (linkp->ll_class == 0x60) {
linkp->ll_class = DATALINK_CLASS_IPTUN;
rewrite_needed = B_TRUE;
}
return (0);
parse_fail:
/*
* Free linkp->ll_head (link attribute list)
*/
linkattr_destroy(linkp);
return (-1);
}
static boolean_t
process_link_line(char *buf, dlmgmt_link_t *linkp)
{
int i, len, llen;
char *str, *lasts;
char tmpbuf[MAXLINELEN];
bzero(linkp, sizeof (*linkp));
linkp->ll_linkid = DATALINK_INVALID_LINKID;
linkp->ll_zoneid = ALL_ZONES;
/*
* Use a copy of buf for parsing so that we can do whatever we want.
*/
(void) strlcpy(tmpbuf, buf, MAXLINELEN);
/*
* Skip leading spaces, blank lines, and comments.
*/
len = strlen(tmpbuf);
for (i = 0; i < len; i++) {
if (!isspace(tmpbuf[i]))
break;
}
if (i == len || tmpbuf[i] == '#')
return (B_TRUE);
str = tmpbuf + i;
/*
* Find the link name and assign it to the link structure.
*/
if (strtok_r(str, " \n\t", &lasts) == NULL)
goto fail;
llen = strlen(str);
/*
* Note that a previous version of the persistent datalink.conf file
* stored the linkid as the first field. In that case, the name will
* be obtained through parse_linkprops from a property with the format
* "name=". If we encounter such a format, we set
* rewrite_needed so that dlmgmt_db_init() can rewrite the file with
* the new format after it's done reading in the data.
*/
if (isdigit(str[0])) {
linkp->ll_linkid = atoi(str);
rewrite_needed = B_TRUE;
} else {
if (strlcpy(linkp->ll_link, str, sizeof (linkp->ll_link)) >=
sizeof (linkp->ll_link))
goto fail;
}
str += llen + 1;
if (str >= tmpbuf + len)
goto fail;
/*
* Now find the list of link properties.
*/
if ((str = strtok_r(str, " \n\t", &lasts)) == NULL)
goto fail;
if (parse_linkprops(str, linkp) < 0)
goto fail;
return (B_TRUE);
fail:
/*
* Delete corrupted line.
*/
buf[0] = '\0';
return (B_FALSE);
}
/*
* Find any properties in linkp that refer to "old", and rename to "new".
* Return B_TRUE if any renaming occurred.
*/
static int
dlmgmt_attr_rename(dlmgmt_link_t *linkp, const char *old, const char *new,
boolean_t *renamed)
{
dlmgmt_linkattr_t *attrp;
char *newval = NULL, *pname;
char valcp[MAXLINKATTRVALLEN];
size_t newsize;
*renamed = B_FALSE;
if ((attrp = linkattr_find(linkp->ll_head, "linkover")) != NULL ||
(attrp = linkattr_find(linkp->ll_head, "simnetpeer")) != NULL) {
if (strcmp(old, (char *)attrp->lp_val) == 0) {
newsize = strlen(new) + 1;
if ((newval = malloc(newsize)) == NULL)
return (errno);
(void) strcpy(newval, new);
free(attrp->lp_val);
attrp->lp_val = newval;
attrp->lp_sz = newsize;
*renamed = B_TRUE;
}
return (0);
}
if ((attrp = linkattr_find(linkp->ll_head, "portnames")) == NULL)
return (0);
/* :[:]... */
if ((newval = calloc(MAXLINKATTRVALLEN, sizeof (char))) == NULL)
return (errno);
bcopy(attrp->lp_val, valcp, sizeof (valcp));
pname = strtok(valcp, ":");
while (pname != NULL) {
if (strcmp(pname, old) == 0) {
(void) strcat(newval, new);
*renamed = B_TRUE;
} else {
(void) strcat(newval, pname);
}
(void) strcat(newval, ":");
pname = strtok(NULL, ":");
}
if (*renamed) {
free(attrp->lp_val);
attrp->lp_val = newval;
attrp->lp_sz = strlen(newval) + 1;
} else {
free(newval);
}
return (0);
}
static int
process_db_write(dlmgmt_db_req_t *req, FILE *fp, FILE *nfp)
{
boolean_t done = B_FALSE;
int err = 0;
dlmgmt_link_t link_in_file, *linkp = NULL, *dblinkp;
boolean_t persist = (req->ls_flags == DLMGMT_PERSIST);
boolean_t writeall, rename, attr_renamed;
char buf[MAXLINELEN];
writeall = (req->ls_linkid == DATALINK_ALL_LINKID);
if (req->ls_op == DLMGMT_DB_OP_WRITE && !writeall) {
/*
* find the link in the avl tree with the given linkid.
*/
linkp = link_by_id(req->ls_linkid, req->ls_zoneid);
if (linkp == NULL || (linkp->ll_flags & req->ls_flags) == 0) {
/*
* This link has already been changed. This could
* happen if the request is pending because of
* read-only file-system. If so, we are done.
*/
return (0);
}
/*
* In the case of a rename, linkp's name has been updated to
* the new name, and req->ls_link is the old link name.
*/
rename = (strcmp(req->ls_link, linkp->ll_link) != 0);
}
/*
* fp can be NULL if the file didn't initially exist and we're
* creating it as part of this write operation.
*/
if (fp == NULL)
goto write;
while (err == 0 && fgets(buf, sizeof (buf), fp) != NULL &&
process_link_line(buf, &link_in_file)) {
/*
* Only the link name is needed. Free the memory allocated for
* the link attributes list of link_in_file.
*/
linkattr_destroy(&link_in_file);
if (link_in_file.ll_link[0] == '\0' || done) {
/*
* this is a comment line or we are done updating the
* line for the specified link, write the rest of
* lines out.
*/
if (fputs(buf, nfp) == EOF)
err = errno;
continue;
}
switch (req->ls_op) {
case DLMGMT_DB_OP_WRITE:
/*
* For write operations, we generate a new output line
* if we're either writing all links (writeall) or if
* the name of the link in the file matches the one
* we're looking for. Otherwise, we write out the
* buffer as-is.
*
* If we're doing a rename operation, ensure that any
* references to the link being renamed in link
* properties are also updated before we write
* anything.
*/
if (writeall) {
linkp = link_by_name(link_in_file.ll_link,
req->ls_zoneid);
}
if (writeall || strcmp(req->ls_link,
link_in_file.ll_link) == 0) {
generate_link_line(linkp, persist, buf);
if (!writeall && !rename)
done = B_TRUE;
} else if (rename && persist) {
dblinkp = link_by_name(link_in_file.ll_link,
req->ls_zoneid);
err = dlmgmt_attr_rename(dblinkp, req->ls_link,
linkp->ll_link, &attr_renamed);
if (err != 0)
break;
if (attr_renamed) {
generate_link_line(dblinkp, persist,
buf);
}
}
if (fputs(buf, nfp) == EOF)
err = errno;
break;
case DLMGMT_DB_OP_DELETE:
/*
* Delete is simple. If buf does not represent the
* link we're deleting, write it out.
*/
if (strcmp(req->ls_link, link_in_file.ll_link) != 0) {
if (fputs(buf, nfp) == EOF)
err = errno;
} else {
done = B_TRUE;
}
break;
case DLMGMT_DB_OP_READ:
default:
err = EINVAL;
break;
}
}
write:
/*
* If we get to the end of the file and have not seen what linkid
* points to, write it out then.
*/
if (req->ls_op == DLMGMT_DB_OP_WRITE && !writeall && !rename && !done) {
generate_link_line(linkp, persist, buf);
done = B_TRUE;
if (fputs(buf, nfp) == EOF)
err = errno;
}
return (err);
}
static int
process_db_read(dlmgmt_db_req_t *req, FILE *fp)
{
avl_index_t name_where, id_where;
dlmgmt_link_t link_in_file, *newlink, *link_in_db;
char buf[MAXLINELEN];
int err = 0;
/*
* This loop processes each line of the configuration file.
*/
while (fgets(buf, MAXLINELEN, fp) != NULL) {
if (!process_link_line(buf, &link_in_file)) {
err = EINVAL;
break;
}
/*
* Skip the comment line.
*/
if (link_in_file.ll_link[0] == '\0') {
linkattr_destroy(&link_in_file);
continue;
}
if ((req->ls_flags & DLMGMT_ACTIVE) &&
link_in_file.ll_linkid == DATALINK_INVALID_LINKID) {
linkattr_destroy(&link_in_file);
continue;
}
/*
* Persistent configuration files do not include the "zone"
* attribute. In that case, ll_zoneid will have the the
* ALL_ZONES sentinel value. Adjust it here to the requesting
* zone's ID.
*/
if (link_in_file.ll_zoneid == ALL_ZONES)
link_in_file.ll_zoneid = req->ls_zoneid;
assert(req->ls_zoneid == 0 ||
link_in_file.ll_zoneid == req->ls_zoneid);
link_in_db = link_by_name(link_in_file.ll_link,
link_in_file.ll_zoneid);
if (link_in_db != NULL) {
/*
* If the link in the database already has the flag
* for this request set, then the entry is a
* duplicate. If it's not a duplicate, then simply
* turn on the appropriate flag on the existing link.
*/
if (link_in_db->ll_flags & req->ls_flags) {
dlmgmt_log(LOG_WARNING, "Duplicate links "
"in the repository: %s",
link_in_file.ll_link);
linkattr_destroy(&link_in_file);
} else {
if (req->ls_flags & DLMGMT_PERSIST) {
/*
* Save the newly read properties into
* the existing link.
*/
assert(link_in_db->ll_head == NULL);
link_in_db->ll_head =
link_in_file.ll_head;
} else {
linkattr_destroy(&link_in_file);
}
link_in_db->ll_flags |= req->ls_flags;
}
} else {
/*
* This is a new link. Allocate a new dlmgmt_link_t
* and add it to the trees.
*/
newlink = calloc(1, sizeof (*newlink));
if (newlink == NULL) {
dlmgmt_log(LOG_WARNING, "Unable to allocate "
"memory to create new link %s",
link_in_file.ll_link);
linkattr_destroy(&link_in_file);
continue;
}
bcopy(&link_in_file, newlink, sizeof (*newlink));
if (newlink->ll_linkid == DATALINK_INVALID_LINKID)
newlink->ll_linkid = dlmgmt_nextlinkid;
if (avl_find(&dlmgmt_id_avl, newlink, &id_where) !=
NULL) {
dlmgmt_log(LOG_WARNING, "Link ID %d is already"
" in use, destroying link %s",
newlink->ll_linkid, newlink->ll_link);
link_destroy(newlink);
continue;
}
if ((req->ls_flags & DLMGMT_ACTIVE) &&
link_activate(newlink) != 0) {
dlmgmt_log(LOG_WARNING, "Unable to activate %s",
newlink->ll_link);
link_destroy(newlink);
continue;
}
avl_insert(&dlmgmt_id_avl, newlink, id_where);
/*
* link_activate call above can insert newlink in
* dlmgmt_name_avl tree when activating a link that is
* assigned to a NGZ.
*/
if (avl_find(&dlmgmt_name_avl, newlink,
&name_where) == NULL)
avl_insert(&dlmgmt_name_avl, newlink,
name_where);
dlmgmt_advance(newlink);
newlink->ll_flags |= req->ls_flags;
}
}
return (err);
}
/*
* Generate an entry in the link database.
* Each entry has this format:
* =,;...;=,;
*/
static void
generate_link_line(dlmgmt_link_t *linkp, boolean_t persist, char *buf)
{
char tmpbuf[MAXLINELEN];
char *ptr = tmpbuf;
char *lim = tmpbuf + MAXLINELEN;
dlmgmt_linkattr_t *cur_p = NULL;
uint64_t u64;
ptr += snprintf(ptr, BUFLEN(lim, ptr), "%s\t", linkp->ll_link);
if (!persist) {
char zname[ZONENAME_MAX];
/*
* We store the linkid and the zone name in the active database
* so that dlmgmtd can recover in the event that it is
* restarted.
*/
u64 = linkp->ll_linkid;
ptr += write_uint64(ptr, BUFLEN(lim, ptr), "linkid", &u64);
if (getzonenamebyid(linkp->ll_zoneid, zname,
sizeof (zname)) != -1) {
ptr += write_str(ptr, BUFLEN(lim, ptr), "zone", zname);
}
}
u64 = linkp->ll_class;
ptr += write_uint64(ptr, BUFLEN(lim, ptr), "class", &u64);
u64 = linkp->ll_media;
ptr += write_uint64(ptr, BUFLEN(lim, ptr), "media", &u64);
if (!persist && linkp->ll_transient) {
boolean_t b = B_TRUE;
ptr += write_boolean(ptr, BUFLEN(lim, ptr), "transient", &b);
}
/*
* The daemon does not keep any active link attribute. Only store the
* attributes if this request is for persistent configuration,
*/
if (persist) {
for (cur_p = linkp->ll_head; cur_p != NULL;
cur_p = cur_p->lp_next) {
ptr += translators[cur_p->lp_type].write_func(ptr,
BUFLEN(lim, ptr), cur_p->lp_name, cur_p->lp_val);
}
}
if (ptr <= lim)
(void) snprintf(buf, MAXLINELEN, "%s\n", tmpbuf);
}
int
dlmgmt_delete_db_entry(dlmgmt_link_t *linkp, uint32_t flags)
{
return (dlmgmt_db_update(DLMGMT_DB_OP_DELETE, linkp->ll_link, linkp,
flags));
}
int
dlmgmt_write_db_entry(const char *entryname, dlmgmt_link_t *linkp,
uint32_t flags)
{
int err;
if (flags & DLMGMT_PERSIST) {
if ((err = dlmgmt_db_update(DLMGMT_DB_OP_WRITE, entryname,
linkp, DLMGMT_PERSIST)) != 0) {
return (err);
}
}
if (flags & DLMGMT_ACTIVE) {
if (((err = dlmgmt_db_update(DLMGMT_DB_OP_WRITE, entryname,
linkp, DLMGMT_ACTIVE)) != 0) && (flags & DLMGMT_PERSIST)) {
(void) dlmgmt_db_update(DLMGMT_DB_OP_DELETE, entryname,
linkp, DLMGMT_PERSIST);
return (err);
}
}
return (0);
}
/*
* Upgrade properties that have link IDs as values to link names. Because '.'
* is a valid linkname character, the port separater for link aggregations
* must be changed to ':'.
*/
static void
linkattr_upgrade(dlmgmt_linkattr_t *attrp)
{
datalink_id_t linkid;
char *portidstr;
char portname[MAXLINKNAMELEN + 1];
dlmgmt_link_t *linkp;
char *new_attr_val;
size_t new_attr_sz;
boolean_t upgraded = B_FALSE;
if (strcmp(attrp->lp_name, "linkover") == 0 ||
strcmp(attrp->lp_name, "simnetpeer") == 0) {
if (attrp->lp_type == DLADM_TYPE_UINT64) {
linkid = (datalink_id_t)*(uint64_t *)attrp->lp_val;
if ((linkp = link_by_id(linkid, GLOBAL_ZONEID)) == NULL)
return;
new_attr_sz = strlen(linkp->ll_link) + 1;
if ((new_attr_val = malloc(new_attr_sz)) == NULL)
return;
(void) strcpy(new_attr_val, linkp->ll_link);
upgraded = B_TRUE;
}
} else if (strcmp(attrp->lp_name, "portnames") == 0) {
/*
* The old format for "portnames" was
* ".[.]...". The new format is
* ":[:]...".
*/
if (!isdigit(((char *)attrp->lp_val)[0]))
return;
new_attr_val = calloc(MAXLINKATTRVALLEN, sizeof (char));
if (new_attr_val == NULL)
return;
portidstr = (char *)attrp->lp_val;
while (*portidstr != '\0') {
errno = 0;
linkid = strtol(portidstr, &portidstr, 10);
if (linkid == 0 || *portidstr != '.' ||
(linkp = link_by_id(linkid, GLOBAL_ZONEID)) ==
NULL) {
free(new_attr_val);
return;
}
(void) snprintf(portname, sizeof (portname), "%s:",
linkp->ll_link);
if (strlcat(new_attr_val, portname,
MAXLINKATTRVALLEN) >= MAXLINKATTRVALLEN) {
free(new_attr_val);
return;
}
/* skip the '.' delimiter */
portidstr++;
}
new_attr_sz = strlen(new_attr_val) + 1;
upgraded = B_TRUE;
}
if (upgraded) {
attrp->lp_type = DLADM_TYPE_STR;
attrp->lp_sz = new_attr_sz;
free(attrp->lp_val);
attrp->lp_val = new_attr_val;
}
}
static void
dlmgmt_db_upgrade(dlmgmt_link_t *linkp)
{
dlmgmt_linkattr_t *attrp;
for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next)
linkattr_upgrade(attrp);
}
static void
dlmgmt_db_phys_activate(dlmgmt_link_t *linkp)
{
linkp->ll_flags |= DLMGMT_ACTIVE;
(void) dlmgmt_write_db_entry(linkp->ll_link, linkp, DLMGMT_ACTIVE);
}
static void
dlmgmt_db_walk(zoneid_t zoneid, datalink_class_t class, db_walk_func_t *func)
{
dlmgmt_link_t *linkp;
for (linkp = avl_first(&dlmgmt_id_avl); linkp != NULL;
linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
if (linkp->ll_zoneid == zoneid && (linkp->ll_class & class))
func(linkp);
}
}
/*
* Attempt to mitigate one of the deadlocks in the dlmgmtd architecture.
*
* dlmgmt_db_init() calls dlmgmt_process_db_req() which eventually gets to
* dlmgmt_zfop() which tries to fork, enter the zone and read the file.
* Because of the upcall architecture of dlmgmtd this can lead to deadlock
* with the following scenario:
* a) the thread preparing to fork will have acquired the malloc locks
* then attempt to suspend every thread in preparation to fork.
* b) all of the upcalls will be blocked in door_ucred() trying to malloc()
* and get the credentials of their caller.
* c) we can't suspend the in-kernel thread making the upcall.
*
* Thus, we cannot serve door requests because we're blocked in malloc()
* which fork() owns, but fork() is in turn blocked on the in-kernel thread
* making the door upcall. This is a fundamental architectural problem with
* any server handling upcalls and also trying to fork().
*
* To minimize the chance of this deadlock occuring, we check ahead of time to
* see if the file we want to read actually exists in the zone (which it almost
* never does), so we don't need fork in that case (i.e. rarely to never).
*/
static boolean_t
zone_file_exists(char *zoneroot, char *filename)
{
struct stat sb;
char fname[MAXPATHLEN];
(void) snprintf(fname, sizeof (fname), "%s/%s", zoneroot, filename);
if (stat(fname, &sb) == -1)
return (B_FALSE);
return (B_TRUE);
}
/*
* Initialize the datalink mapping and the link's
* attributes list based on the configuration file /etc/dladm/datalink.conf
* and the active configuration cache file
* /etc/svc/volatile/dladm/datalink-management:default.cache.
*/
int
dlmgmt_db_init(zoneid_t zoneid, char *zoneroot)
{
dlmgmt_db_req_t *req;
int err;
boolean_t boot = B_FALSE;
if ((req = dlmgmt_db_req_alloc(DLMGMT_DB_OP_READ, NULL,
DATALINK_INVALID_LINKID, zoneid, DLMGMT_ACTIVE, &err)) == NULL)
return (err);
if (zone_file_exists(zoneroot, cachefile)) {
if ((err = dlmgmt_process_db_req(req)) != 0) {
/*
* If we get back ENOENT, that means that the active
* configuration file doesn't exist yet, and is not an
* error. We'll create it down below after we've
* loaded the persistent configuration.
*/
if (err != ENOENT)
goto done;
boot = B_TRUE;
}
} else {
boot = B_TRUE;
}
if (zone_file_exists(zoneroot, DLMGMT_PERSISTENT_DB_PATH)) {
req->ls_flags = DLMGMT_PERSIST;
err = dlmgmt_process_db_req(req);
if (err != 0 && err != ENOENT)
goto done;
}
err = 0;
if (rewrite_needed) {
/*
* First update links in memory, then dump the entire db to
* disk.
*/
dlmgmt_db_walk(zoneid, DATALINK_CLASS_ALL, dlmgmt_db_upgrade);
req->ls_op = DLMGMT_DB_OP_WRITE;
req->ls_linkid = DATALINK_ALL_LINKID;
if ((err = dlmgmt_process_db_req(req)) != 0 &&
err != EINPROGRESS)
goto done;
}
if (boot) {
dlmgmt_db_walk(zoneid, DATALINK_CLASS_PHYS,
dlmgmt_db_phys_activate);
}
done:
if (err == EINPROGRESS)
err = 0;
else
free(req);
return (err);
}
/*
* Remove all links in the given zoneid.
*/
void
dlmgmt_db_fini(zoneid_t zoneid)
{
dlmgmt_link_t *linkp = avl_first(&dlmgmt_name_avl), *next_linkp;
while (linkp != NULL) {
next_linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
if (linkp->ll_zoneid == zoneid) {
(void) dlmgmt_destroy_common(linkp,
DLMGMT_ACTIVE | DLMGMT_PERSIST);
}
linkp = next_linkp;
}
}
/*
* 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2016 Joyent, Inc.
* Copyright 2023 Oxide Computer Company
*/
/*
* Main door handler functions used by dlmgmtd to process the different door
* call requests. Door call requests can come from the user-land applications,
* or from the kernel.
*
* Note on zones handling:
*
* There are two zoneid's associated with a link. One is the zoneid of the
* zone in which the link was created (ll_zoneid in the dlmgmt_link_t), and
* the other is the zoneid of the zone where the link is currently assigned
* (the "zone" link property). The two can be different if a datalink is
* created in the global zone and subsequently assigned to a non-global zone
* via zonecfg or via explicitly setting the "zone" link property.
*
* Door clients can see links that were created in their zone, and links that
* are currently assigned to their zone. Door clients in a zone can only
* modify links that were created in their zone.
*
* The datalink ID space is global, while each zone has its own datalink name
* space. This allows each zone to have complete freedom over the names that
* they assign to links created within the zone.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dlmgmt_impl.h"
typedef void dlmgmt_door_handler_t(void *, void *, size_t *, zoneid_t,
ucred_t *);
typedef struct dlmgmt_door_info_s {
uint_t di_cmd;
size_t di_reqsz;
size_t di_acksz;
dlmgmt_door_handler_t *di_handler;
} dlmgmt_door_info_t;
/*
* Check if the caller has the required privileges to operate on a link of the
* given class.
*/
static int
dlmgmt_checkprivs(datalink_class_t class, ucred_t *cred)
{
const priv_set_t *eset;
eset = ucred_getprivset(cred, PRIV_EFFECTIVE);
if (eset != NULL && ((class == DATALINK_CLASS_IPTUN &&
priv_ismember(eset, PRIV_SYS_IPTUN_CONFIG)) ||
priv_ismember(eset, PRIV_SYS_DL_CONFIG) ||
priv_ismember(eset, PRIV_SYS_NET_CONFIG)))
return (0);
return (EACCES);
}
static dlmgmt_link_t *
dlmgmt_getlink_by_dev(char *devname, zoneid_t zoneid)
{
dlmgmt_link_t *linkp = avl_first(&dlmgmt_id_avl);
for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
if (link_is_visible(linkp, zoneid) &&
(linkp->ll_class == DATALINK_CLASS_PHYS) &&
linkattr_equal(&(linkp->ll_head), FDEVNAME, devname,
strlen(devname) + 1)) {
return (linkp);
}
}
return (NULL);
}
/*
* Post the EC_DATALINK sysevent for the given linkid. This sysevent will
* be consumed by the datalink sysevent module.
*/
static void
dlmgmt_post_sysevent(const char *subclass, datalink_id_t linkid,
boolean_t reconfigured)
{
nvlist_t *nvl = NULL;
sysevent_id_t eid;
int err;
if (((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0) ||
((err = nvlist_add_uint64(nvl, RCM_NV_LINKID, linkid)) != 0) ||
((err = nvlist_add_boolean_value(nvl, RCM_NV_RECONFIGURED,
reconfigured)) != 0)) {
goto done;
}
if (sysevent_post_event(EC_DATALINK, (char *)subclass, SUNW_VENDOR,
(char *)progname, nvl, &eid) == -1) {
err = errno;
}
done:
if (err != 0) {
dlmgmt_log(LOG_WARNING, "dlmgmt_post_sysevent(%d) failed: %s",
linkid, strerror(err));
}
nvlist_free(nvl);
}
/* ARGSUSED */
static void
dlmgmt_upcall_create(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_upcall_arg_create_t *create = argp;
dlmgmt_create_retval_t *retvalp = retp;
datalink_class_t class;
uint32_t media;
dlmgmt_link_t *linkp;
char link[MAXLINKNAMELEN];
uint32_t flags;
int err = 0;
boolean_t created = B_FALSE;
boolean_t reconfigured = B_FALSE;
/*
* Determine whether this link is persistent. Note that this request
* is coming from kernel so this link must be active.
*/
flags = DLMGMT_ACTIVE | (create->ld_persist ? DLMGMT_PERSIST : 0);
class = create->ld_class;
media = create->ld_media;
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
if ((err = dlmgmt_checkprivs(class, cred)) != 0)
goto done;
/*
* Check to see whether this is the reattachment of an existing
* physical link. If so, return its linkid.
*/
if ((class == DATALINK_CLASS_PHYS) && (linkp =
dlmgmt_getlink_by_dev(create->ld_devname, zoneid)) != NULL) {
if (linkattr_equal(&(linkp->ll_head), FPHYMAJ,
&create->ld_phymaj, sizeof (uint64_t)) &&
linkattr_equal(&(linkp->ll_head), FPHYINST,
&create->ld_phyinst, sizeof (uint64_t)) &&
(linkp->ll_flags & flags) == flags) {
/*
* If nothing has been changed, directly return.
*/
goto noupdate;
}
err = linkattr_set(&(linkp->ll_head), FPHYMAJ,
&create->ld_phymaj, sizeof (uint64_t), DLADM_TYPE_UINT64);
if (err != 0)
goto done;
err = linkattr_set(&(linkp->ll_head), FPHYINST,
&create->ld_phyinst, sizeof (uint64_t), DLADM_TYPE_UINT64);
if (err != 0)
goto done;
/*
* This is a device that is dynamic reconfigured.
*/
if ((linkp->ll_flags & DLMGMT_ACTIVE) == 0)
reconfigured = B_TRUE;
if ((err = link_activate(linkp)) != 0)
goto done;
linkp->ll_flags |= flags;
linkp->ll_gen++;
goto done;
}
if ((err = dlmgmt_create_common(create->ld_devname, class, media,
zoneid, flags, &linkp)) == EEXIST) {
/*
* The link name already exists. Return error if this is a
* non-physical link (in that case, the link name must be
* the same as the given name).
*/
if (class != DATALINK_CLASS_PHYS)
goto done;
/*
* The physical link's name already exists, request
* a suggested link name: net
*/
err = dlmgmt_generate_name("net", link, MAXLINKNAMELEN, zoneid);
if (err != 0)
goto done;
err = dlmgmt_create_common(link, class, media, zoneid, flags,
&linkp);
}
if (err != 0)
goto done;
created = B_TRUE;
/*
* This is a new link. Only need to persist link attributes for
* physical links.
*/
if (class == DATALINK_CLASS_PHYS &&
(((err = linkattr_set(&linkp->ll_head, FDEVNAME, create->ld_devname,
strlen(create->ld_devname) + 1, DLADM_TYPE_STR)) != 0) ||
((err = linkattr_set(&linkp->ll_head, FPHYMAJ, &create->ld_phymaj,
sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0) ||
((err = linkattr_set(&linkp->ll_head, FPHYINST, &create->ld_phyinst,
sizeof (uint64_t), DLADM_TYPE_UINT64)) != 0))) {
(void) dlmgmt_destroy_common(linkp, flags);
}
done:
if ((err == 0) && ((err = dlmgmt_write_db_entry(linkp->ll_link, linkp,
linkp->ll_flags)) != 0) && created) {
(void) dlmgmt_destroy_common(linkp, flags);
}
noupdate:
if (err == 0)
retvalp->lr_linkid = linkp->ll_linkid;
dlmgmt_table_unlock();
if ((err == 0) && (class == DATALINK_CLASS_PHYS)) {
/*
* Post the ESC_DATALINK_PHYS_ADD sysevent. This sysevent
* is consumed by the datalink sysevent module which in
* turn generates the RCM_RESOURCE_LINK_NEW RCM event.
*/
dlmgmt_post_sysevent(ESC_DATALINK_PHYS_ADD,
retvalp->lr_linkid, reconfigured);
}
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_upcall_update(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_upcall_arg_update_t *update = argp;
dlmgmt_update_retval_t *retvalp = retp;
uint32_t media = update->ld_media;
dlmgmt_link_t *linkp;
int err = 0;
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
/*
* Check to see whether this is the reattachment of an existing
* physical link. If so, return its linkid.
*/
if ((linkp = dlmgmt_getlink_by_dev(update->ld_devname, zoneid)) ==
NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
retvalp->lr_linkid = linkp->ll_linkid;
retvalp->lr_media = media;
if (linkp->ll_media != media && linkp->ll_media != DL_OTHER) {
/*
* Assume a DL_ETHER link ce0, a DL_WIFI link ath0
* 1. # dladm rename-link ce0 net0
* 2. DR out ce0. net0 is down.
* 3. use rename-link to have the ath0 device inherit
* the configuration from net0
* # dladm rename-link ath0 net0
* 4. DR in ath0.
* As ath0 and ce0 do not have the same media type, ath0
* cannot inherit the configuration of net0.
*/
err = EEXIST;
/*
* Return the media type of the existing link to indicate the
* reason for the name conflict.
*/
retvalp->lr_media = linkp->ll_media;
goto done;
}
if (update->ld_novanity &&
(strcmp(update->ld_devname, linkp->ll_link) != 0)) {
/*
* Return an error if this is a physical link that does not
* support vanity naming, but the link name is not the same
* as the given device name.
*/
err = EEXIST;
goto done;
}
if (linkp->ll_media != media) {
linkp->ll_media = media;
linkp->ll_gen++;
(void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
linkp->ll_flags);
}
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_upcall_destroy(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_upcall_arg_destroy_t *destroy = argp;
dlmgmt_destroy_retval_t *retvalp = retp;
datalink_id_t linkid = destroy->ld_linkid;
dlmgmt_link_t *linkp = NULL;
uint32_t flags, dflags = 0;
int err = 0;
flags = DLMGMT_ACTIVE | (destroy->ld_persist ? DLMGMT_PERSIST : 0);
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
if (((linkp->ll_flags & flags) & DLMGMT_ACTIVE) != 0) {
if ((err = dlmgmt_delete_db_entry(linkp, DLMGMT_ACTIVE)) != 0)
goto done;
dflags |= DLMGMT_ACTIVE;
}
if (((linkp->ll_flags & flags) & DLMGMT_PERSIST) != 0) {
if ((err = dlmgmt_delete_db_entry(linkp, DLMGMT_PERSIST)) != 0)
goto done;
dflags |= DLMGMT_PERSIST;
}
err = dlmgmt_destroy_common(linkp, flags);
done:
if (err != 0 && dflags != 0)
(void) dlmgmt_write_db_entry(linkp->ll_link, linkp, dflags);
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_getname(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_getname_t *getname = argp;
dlmgmt_getname_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
int err = 0;
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
if ((linkp = link_by_id(getname->ld_linkid, zoneid)) == NULL) {
err = ENOENT;
} else if (strlcpy(retvalp->lr_link, linkp->ll_link, MAXLINKNAMELEN) >=
MAXLINKNAMELEN) {
err = ENOSPC;
} else {
retvalp->lr_flags = linkp->ll_flags;
retvalp->lr_class = linkp->ll_class;
retvalp->lr_media = linkp->ll_media;
retvalp->lr_flags |= linkp->ll_transient ? DLMGMT_TRANSIENT : 0;
}
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_getlinkid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_getlinkid_t *getlinkid = argp;
dlmgmt_getlinkid_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
int err = 0;
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
if ((linkp = link_by_name(getlinkid->ld_link, zoneid)) == NULL) {
/*
* The link does not exist in this zone.
*/
err = ENOENT;
goto done;
}
retvalp->lr_linkid = linkp->ll_linkid;
retvalp->lr_flags = linkp->ll_flags;
retvalp->lr_class = linkp->ll_class;
retvalp->lr_media = linkp->ll_media;
retvalp->lr_flags |= linkp->ll_transient ? DLMGMT_TRANSIENT : 0;
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_getnext(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_getnext_t *getnext = argp;
dlmgmt_getnext_retval_t *retvalp = retp;
dlmgmt_link_t link, *linkp;
avl_index_t where;
int err = 0;
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
link.ll_linkid = (getnext->ld_linkid + 1);
if ((linkp = avl_find(&dlmgmt_id_avl, &link, &where)) == NULL)
linkp = avl_nearest(&dlmgmt_id_avl, where, AVL_AFTER);
for (; linkp != NULL; linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
if (!link_is_visible(linkp, zoneid))
continue;
if ((linkp->ll_class & getnext->ld_class) &&
(linkp->ll_flags & getnext->ld_flags) &&
DATALINK_MEDIA_ACCEPTED(getnext->ld_dmedia,
linkp->ll_media))
break;
}
if (linkp == NULL) {
err = ENOENT;
} else {
retvalp->lr_linkid = linkp->ll_linkid;
retvalp->lr_class = linkp->ll_class;
retvalp->lr_media = linkp->ll_media;
retvalp->lr_flags = linkp->ll_flags;
retvalp->lr_flags |= linkp->ll_transient ? DLMGMT_TRANSIENT : 0;
}
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_upcall_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_upcall_arg_getattr_t *getattr = argp;
dlmgmt_getattr_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
if ((linkp = link_by_id(getattr->ld_linkid, zoneid)) == NULL) {
retvalp->lr_err = ENOENT;
} else {
retvalp->lr_err = dlmgmt_getattr_common(&linkp->ll_head,
getattr->ld_attr, retvalp);
}
dlmgmt_table_unlock();
}
/* ARGSUSED */
static void
dlmgmt_createid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_createid_t *createid = argp;
dlmgmt_createid_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
datalink_id_t linkid = DATALINK_INVALID_LINKID;
char link[MAXLINKNAMELEN];
int err;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_table_lock(B_TRUE);
if ((err = dlmgmt_checkprivs(createid->ld_class, cred)) != 0)
goto done;
if (createid->ld_prefix) {
err = dlmgmt_generate_name(createid->ld_link, link,
MAXLINKNAMELEN, zoneid);
if (err != 0)
goto done;
err = dlmgmt_create_common(link, createid->ld_class,
createid->ld_media, zoneid, createid->ld_flags, &linkp);
} else {
err = dlmgmt_create_common(createid->ld_link,
createid->ld_class, createid->ld_media, zoneid,
createid->ld_flags, &linkp);
}
if (err == 0) {
/*
* Keep the active mapping.
*/
linkid = linkp->ll_linkid;
if (createid->ld_flags & DLMGMT_ACTIVE) {
(void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
DLMGMT_ACTIVE);
}
}
done:
dlmgmt_table_unlock();
retvalp->lr_linkid = linkid;
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_destroyid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_destroyid_t *destroyid = argp;
dlmgmt_destroyid_retval_t *retvalp = retp;
datalink_id_t linkid = destroyid->ld_linkid;
uint32_t flags = destroyid->ld_flags;
dlmgmt_link_t *linkp = NULL;
int err = 0;
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
/*
* Delete the active mapping.
*/
if (flags & DLMGMT_ACTIVE)
err = dlmgmt_delete_db_entry(linkp, DLMGMT_ACTIVE);
if (err == 0)
err = dlmgmt_destroy_common(linkp, flags);
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/*
* Remap a linkid to a given link name, i.e., rename an existing link1
* (ld_linkid) to a non-existent link2 (ld_link): rename link1's name to
* the given link name.
*/
/* ARGSUSED */
static void
dlmgmt_remapid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_remapid_t *remapid = argp;
dlmgmt_remapid_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
char oldname[MAXLINKNAMELEN];
boolean_t renamed = B_FALSE;
int err = 0;
if (!dladm_valid_linkname(remapid->ld_link)) {
retvalp->lr_err = EINVAL;
return;
}
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
if ((linkp = link_by_id(remapid->ld_linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
if (link_by_name(remapid->ld_link, linkp->ll_zoneid) != NULL) {
err = EEXIST;
goto done;
}
(void) strlcpy(oldname, linkp->ll_link, MAXLINKNAMELEN);
avl_remove(&dlmgmt_name_avl, linkp);
(void) strlcpy(linkp->ll_link, remapid->ld_link, MAXLINKNAMELEN);
avl_add(&dlmgmt_name_avl, linkp);
renamed = B_TRUE;
if (linkp->ll_flags & DLMGMT_ACTIVE) {
err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_ACTIVE);
if (err != 0)
goto done;
}
if (linkp->ll_flags & DLMGMT_PERSIST) {
err = dlmgmt_write_db_entry(oldname, linkp, DLMGMT_PERSIST);
if (err != 0) {
if (linkp->ll_flags & DLMGMT_ACTIVE) {
(void) dlmgmt_write_db_entry(remapid->ld_link,
linkp, DLMGMT_ACTIVE);
}
goto done;
}
}
dlmgmt_advance(linkp);
linkp->ll_gen++;
done:
if (err != 0 && renamed) {
avl_remove(&dlmgmt_name_avl, linkp);
(void) strlcpy(linkp->ll_link, oldname, MAXLINKNAMELEN);
avl_add(&dlmgmt_name_avl, linkp);
}
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_upid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_upid_t *upid = argp;
dlmgmt_upid_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
int err = 0;
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
if ((linkp = link_by_id(upid->ld_linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
if (linkp->ll_flags & DLMGMT_ACTIVE) {
err = EINVAL;
goto done;
}
if ((err = link_activate(linkp)) == 0) {
(void) dlmgmt_write_db_entry(linkp->ll_link, linkp,
DLMGMT_ACTIVE);
}
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_createconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_createconf_t *createconf = argp;
dlmgmt_createconf_retval_t *retvalp = retp;
dlmgmt_dlconf_t *dlconfp;
int err;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
if ((err = dlmgmt_checkprivs(createconf->ld_class, cred)) != 0)
goto done;
err = dlconf_create(createconf->ld_link, createconf->ld_linkid,
createconf->ld_class, createconf->ld_media, zoneid, &dlconfp);
if (err == 0) {
avl_add(&dlmgmt_dlconf_avl, dlconfp);
dlmgmt_advance_dlconfid(dlconfp);
retvalp->lr_confid = dlconfp->ld_id;
}
done:
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_setattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_setattr_t *setattr = argp;
dlmgmt_setattr_retval_t *retvalp = retp;
dlmgmt_dlconf_t dlconf, *dlconfp;
int err = 0;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
dlconf.ld_id = setattr->ld_confid;
dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
goto done;
err = linkattr_set(&(dlconfp->ld_head), setattr->ld_attr,
&setattr->ld_attrval, setattr->ld_attrsz, setattr->ld_type);
done:
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_unsetconfattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_unsetattr_t *unsetattr = argp;
dlmgmt_unsetattr_retval_t *retvalp = retp;
dlmgmt_dlconf_t dlconf, *dlconfp;
int err = 0;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
dlconf.ld_id = unsetattr->ld_confid;
dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
goto done;
linkattr_unset(&(dlconfp->ld_head), unsetattr->ld_attr);
done:
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/*
* Note that dlmgmt_openconf() returns a conf ID of a conf AVL tree entry,
* which is managed by dlmgmtd. The ID is used to find the conf entry when
* dlmgmt_write_conf() is called. The conf entry contains an ld_gen value
* (which is the generation number - ll_gen) of the dlmgmt_link_t at the time
* of dlmgmt_openconf(), and ll_gen changes every time the dlmgmt_link_t
* changes its attributes. Therefore, dlmgmt_write_conf() can compare ld_gen
* in the conf entry against the latest dlmgmt_link_t ll_gen value to see if
* anything has changed between the dlmgmt_openconf() and dlmgmt_writeconf()
* calls. If so, EAGAIN is returned. This mechanism can ensures atomicity
* across the pair of dladm_read_conf() and dladm_write_conf() calls.
*/
/* ARGSUSED */
static void
dlmgmt_writeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_writeconf_t *writeconf = argp;
dlmgmt_writeconf_retval_t *retvalp = retp;
dlmgmt_dlconf_t dlconf, *dlconfp;
dlmgmt_link_t *linkp;
dlmgmt_linkattr_t *attrp, *next;
int err = 0;
/*
* Hold the lock to access the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
dlconf.ld_id = writeconf->ld_confid;
dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
goto done;
/*
* Hold the writer lock to update the link table.
*/
dlmgmt_table_lock(B_TRUE);
linkp = link_by_id(dlconfp->ld_linkid, zoneid);
if ((linkp == NULL) || (linkp->ll_class != dlconfp->ld_class) ||
(linkp->ll_media != dlconfp->ld_media) ||
(strcmp(linkp->ll_link, dlconfp->ld_link) != 0)) {
/*
* The link does not exist.
*/
dlmgmt_table_unlock();
err = ENOENT;
goto done;
}
if (linkp->ll_gen != dlconfp->ld_gen) {
/*
* Something has changed the link configuration; try again.
*/
dlmgmt_table_unlock();
err = EAGAIN;
goto done;
}
/*
* Delete the old attribute list.
*/
for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
next = attrp->lp_next;
free(attrp->lp_val);
free(attrp);
}
linkp->ll_head = NULL;
/*
* Set the new attribute.
*/
for (attrp = dlconfp->ld_head; attrp != NULL; attrp = attrp->lp_next) {
if ((err = linkattr_set(&(linkp->ll_head), attrp->lp_name,
attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
dlmgmt_table_unlock();
goto done;
}
}
linkp->ll_gen++;
err = dlmgmt_write_db_entry(linkp->ll_link, linkp, DLMGMT_PERSIST);
dlmgmt_table_unlock();
done:
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_removeconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_removeconf_t *removeconf = argp;
dlmgmt_removeconf_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
int err;
dlmgmt_table_lock(B_TRUE);
if ((linkp = link_by_id(removeconf->ld_linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if (zoneid != GLOBAL_ZONEID && linkp->ll_onloan) {
/*
* A non-global zone cannot remove the persistent
* configuration of a link that is on loan from the global
* zone.
*/
err = EACCES;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
err = dlmgmt_delete_db_entry(linkp, DLMGMT_PERSIST);
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_destroyconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_destroyconf_t *destroyconf = argp;
dlmgmt_destroyconf_retval_t *retvalp = retp;
dlmgmt_dlconf_t dlconf, *dlconfp;
int err = 0;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
dlconf.ld_id = destroyconf->ld_confid;
dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
if (dlconfp == NULL || zoneid != dlconfp->ld_zoneid) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0)
goto done;
avl_remove(&dlmgmt_dlconf_avl, dlconfp);
dlconf_destroy(dlconfp);
done:
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/*
* dlmgmt_openconf() returns a handle of the current configuration, which
* is then used to update the configuration by dlmgmt_writeconf(). Therefore,
* it requires privileges.
*
* Further, please see the comments above dladm_write_conf() to see how
* ld_gen is used to ensure atomicity across the {dlmgmt_openconf(),
* dlmgmt_writeconf()} pair.
*/
/* ARGSUSED */
static void
dlmgmt_openconf(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_openconf_t *openconf = argp;
dlmgmt_openconf_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
datalink_id_t linkid = openconf->ld_linkid;
dlmgmt_dlconf_t *dlconfp;
dlmgmt_linkattr_t *attrp;
int err = 0;
/*
* Hold the writer lock to update the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_TRUE);
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
linkp = link_by_id(linkid, zoneid);
if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
/* The persistent link configuration does not exist. */
err = ENOENT;
goto done;
}
if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
/*
* The caller is in a non-global zone and the persistent
* configuration belongs to the global zone.
*/
err = EACCES;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
if ((err = dlconf_create(linkp->ll_link, linkp->ll_linkid,
linkp->ll_class, linkp->ll_media, zoneid, &dlconfp)) != 0)
goto done;
for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
if ((err = linkattr_set(&(dlconfp->ld_head), attrp->lp_name,
attrp->lp_val, attrp->lp_sz, attrp->lp_type)) != 0) {
dlconf_destroy(dlconfp);
goto done;
}
}
dlconfp->ld_gen = linkp->ll_gen;
avl_add(&dlmgmt_dlconf_avl, dlconfp);
dlmgmt_advance_dlconfid(dlconfp);
retvalp->lr_confid = dlconfp->ld_id;
done:
dlmgmt_table_unlock();
dlmgmt_dlconf_table_unlock();
retvalp->lr_err = err;
}
/*
* dlmgmt_getconfsnapshot() returns a read-only snapshot of all the
* configuration, and requires no privileges.
*
* If the given size cannot hold all the configuration, set the size
* that is needed, and return ENOSPC.
*/
/* ARGSUSED */
static void
dlmgmt_getconfsnapshot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_getconfsnapshot_t *snapshot = argp;
dlmgmt_getconfsnapshot_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
datalink_id_t linkid = snapshot->ld_linkid;
dlmgmt_linkattr_t *attrp;
char *buf;
size_t nvlsz;
nvlist_t *nvl = NULL;
int err = 0;
assert(*sz >= sizeof (dlmgmt_getconfsnapshot_retval_t));
/*
* Hold the reader lock to access the link
*/
dlmgmt_table_lock(B_FALSE);
linkp = link_by_id(linkid, zoneid);
if ((linkp == NULL) || !(linkp->ll_flags & DLMGMT_PERSIST)) {
/* The persistent link configuration does not exist. */
err = ENOENT;
goto done;
}
if (linkp->ll_onloan && zoneid != GLOBAL_ZONEID) {
/*
* The caller is in a non-global zone and the persistent
* configuration belongs to the global zone.
*/
err = EACCES;
goto done;
}
err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0);
if (err != 0)
goto done;
for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next) {
if ((err = nvlist_add_byte_array(nvl, attrp->lp_name,
attrp->lp_val, attrp->lp_sz)) != 0) {
goto done;
}
}
if ((err = nvlist_size(nvl, &nvlsz, NV_ENCODE_NATIVE)) != 0)
goto done;
if (nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t) > *sz) {
*sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
err = ENOSPC;
goto done;
}
/*
* pack the the nvlist into the return value.
*/
*sz = nvlsz + sizeof (dlmgmt_getconfsnapshot_retval_t);
retvalp->lr_nvlsz = nvlsz;
buf = (char *)retvalp + sizeof (dlmgmt_getconfsnapshot_retval_t);
err = nvlist_pack(nvl, &buf, &nvlsz, NV_ENCODE_NATIVE, 0);
done:
dlmgmt_table_unlock();
nvlist_free(nvl);
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_getattr(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_getattr_t *getattr = argp;
dlmgmt_getattr_retval_t *retvalp = retp;
dlmgmt_dlconf_t dlconf, *dlconfp;
int err;
/*
* Hold the read lock to access the dlconf table.
*/
dlmgmt_dlconf_table_lock(B_FALSE);
dlconf.ld_id = getattr->ld_confid;
if ((dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL)) == NULL ||
zoneid != dlconfp->ld_zoneid) {
retvalp->lr_err = ENOENT;
} else {
if ((err = dlmgmt_checkprivs(dlconfp->ld_class, cred)) != 0) {
retvalp->lr_err = err;
} else {
retvalp->lr_err = dlmgmt_getattr_common(
&dlconfp->ld_head, getattr->ld_attr, retvalp);
}
}
dlmgmt_dlconf_table_unlock();
}
/* ARGSUSED */
static void
dlmgmt_upcall_linkprop_init(void *argp, void *retp, size_t *sz,
zoneid_t zoneid, ucred_t *cred)
{
dlmgmt_door_linkprop_init_t *lip = argp;
dlmgmt_linkprop_init_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
int err;
dlmgmt_table_lock(B_FALSE);
if ((linkp = link_by_id(lip->ld_linkid, zoneid)) == NULL)
err = ENOENT;
else
err = dlmgmt_checkprivs(linkp->ll_class, cred);
dlmgmt_table_unlock();
if (err == 0) {
dladm_status_t s;
char buf[DLADM_STRSIZE];
s = dladm_init_linkprop(dld_handle, lip->ld_linkid, B_TRUE);
if (s != DLADM_STATUS_OK) {
dlmgmt_log(LOG_WARNING,
"linkprop initialization failed on link %d: %s",
lip->ld_linkid, dladm_status2str(s, buf));
err = EINVAL;
}
}
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_setzoneid(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
dlmgmt_door_setzoneid_t *setzoneid = argp;
dlmgmt_setzoneid_retval_t *retvalp = retp;
dlmgmt_link_t *linkp;
datalink_id_t linkid = setzoneid->ld_linkid;
zoneid_t oldzoneid, newzoneid;
int err = 0;
dlmgmt_table_lock(B_TRUE);
/* We currently only allow changing zoneid's from the global zone. */
if (zoneid != GLOBAL_ZONEID) {
err = EACCES;
goto done;
}
if ((linkp = link_by_id(linkid, zoneid)) == NULL) {
err = ENOENT;
goto done;
}
if ((err = dlmgmt_checkprivs(linkp->ll_class, cred)) != 0)
goto done;
/* We can only assign an active link to a zone. */
if (!(linkp->ll_flags & DLMGMT_ACTIVE)) {
err = EINVAL;
goto done;
}
oldzoneid = linkp->ll_zoneid;
newzoneid = setzoneid->ld_zoneid;
if (oldzoneid == newzoneid)
goto done;
/*
* Before we remove the link from its current zone, make sure that
* there isn't a link with the same name in the destination zone.
*/
if (zoneid != GLOBAL_ZONEID &&
link_by_name(linkp->ll_link, newzoneid) != NULL) {
err = EEXIST;
goto done;
}
if (oldzoneid != GLOBAL_ZONEID) {
if (zone_remove_datalink(oldzoneid, linkid) != 0) {
err = errno;
dlmgmt_log(LOG_WARNING, "unable to remove link %d from "
"zone %d: %s", linkid, oldzoneid, strerror(err));
goto done;
}
if (linkp->ll_onloan) {
avl_remove(&dlmgmt_loan_avl, linkp);
linkp->ll_onloan = B_FALSE;
}
}
if (newzoneid != GLOBAL_ZONEID) {
if (zone_add_datalink(newzoneid, linkid) != 0) {
err = errno;
dlmgmt_log(LOG_WARNING, "unable to add link %d to zone "
"%d: %s", linkid, newzoneid, strerror(err));
(void) zone_add_datalink(oldzoneid, linkid);
goto done;
}
if (!linkp->ll_transient) {
avl_add(&dlmgmt_loan_avl, linkp);
linkp->ll_onloan = B_TRUE;
}
}
avl_remove(&dlmgmt_name_avl, linkp);
linkp->ll_zoneid = newzoneid;
avl_add(&dlmgmt_name_avl, linkp);
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_zoneboot(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
int err;
dlmgmt_door_zoneboot_t *zoneboot = argp;
dlmgmt_zoneboot_retval_t *retvalp = retp;
dlmgmt_table_lock(B_TRUE);
if ((err = dlmgmt_checkprivs(0, cred)) != 0)
goto done;
if (zoneid != GLOBAL_ZONEID) {
err = EACCES;
goto done;
}
if (zoneboot->ld_zoneid == GLOBAL_ZONEID) {
err = EINVAL;
goto done;
}
if ((err = dlmgmt_elevate_privileges()) == 0) {
err = dlmgmt_zone_init(zoneboot->ld_zoneid);
(void) dlmgmt_drop_privileges();
}
done:
dlmgmt_table_unlock();
retvalp->lr_err = err;
}
/* ARGSUSED */
static void
dlmgmt_zonehalt(void *argp, void *retp, size_t *sz, zoneid_t zoneid,
ucred_t *cred)
{
int err = 0;
dlmgmt_door_zonehalt_t *zonehalt = argp;
dlmgmt_zonehalt_retval_t *retvalp = retp;
if ((err = dlmgmt_checkprivs(0, cred)) == 0) {
if (zoneid != GLOBAL_ZONEID) {
err = EACCES;
} else if (zonehalt->ld_zoneid == GLOBAL_ZONEID) {
err = EINVAL;
} else {
dlmgmt_table_lock(B_TRUE);
dlmgmt_db_fini(zonehalt->ld_zoneid);
dlmgmt_table_unlock();
}
}
retvalp->lr_err = err;
}
static dlmgmt_door_info_t i_dlmgmt_door_info_tbl[] = {
{ DLMGMT_CMD_DLS_CREATE, sizeof (dlmgmt_upcall_arg_create_t),
sizeof (dlmgmt_create_retval_t), dlmgmt_upcall_create },
{ DLMGMT_CMD_DLS_GETATTR, sizeof (dlmgmt_upcall_arg_getattr_t),
sizeof (dlmgmt_getattr_retval_t), dlmgmt_upcall_getattr },
{ DLMGMT_CMD_DLS_DESTROY, sizeof (dlmgmt_upcall_arg_destroy_t),
sizeof (dlmgmt_destroy_retval_t), dlmgmt_upcall_destroy },
{ DLMGMT_CMD_GETNAME, sizeof (dlmgmt_door_getname_t),
sizeof (dlmgmt_getname_retval_t), dlmgmt_getname },
{ DLMGMT_CMD_GETLINKID, sizeof (dlmgmt_door_getlinkid_t),
sizeof (dlmgmt_getlinkid_retval_t), dlmgmt_getlinkid },
{ DLMGMT_CMD_GETNEXT, sizeof (dlmgmt_door_getnext_t),
sizeof (dlmgmt_getnext_retval_t), dlmgmt_getnext },
{ DLMGMT_CMD_DLS_UPDATE, sizeof (dlmgmt_upcall_arg_update_t),
sizeof (dlmgmt_update_retval_t), dlmgmt_upcall_update },
{ DLMGMT_CMD_CREATE_LINKID, sizeof (dlmgmt_door_createid_t),
sizeof (dlmgmt_createid_retval_t), dlmgmt_createid },
{ DLMGMT_CMD_DESTROY_LINKID, sizeof (dlmgmt_door_destroyid_t),
sizeof (dlmgmt_destroyid_retval_t), dlmgmt_destroyid },
{ DLMGMT_CMD_REMAP_LINKID, sizeof (dlmgmt_door_remapid_t),
sizeof (dlmgmt_remapid_retval_t), dlmgmt_remapid },
{ DLMGMT_CMD_CREATECONF, sizeof (dlmgmt_door_createconf_t),
sizeof (dlmgmt_createconf_retval_t), dlmgmt_createconf },
{ DLMGMT_CMD_OPENCONF, sizeof (dlmgmt_door_openconf_t),
sizeof (dlmgmt_openconf_retval_t), dlmgmt_openconf },
{ DLMGMT_CMD_WRITECONF, sizeof (dlmgmt_door_writeconf_t),
sizeof (dlmgmt_writeconf_retval_t), dlmgmt_writeconf },
{ DLMGMT_CMD_UP_LINKID, sizeof (dlmgmt_door_upid_t),
sizeof (dlmgmt_upid_retval_t), dlmgmt_upid },
{ DLMGMT_CMD_SETATTR, sizeof (dlmgmt_door_setattr_t),
sizeof (dlmgmt_setattr_retval_t), dlmgmt_setattr },
{ DLMGMT_CMD_UNSETATTR, sizeof (dlmgmt_door_unsetattr_t),
sizeof (dlmgmt_unsetattr_retval_t), dlmgmt_unsetconfattr },
{ DLMGMT_CMD_REMOVECONF, sizeof (dlmgmt_door_removeconf_t),
sizeof (dlmgmt_removeconf_retval_t), dlmgmt_removeconf },
{ DLMGMT_CMD_DESTROYCONF, sizeof (dlmgmt_door_destroyconf_t),
sizeof (dlmgmt_destroyconf_retval_t), dlmgmt_destroyconf },
{ DLMGMT_CMD_GETATTR, sizeof (dlmgmt_door_getattr_t),
sizeof (dlmgmt_getattr_retval_t), dlmgmt_getattr },
{ DLMGMT_CMD_GETCONFSNAPSHOT, sizeof (dlmgmt_door_getconfsnapshot_t),
sizeof (dlmgmt_getconfsnapshot_retval_t), dlmgmt_getconfsnapshot },
{ DLMGMT_CMD_LINKPROP_INIT, sizeof (dlmgmt_door_linkprop_init_t),
sizeof (dlmgmt_linkprop_init_retval_t),
dlmgmt_upcall_linkprop_init },
{ DLMGMT_CMD_SETZONEID, sizeof (dlmgmt_door_setzoneid_t),
sizeof (dlmgmt_setzoneid_retval_t), dlmgmt_setzoneid },
{ DLMGMT_CMD_ZONEBOOT, sizeof (dlmgmt_door_zoneboot_t),
sizeof (dlmgmt_zoneboot_retval_t), dlmgmt_zoneboot },
{ DLMGMT_CMD_ZONEHALT, sizeof (dlmgmt_door_zonehalt_t),
sizeof (dlmgmt_zonehalt_retval_t), dlmgmt_zonehalt },
{ 0, 0, 0, NULL }
};
static dlmgmt_door_info_t *
dlmgmt_getcmdinfo(int cmd)
{
dlmgmt_door_info_t *infop = i_dlmgmt_door_info_tbl;
while (infop->di_handler != NULL) {
if (infop->di_cmd == cmd)
break;
infop++;
}
return (infop);
}
/* ARGSUSED */
void
dlmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
uint_t n_desc)
{
dlmgmt_door_arg_t *door_arg = (dlmgmt_door_arg_t *)(void *)argp;
dlmgmt_door_info_t *infop = NULL;
dlmgmt_retval_t retval;
ucred_t *cred = NULL;
zoneid_t zoneid;
void *retvalp = NULL;
size_t sz, acksz;
int err = 0;
infop = dlmgmt_getcmdinfo(door_arg->ld_cmd);
if (infop == NULL || argsz != infop->di_reqsz) {
err = EINVAL;
goto done;
}
if (door_ucred(&cred) != 0 || (zoneid = ucred_getzoneid(cred)) == -1) {
err = errno;
goto done;
}
/*
* Note that malloc() cannot be used here because door_return
* never returns, and memory allocated by malloc() would get leaked.
* Use alloca() instead.
*/
acksz = infop->di_acksz;
again:
retvalp = alloca(acksz);
sz = acksz;
infop->di_handler(argp, retvalp, &acksz, zoneid, cred);
if (acksz > sz) {
/*
* If the specified buffer size is not big enough to hold the
* return value, reallocate the buffer and try to get the
* result one more time.
*/
assert(((dlmgmt_retval_t *)retvalp)->lr_err == ENOSPC);
goto again;
}
done:
if (cred != NULL)
ucred_free(cred);
if (err == 0) {
(void) door_return(retvalp, acksz, NULL, 0);
} else {
retval.lr_err = err;
(void) door_return((char *)&retval, sizeof (retval), NULL, 0);
}
}
/*
* 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2017 Joyent, Inc.
* Copyright 2023 Oxide Computer Company
*/
/*
* Functions to maintain a table of datalink configuration information.
*/
#ifndef _DLMGMT_IMPL_H
#define _DLMGMT_IMPL_H
#ifdef __cplusplus
extern "C" {
#endif
#include
#include
#include
/*
* datalink attribute structure
*/
typedef struct dlmgmt_linkattr_s {
struct dlmgmt_linkattr_s *lp_next;
struct dlmgmt_linkattr_s *lp_prev;
char lp_name[MAXLINKATTRLEN];
void *lp_val;
dladm_datatype_t lp_type;
uint_t lp_sz;
boolean_t lp_linkprop;
} dlmgmt_linkattr_t;
/*
* datalink structure
*/
typedef struct dlmgmt_link_s {
dlmgmt_linkattr_t *ll_head;
char ll_link[MAXLINKNAMELEN];
datalink_class_t ll_class;
uint32_t ll_media;
datalink_id_t ll_linkid;
/*
* The zone that owns the link. If this is set to the id of
* an NGZ and ll_onloan is set then the link was created and
* is owned by the GZ but is currently being loaned out to an
* NGZ. E.g., when the GZ admin creates a VNIC for exclusive
* use by an NGZ. If ll_onloan is set then ll_zoneid cannot be 0.
*
* If ll_zoneid is set to the id of an NGZ but ll_onloan is
* not set then the link was created by, and is owned by, the NGZ.
*/
zoneid_t ll_zoneid;
boolean_t ll_onloan;
avl_node_t ll_name_node;
avl_node_t ll_id_node;
avl_node_t ll_loan_node;
uint32_t ll_flags;
uint32_t ll_gen; /* generation number */
/*
* A transient link is one that is created and destroyed along with the
* lifetime of a zone. It is a non-persistent link that is owned by the
* zone (ll_zoneid is set to the id of the NGZ). It is specifically not
* on-loan from the GZ.
*/
boolean_t ll_transient;
} dlmgmt_link_t;
/*
* datalink configuration request structure
*/
typedef struct dlmgmt_dlconf_s {
dlmgmt_linkattr_t *ld_head;
char ld_link[MAXLINKNAMELEN];
datalink_id_t ld_linkid;
datalink_class_t ld_class;
uint32_t ld_media;
int ld_id;
zoneid_t ld_zoneid;
uint32_t ld_gen;
avl_node_t ld_node;
} dlmgmt_dlconf_t;
extern boolean_t debug;
extern const char *progname;
extern char cachefile[];
extern dladm_handle_t dld_handle;
extern datalink_id_t dlmgmt_nextlinkid;
extern avl_tree_t dlmgmt_name_avl;
extern avl_tree_t dlmgmt_id_avl;
extern avl_tree_t dlmgmt_loan_avl;
extern avl_tree_t dlmgmt_dlconf_avl;
boolean_t linkattr_equal(dlmgmt_linkattr_t **, const char *, void *,
size_t);
dlmgmt_linkattr_t *linkattr_find(dlmgmt_linkattr_t *, const char *);
void linkattr_unset(dlmgmt_linkattr_t **, const char *);
int linkattr_set(dlmgmt_linkattr_t **, const char *, void *,
size_t, dladm_datatype_t);
int linkattr_get(dlmgmt_linkattr_t **, const char *, void **,
size_t *, dladm_datatype_t *);
void linkattr_destroy(dlmgmt_link_t *);
void link_destroy(dlmgmt_link_t *);
int link_activate(dlmgmt_link_t *);
boolean_t link_is_visible(dlmgmt_link_t *, zoneid_t);
dlmgmt_link_t *link_by_id(datalink_id_t, zoneid_t);
dlmgmt_link_t *link_by_name(const char *, zoneid_t);
int dlmgmt_create_common(const char *, datalink_class_t,
uint32_t, zoneid_t, uint32_t, dlmgmt_link_t **);
int dlmgmt_destroy_common(dlmgmt_link_t *, uint32_t);
int dlmgmt_getattr_common(dlmgmt_linkattr_t **, const char *,
dlmgmt_getattr_retval_t *);
void dlmgmt_advance(dlmgmt_link_t *);
void dlmgmt_table_lock(boolean_t);
void dlmgmt_table_unlock();
int dlconf_create(const char *, datalink_id_t, datalink_class_t,
uint32_t, zoneid_t, dlmgmt_dlconf_t **);
void dlconf_destroy(dlmgmt_dlconf_t *);
void dlmgmt_advance_dlconfid(dlmgmt_dlconf_t *);
void dlmgmt_dlconf_table_lock(boolean_t);
void dlmgmt_dlconf_table_unlock(void);
int dlmgmt_generate_name(const char *, char *, size_t, zoneid_t);
void dlmgmt_linktable_init(void);
void dlmgmt_linktable_fini(void);
int dlmgmt_zone_init(zoneid_t);
int dlmgmt_elevate_privileges(void);
int dlmgmt_drop_privileges();
void dlmgmt_handler(void *, char *, size_t, door_desc_t *, uint_t);
void dlmgmt_log(int, const char *, ...);
int dlmgmt_write_db_entry(const char *, dlmgmt_link_t *, uint32_t);
int dlmgmt_delete_db_entry(dlmgmt_link_t *, uint32_t);
int dlmgmt_db_init(zoneid_t, char *);
void dlmgmt_db_fini(zoneid_t);
#ifdef __cplusplus
}
#endif
#endif /* _DLMGMT_IMPL_H */
/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2011 Joyent, Inc. All rights reserved.
*/
/*
* The dlmgmtd daemon is started by the datalink-management SMF service.
* This daemon is used to manage mapping and the
* persistent datalink configuration.
*
* Today, the mapping and the persistent configuration
* of datalinks is kept in /etc/dladm/datalink.conf, and the daemon keeps
* a copy of the datalinks in the memory (see dlmgmt_id_avl and
* dlmgmt_name_avl). The active mapping is kept in
* /etc/svc/volatile/dladm cache file, so that the mapping can be recovered
* when dlmgmtd exits for some reason (e.g., when dlmgmtd is accidentally
* killed).
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dlmgmt_impl.h"
const char *progname;
boolean_t debug;
static int pfds[2];
/*
* This file descriptor to DLMGMT_DOOR cannot be in the libdladm
* handle because the door isn't created when the handle is created.
*/
static int dlmgmt_door_fd = -1;
/*
* This libdladm handle is global so that dlmgmt_upcall_linkprop_init() can
* pass to libdladm. The handle is opened with "ALL" privileges, before
* privileges are dropped in dlmgmt_drop_privileges(). It is not able to open
* DLMGMT_DOOR at that time as it hasn't been created yet. This door in the
* handle is opened in the first call to dladm_door_fd().
*/
dladm_handle_t dld_handle = NULL;
static void dlmgmtd_exit(int);
static int dlmgmt_init();
static void dlmgmt_fini();
static int dlmgmt_set_privileges();
static int
dlmgmt_set_doorfd(boolean_t start)
{
dld_ioc_door_t did;
int err = 0;
assert(dld_handle != NULL);
did.did_start_door = start;
if (ioctl(dladm_dld_fd(dld_handle), DLDIOC_DOORSERVER, &did) == -1)
err = errno;
return (err);
}
static int
dlmgmt_door_init(void)
{
int err = 0;
if ((dlmgmt_door_fd = door_create(dlmgmt_handler, NULL,
DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) {
err = errno;
dlmgmt_log(LOG_ERR, "door_create() failed: %s",
strerror(err));
return (err);
}
return (err);
}
static void
dlmgmt_door_fini(void)
{
if (dlmgmt_door_fd == -1)
return;
if (door_revoke(dlmgmt_door_fd) == -1) {
dlmgmt_log(LOG_WARNING, "door_revoke(%s) failed: %s",
DLMGMT_DOOR, strerror(errno));
}
(void) dlmgmt_set_doorfd(B_FALSE);
dlmgmt_door_fd = -1;
}
static int
dlmgmt_door_attach(zoneid_t zoneid, char *rootdir)
{
int fd;
int err = 0;
char doorpath[MAXPATHLEN];
(void) snprintf(doorpath, sizeof (doorpath), "%s%s", rootdir,
DLMGMT_DOOR);
/*
* Create the door file for dlmgmtd.
*/
if ((fd = open(doorpath, O_CREAT|O_RDONLY, 0644)) == -1) {
err = errno;
dlmgmt_log(LOG_ERR, "open(%s) failed: %s", doorpath,
strerror(err));
return (err);
}
(void) close(fd);
if (chown(doorpath, UID_DLADM, GID_NETADM) == -1)
return (errno);
/*
* fdetach first in case a previous daemon instance exited
* ungracefully.
*/
(void) fdetach(doorpath);
if (fattach(dlmgmt_door_fd, doorpath) != 0) {
err = errno;
dlmgmt_log(LOG_ERR, "fattach(%s) failed: %s", doorpath,
strerror(err));
} else if (zoneid == GLOBAL_ZONEID) {
if ((err = dlmgmt_set_doorfd(B_TRUE)) != 0) {
dlmgmt_log(LOG_ERR, "cannot set kernel doorfd: %s",
strerror(err));
}
}
return (err);
}
/*
* Create the /etc/svc/volatile/dladm/ directory if it doesn't exist, load the
* datalink.conf data for this zone, and create/attach the door rendezvous
* file.
*/
int
dlmgmt_zone_init(zoneid_t zoneid)
{
char rootdir[MAXPATHLEN], tmpfsdir[MAXPATHLEN];
int err;
struct stat statbuf;
if (zoneid == GLOBAL_ZONEID) {
rootdir[0] = '\0';
} else if (zone_getattr(zoneid, ZONE_ATTR_ROOT, rootdir,
sizeof (rootdir)) < 0) {
return (errno);
}
/*
* Create the DLMGMT_TMPFS_DIR directory.
*/
(void) snprintf(tmpfsdir, sizeof (tmpfsdir), "%s%s", rootdir,
DLMGMT_TMPFS_DIR);
if (stat(tmpfsdir, &statbuf) < 0) {
if (mkdir(tmpfsdir, (mode_t)0755) < 0)
return (errno);
} else if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
return (ENOTDIR);
}
if ((chmod(tmpfsdir, 0755) < 0) ||
(chown(tmpfsdir, UID_DLADM, GID_NETADM) < 0)) {
return (EPERM);
}
if ((err = dlmgmt_db_init(zoneid, rootdir)) != 0)
return (err);
return (dlmgmt_door_attach(zoneid, rootdir));
}
/*
* Initialize each running zone.
*/
static int
dlmgmt_allzones_init(void)
{
int i;
zoneid_t *zids = NULL;
uint_t nzids, nzids_saved;
if (zone_list(NULL, &nzids) != 0)
return (errno);
again:
nzids *= 2;
if ((zids = malloc(nzids * sizeof (zoneid_t))) == NULL)
return (errno);
nzids_saved = nzids;
if (zone_list(zids, &nzids) != 0) {
free(zids);
return (errno);
}
if (nzids > nzids_saved) {
free(zids);
goto again;
}
for (i = 0; i < nzids; i++) {
int res;
zone_status_t status;
/*
* Skip over zones that have gone away or are going down
* since we got the list. Process all zones in the list,
* logging errors for any that failed.
*/
if (zone_getattr(zids[i], ZONE_ATTR_STATUS, &status,
sizeof (status)) < 0) {
continue;
}
switch (status) {
case ZONE_IS_SHUTTING_DOWN:
case ZONE_IS_EMPTY:
case ZONE_IS_DOWN:
case ZONE_IS_DYING:
case ZONE_IS_DEAD:
case ZONE_IS_INITIALIZED:
case ZONE_IS_UNINITIALIZED:
continue;
default:
break;
}
if ((res = dlmgmt_zone_init(zids[i])) != 0) {
(void) fprintf(stderr, "zone (%ld) init error %s",
zids[i], strerror(res));
dlmgmt_log(LOG_ERR, "zone (%d) init error %s",
zids[i], strerror(res));
}
}
free(zids);
return (0);
}
static int
dlmgmt_init(void)
{
int err;
char *fmri, *c;
char filename[MAXPATHLEN];
if (dladm_open(&dld_handle) != DLADM_STATUS_OK) {
dlmgmt_log(LOG_ERR, "dladm_open() failed");
return (EPERM);
}
if (signal(SIGTERM, dlmgmtd_exit) == SIG_ERR ||
signal(SIGINT, dlmgmtd_exit) == SIG_ERR) {
err = errno;
dlmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s",
strerror(err));
return (err);
}
/*
* First derive the name of the cache file from the FMRI name. This
* cache name is used to keep active datalink configuration.
*/
if (debug) {
(void) snprintf(cachefile, MAXPATHLEN, "%s/%s%s",
DLMGMT_TMPFS_DIR, progname, ".debug.cache");
} else {
if ((fmri = getenv("SMF_FMRI")) == NULL) {
dlmgmt_log(LOG_ERR, "dlmgmtd is an smf(7) managed "
"service and should not be run from the command "
"line.");
return (EINVAL);
}
/*
* The FMRI name is in the form of
* svc:/service/service:instance. We need to remove the
* prefix "svc:/" and replace '/' with '-'. The cache file
* name is in the form of "service:instance.cache".
*/
if ((c = strchr(fmri, '/')) != NULL)
c++;
else
c = fmri;
(void) snprintf(filename, MAXPATHLEN, "%s.cache", c);
c = filename;
while ((c = strchr(c, '/')) != NULL)
*c = '-';
(void) snprintf(cachefile, MAXPATHLEN, "%s/%s",
DLMGMT_TMPFS_DIR, filename);
}
dlmgmt_linktable_init();
if ((err = dlmgmt_door_init()) != 0)
goto done;
/*
* Load datalink configuration and create dlmgmtd door files for all
* currently running zones.
*/
if ((err = dlmgmt_allzones_init()) != 0)
dlmgmt_door_fini();
done:
if (err != 0)
dlmgmt_linktable_fini();
return (err);
}
static void
dlmgmt_fini(void)
{
dlmgmt_door_fini();
dlmgmt_linktable_fini();
if (dld_handle != NULL) {
dladm_close(dld_handle);
dld_handle = NULL;
}
}
/*
* This is called by the child process to inform the parent process to
* exit with the given return value.
*/
static void
dlmgmt_inform_parent_exit(int rv)
{
if (debug)
return;
if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) {
dlmgmt_log(LOG_WARNING,
"dlmgmt_inform_parent_exit() failed: %s", strerror(errno));
(void) close(pfds[1]);
exit(EXIT_FAILURE);
}
(void) close(pfds[1]);
}
/*ARGSUSED*/
static void
dlmgmtd_exit(int signo)
{
(void) close(pfds[1]);
dlmgmt_fini();
exit(EXIT_FAILURE);
}
static void
usage(void)
{
(void) fprintf(stderr, "Usage: %s [-d]\n", progname);
exit(EXIT_FAILURE);
}
/*
* Restrict privileges to only those needed.
*/
int
dlmgmt_drop_privileges(void)
{
priv_set_t *pset;
priv_ptype_t ptype;
zoneid_t zoneid = getzoneid();
int err = 0;
if ((pset = priv_allocset()) == NULL)
return (errno);
/*
* The global zone needs PRIV_PROC_FORK so that it can fork() when it
* issues db ops in non-global zones, PRIV_SYS_CONFIG to post
* sysevents, and PRIV_SYS_DL_CONFIG to initialize link properties in
* dlmgmt_upcall_linkprop_init().
*
* We remove non-basic privileges from the permitted (and thus
* effective) set. When executing in a non-global zone, dlmgmtd
* only needs to read and write to files that it already owns.
*/
priv_basicset(pset);
(void) priv_delset(pset, PRIV_PROC_EXEC);
(void) priv_delset(pset, PRIV_PROC_INFO);
(void) priv_delset(pset, PRIV_PROC_SESSION);
(void) priv_delset(pset, PRIV_FILE_LINK_ANY);
if (zoneid == GLOBAL_ZONEID) {
ptype = PRIV_EFFECTIVE;
if (priv_addset(pset, PRIV_SYS_CONFIG) == -1 ||
priv_addset(pset, PRIV_SYS_DL_CONFIG) == -1)
err = errno;
} else {
(void) priv_delset(pset, PRIV_PROC_FORK);
ptype = PRIV_PERMITTED;
}
if (err == 0 && setppriv(PRIV_SET, ptype, pset) == -1)
err = errno;
priv_freeset(pset);
return (err);
}
int
dlmgmt_elevate_privileges(void)
{
priv_set_t *privset;
int err = 0;
if ((privset = priv_str_to_set("zone", ",", NULL)) == NULL)
return (errno);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, privset) == -1)
err = errno;
priv_freeset(privset);
return (err);
}
/*
* Set the uid of this daemon to the "dladm" user and drop privileges to only
* those needed.
*/
static int
dlmgmt_set_privileges(void)
{
int err;
(void) setgroups(0, NULL);
if (setegid(GID_NETADM) == -1 || seteuid(UID_DLADM) == -1)
err = errno;
else
err = dlmgmt_drop_privileges();
return (err);
}
/*
* Keep the pfds fd open, close other fds.
*/
/*ARGSUSED*/
static int
closefunc(void *arg, int fd)
{
if (fd != pfds[1])
(void) close(fd);
return (0);
}
static boolean_t
dlmgmt_daemonize(void)
{
pid_t pid;
int rv;
if (pipe(pfds) < 0) {
(void) fprintf(stderr, "%s: pipe() failed: %s\n",
progname, strerror(errno));
exit(EXIT_FAILURE);
}
if ((pid = fork()) == -1) {
(void) fprintf(stderr, "%s: fork() failed: %s\n",
progname, strerror(errno));
exit(EXIT_FAILURE);
} else if (pid > 0) { /* Parent */
(void) close(pfds[1]);
/*
* Read the child process's return value from the pfds.
* If the child process exits unexpected, read() returns -1.
*/
if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) {
(void) kill(pid, SIGKILL);
rv = EXIT_FAILURE;
}
(void) close(pfds[0]);
exit(rv);
}
/* Child */
(void) close(pfds[0]);
(void) setsid();
/*
* Close all files except pfds[1].
*/
(void) fdwalk(closefunc, NULL);
(void) chdir("/");
openlog(progname, LOG_PID, LOG_DAEMON);
return (B_TRUE);
}
int
main(int argc, char *argv[])
{
int opt, err;
progname = strrchr(argv[0], '/');
if (progname != NULL)
progname++;
else
progname = argv[0];
/*
* Process options.
*/
while ((opt = getopt(argc, argv, "d")) != EOF) {
switch (opt) {
case 'd':
debug = B_TRUE;
break;
default:
usage();
}
}
if (!debug && !dlmgmt_daemonize())
return (EXIT_FAILURE);
if ((err = dlmgmt_init()) != 0) {
dlmgmt_log(LOG_ERR, "unable to initialize daemon: %s",
strerror(err));
goto child_out;
} else if ((err = dlmgmt_set_privileges()) != 0) {
dlmgmt_log(LOG_ERR, "unable to set daemon privileges: %s",
strerror(err));
dlmgmt_fini();
goto child_out;
}
/*
* Inform the parent process that it can successfully exit.
*/
dlmgmt_inform_parent_exit(EXIT_SUCCESS);
for (;;)
(void) pause();
child_out:
/* return from main() forcibly exits an MT process */
dlmgmt_inform_parent_exit(EXIT_FAILURE);
return (EXIT_FAILURE);
}
/*
* 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2017 Joyent, Inc.
*/
/*
* Utility functions used by the dlmgmtd daemon.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dlmgmt_impl.h"
/*
* There are three datalink AVL tables. The dlmgmt_name_avl tree contains all
* datalinks and is keyed by zoneid and link name. The dlmgmt_id_avl also
* contains all datalinks, and it is keyed by link ID. The dlmgmt_loan_avl is
* keyed by link name, and contains the set of global-zone links that are
* currently on loan to non-global zones.
*/
avl_tree_t dlmgmt_name_avl;
avl_tree_t dlmgmt_id_avl;
avl_tree_t dlmgmt_loan_avl;
avl_tree_t dlmgmt_dlconf_avl;
static pthread_rwlock_t dlmgmt_avl_lock = PTHREAD_RWLOCK_INITIALIZER;
static pthread_mutex_t dlmgmt_avl_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t dlmgmt_avl_cv = PTHREAD_COND_INITIALIZER;
static pthread_rwlock_t dlmgmt_dlconf_lock = PTHREAD_RWLOCK_INITIALIZER;
typedef struct dlmgmt_prefix {
struct dlmgmt_prefix *lp_next;
char lp_prefix[MAXLINKNAMELEN];
zoneid_t lp_zoneid;
uint_t lp_nextppa;
} dlmgmt_prefix_t;
static dlmgmt_prefix_t dlmgmt_prefixlist;
datalink_id_t dlmgmt_nextlinkid;
static datalink_id_t dlmgmt_nextconfid = 1;
static void dlmgmt_advance_linkid(dlmgmt_link_t *);
static void dlmgmt_advance_ppa(dlmgmt_link_t *);
void
dlmgmt_log(int pri, const char *fmt, ...)
{
va_list alist;
va_start(alist, fmt);
if (debug) {
(void) vfprintf(stderr, fmt, alist);
(void) fputc('\n', stderr);
} else {
vsyslog(pri, fmt, alist);
}
va_end(alist);
}
static int
cmp_link_by_name(const void *v1, const void *v2)
{
const dlmgmt_link_t *link1 = v1;
const dlmgmt_link_t *link2 = v2;
int cmp;
cmp = strcmp(link1->ll_link, link2->ll_link);
return ((cmp == 0) ? 0 : ((cmp < 0) ? -1 : 1));
}
/*
* Note that the zoneid associated with a link is effectively part of its
* name. This is essentially what results in having each zone have disjoint
* datalink namespaces.
*/
static int
cmp_link_by_zname(const void *v1, const void *v2)
{
const dlmgmt_link_t *link1 = v1;
const dlmgmt_link_t *link2 = v2;
if (link1->ll_zoneid < link2->ll_zoneid)
return (-1);
if (link1->ll_zoneid > link2->ll_zoneid)
return (1);
return (cmp_link_by_name(link1, link2));
}
static int
cmp_link_by_id(const void *v1, const void *v2)
{
const dlmgmt_link_t *link1 = v1;
const dlmgmt_link_t *link2 = v2;
if ((uint64_t)(link1->ll_linkid) == (uint64_t)(link2->ll_linkid))
return (0);
else if ((uint64_t)(link1->ll_linkid) < (uint64_t)(link2->ll_linkid))
return (-1);
else
return (1);
}
static int
cmp_dlconf_by_id(const void *v1, const void *v2)
{
const dlmgmt_dlconf_t *dlconfp1 = v1;
const dlmgmt_dlconf_t *dlconfp2 = v2;
if (dlconfp1->ld_id == dlconfp2->ld_id)
return (0);
else if (dlconfp1->ld_id < dlconfp2->ld_id)
return (-1);
else
return (1);
}
void
dlmgmt_linktable_init(void)
{
/*
* Initialize the prefix list. First add the "net" prefix for the
* global zone to the list.
*/
dlmgmt_prefixlist.lp_next = NULL;
dlmgmt_prefixlist.lp_zoneid = GLOBAL_ZONEID;
dlmgmt_prefixlist.lp_nextppa = 0;
(void) strlcpy(dlmgmt_prefixlist.lp_prefix, "net", MAXLINKNAMELEN);
avl_create(&dlmgmt_name_avl, cmp_link_by_zname, sizeof (dlmgmt_link_t),
offsetof(dlmgmt_link_t, ll_name_node));
avl_create(&dlmgmt_id_avl, cmp_link_by_id, sizeof (dlmgmt_link_t),
offsetof(dlmgmt_link_t, ll_id_node));
avl_create(&dlmgmt_loan_avl, cmp_link_by_name, sizeof (dlmgmt_link_t),
offsetof(dlmgmt_link_t, ll_loan_node));
avl_create(&dlmgmt_dlconf_avl, cmp_dlconf_by_id,
sizeof (dlmgmt_dlconf_t), offsetof(dlmgmt_dlconf_t, ld_node));
dlmgmt_nextlinkid = 1;
}
void
dlmgmt_linktable_fini(void)
{
dlmgmt_prefix_t *lpp, *next;
for (lpp = dlmgmt_prefixlist.lp_next; lpp != NULL; lpp = next) {
next = lpp->lp_next;
free(lpp);
}
avl_destroy(&dlmgmt_dlconf_avl);
avl_destroy(&dlmgmt_name_avl);
avl_destroy(&dlmgmt_loan_avl);
avl_destroy(&dlmgmt_id_avl);
}
static void
linkattr_add(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
{
if (*headp == NULL) {
*headp = attrp;
} else {
(*headp)->lp_prev = attrp;
attrp->lp_next = *headp;
*headp = attrp;
}
}
static void
linkattr_rm(dlmgmt_linkattr_t **headp, dlmgmt_linkattr_t *attrp)
{
dlmgmt_linkattr_t *next, *prev;
next = attrp->lp_next;
prev = attrp->lp_prev;
if (next != NULL)
next->lp_prev = prev;
if (prev != NULL)
prev->lp_next = next;
else
*headp = next;
}
dlmgmt_linkattr_t *
linkattr_find(dlmgmt_linkattr_t *headp, const char *attr)
{
dlmgmt_linkattr_t *attrp;
for (attrp = headp; attrp != NULL; attrp = attrp->lp_next) {
if (strcmp(attrp->lp_name, attr) == 0)
break;
}
return (attrp);
}
int
linkattr_set(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
size_t attrsz, dladm_datatype_t type)
{
dlmgmt_linkattr_t *attrp;
void *newval;
boolean_t new;
attrp = linkattr_find(*headp, attr);
if (attrp != NULL) {
/*
* It is already set. If the value changed, update it.
*/
if (linkattr_equal(headp, attr, attrval, attrsz))
return (0);
new = B_FALSE;
} else {
/*
* It is not set yet, allocate the linkattr and prepend to the
* list.
*/
if ((attrp = calloc(1, sizeof (dlmgmt_linkattr_t))) == NULL)
return (ENOMEM);
(void) strlcpy(attrp->lp_name, attr, MAXLINKATTRLEN);
new = B_TRUE;
}
if ((newval = calloc(1, attrsz)) == NULL) {
if (new)
free(attrp);
return (ENOMEM);
}
if (!new)
free(attrp->lp_val);
attrp->lp_val = newval;
bcopy(attrval, attrp->lp_val, attrsz);
attrp->lp_sz = attrsz;
attrp->lp_type = type;
attrp->lp_linkprop = dladm_attr_is_linkprop(attr);
if (new)
linkattr_add(headp, attrp);
return (0);
}
void
linkattr_unset(dlmgmt_linkattr_t **headp, const char *attr)
{
dlmgmt_linkattr_t *attrp;
if ((attrp = linkattr_find(*headp, attr)) != NULL) {
linkattr_rm(headp, attrp);
free(attrp->lp_val);
free(attrp);
}
}
int
linkattr_get(dlmgmt_linkattr_t **headp, const char *attr, void **attrvalp,
size_t *attrszp, dladm_datatype_t *typep)
{
dlmgmt_linkattr_t *attrp;
if ((attrp = linkattr_find(*headp, attr)) == NULL)
return (ENOENT);
*attrvalp = attrp->lp_val;
*attrszp = attrp->lp_sz;
if (typep != NULL)
*typep = attrp->lp_type;
return (0);
}
boolean_t
linkattr_equal(dlmgmt_linkattr_t **headp, const char *attr, void *attrval,
size_t attrsz)
{
void *saved_attrval;
size_t saved_attrsz;
if (linkattr_get(headp, attr, &saved_attrval, &saved_attrsz, NULL) != 0)
return (B_FALSE);
return ((saved_attrsz == attrsz) &&
(memcmp(saved_attrval, attrval, attrsz) == 0));
}
void
linkattr_destroy(dlmgmt_link_t *linkp)
{
dlmgmt_linkattr_t *next, *attrp;
for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
next = attrp->lp_next;
free(attrp->lp_val);
free(attrp);
}
}
static int
dlmgmt_table_readwritelock(boolean_t write)
{
if (write)
return (pthread_rwlock_trywrlock(&dlmgmt_avl_lock));
else
return (pthread_rwlock_tryrdlock(&dlmgmt_avl_lock));
}
void
dlmgmt_table_lock(boolean_t write)
{
(void) pthread_mutex_lock(&dlmgmt_avl_mutex);
while (dlmgmt_table_readwritelock(write) == EBUSY)
(void) pthread_cond_wait(&dlmgmt_avl_cv, &dlmgmt_avl_mutex);
(void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
}
void
dlmgmt_table_unlock(void)
{
(void) pthread_rwlock_unlock(&dlmgmt_avl_lock);
(void) pthread_mutex_lock(&dlmgmt_avl_mutex);
(void) pthread_cond_broadcast(&dlmgmt_avl_cv);
(void) pthread_mutex_unlock(&dlmgmt_avl_mutex);
}
void
link_destroy(dlmgmt_link_t *linkp)
{
linkattr_destroy(linkp);
free(linkp);
}
/*
* Set the DLMGMT_ACTIVE flag on the link to note that it is active.
* When a link is active and owned by an NGZ then it is added to
* that zone's datalink list.
*/
int
link_activate(dlmgmt_link_t *linkp)
{
int err = 0;
zoneid_t zoneid = ALL_ZONES;
/*
* If zone_check_datalink() returns 0 it means we found the
* link in one of the NGZ's datalink lists. Otherwise the link
* is under the GZ.
*/
if (zone_check_datalink(&zoneid, linkp->ll_linkid) == 0) {
/*
* This is a bit subtle. If the following expression
* is true then the link was found in one of the NGZ's
* datalink lists but the link structure has it under
* the GZ. This means that the link is supposed to be
* loaned out to an NGZ but the dlmgmtd state is out
* of sync -- possibly due to the process restarting.
* In this case we need to sync the dlmgmtd state by
* marking it as on-loan to the NGZ it's currently
* under.
*/
if (zoneid != linkp->ll_zoneid) {
assert(linkp->ll_zoneid == 0);
assert(linkp->ll_onloan == B_FALSE);
assert(linkp->ll_transient == 0);
/*
* If dlmgmtd already has a link with this
* name under the NGZ then we have a problem.
*/
if (link_by_name(linkp->ll_link, zoneid) != NULL) {
err = EEXIST;
goto done;
}
/*
* Remove the current linkp entry from the
* list because it's under the wrong zoneid.
* We don't have to update the dlmgmt_id_avl
* because it compares entries by ll_linkid
* only.
*/
if (avl_find(&dlmgmt_name_avl, linkp, NULL) != NULL)
avl_remove(&dlmgmt_name_avl, linkp);
/*
* Update the link to reflect the fact that
* it's on-loan to an NGZ and re-add it to the
* list.
*/
linkp->ll_zoneid = zoneid;
avl_add(&dlmgmt_name_avl, linkp);
/*
* Since the link was found to be in a zone but
* recorded as a GZ link in the link structure, and
* we've now updated that, also mark it as on-loan to
* the NGZ.
*/
avl_add(&dlmgmt_loan_avl, linkp);
linkp->ll_onloan = B_TRUE;
}
} else if (linkp->ll_zoneid != GLOBAL_ZONEID) {
/*
* In this case the link was not found under any NGZ
* but according to its ll_zoneid member it is owned
* by an NGZ. Add the datalink to the appropriate zone
* datalink list.
*/
err = zone_add_datalink(linkp->ll_zoneid, linkp->ll_linkid);
assert(linkp->ll_onloan == B_FALSE);
}
done:
if (err == 0)
linkp->ll_flags |= DLMGMT_ACTIVE;
return (err);
}
/*
* Is linkp visible from the caller's zoneid? It is if the link is in the
* same zone as the caller, or if the caller is in the global zone and the
* link is on loan to a non-global zone.
*/
boolean_t
link_is_visible(dlmgmt_link_t *linkp, zoneid_t zoneid)
{
return (linkp->ll_zoneid == zoneid ||
(zoneid == GLOBAL_ZONEID && linkp->ll_onloan));
}
dlmgmt_link_t *
link_by_id(datalink_id_t linkid, zoneid_t zoneid)
{
dlmgmt_link_t link, *linkp;
link.ll_linkid = linkid;
if ((linkp = avl_find(&dlmgmt_id_avl, &link, NULL)) == NULL)
return (NULL);
if (zoneid != GLOBAL_ZONEID && linkp->ll_zoneid != zoneid)
return (NULL);
return (linkp);
}
dlmgmt_link_t *
link_by_name(const char *name, zoneid_t zoneid)
{
dlmgmt_link_t link, *linkp;
(void) strlcpy(link.ll_link, name, MAXLINKNAMELEN);
link.ll_zoneid = zoneid;
linkp = avl_find(&dlmgmt_name_avl, &link, NULL);
if (linkp == NULL && zoneid == GLOBAL_ZONEID) {
/* The link could be on loan to a non-global zone? */
linkp = avl_find(&dlmgmt_loan_avl, &link, NULL);
}
return (linkp);
}
int
dlmgmt_create_common(const char *name, datalink_class_t class, uint32_t media,
zoneid_t zoneid, uint32_t flags, dlmgmt_link_t **linkpp)
{
dlmgmt_link_t *linkp = NULL;
avl_index_t name_where, id_where;
int err = 0;
if (!dladm_valid_linkname(name))
return (EINVAL);
if (dlmgmt_nextlinkid == DATALINK_INVALID_LINKID)
return (ENOSPC);
if (flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST | DLMGMT_TRANSIENT) ||
((flags & DLMGMT_PERSIST) && (flags & DLMGMT_TRANSIENT)) ||
flags == 0) {
return (EINVAL);
}
if ((linkp = calloc(1, sizeof (dlmgmt_link_t))) == NULL) {
err = ENOMEM;
goto done;
}
(void) strlcpy(linkp->ll_link, name, MAXLINKNAMELEN);
linkp->ll_class = class;
linkp->ll_media = media;
linkp->ll_linkid = dlmgmt_nextlinkid;
linkp->ll_zoneid = zoneid;
linkp->ll_gen = 0;
/*
* While DLMGMT_TRANSIENT starts off as a flag it is converted
* into a link field since it is really a substate of
* DLMGMT_ACTIVE -- it should not survive as a flag beyond
* this point.
*/
linkp->ll_transient = (flags & DLMGMT_TRANSIENT) ? B_TRUE : B_FALSE;
flags &= ~DLMGMT_TRANSIENT;
if (avl_find(&dlmgmt_name_avl, linkp, &name_where) != NULL ||
avl_find(&dlmgmt_id_avl, linkp, &id_where) != NULL) {
err = EEXIST;
goto done;
}
avl_insert(&dlmgmt_name_avl, linkp, name_where);
avl_insert(&dlmgmt_id_avl, linkp, id_where);
if ((flags & DLMGMT_ACTIVE) && (err = link_activate(linkp)) != 0) {
avl_remove(&dlmgmt_name_avl, linkp);
avl_remove(&dlmgmt_id_avl, linkp);
goto done;
}
linkp->ll_flags = flags;
dlmgmt_advance(linkp);
*linkpp = linkp;
done:
if (err != 0)
free(linkp);
return (err);
}
int
dlmgmt_destroy_common(dlmgmt_link_t *linkp, uint32_t flags)
{
/*
* After dlmgmt_create_common() the link flags should only
* ever include ACTIVE or PERSIST.
*/
assert((linkp->ll_flags & ~(DLMGMT_ACTIVE | DLMGMT_PERSIST)) == 0);
if ((linkp->ll_flags & flags) == 0) {
/*
* The link does not exist in the specified space.
*/
return (ENOENT);
}
linkp->ll_flags &= ~flags;
if (flags & DLMGMT_PERSIST) {
dlmgmt_linkattr_t *next, *attrp;
for (attrp = linkp->ll_head; attrp != NULL; attrp = next) {
next = attrp->lp_next;
free(attrp->lp_val);
free(attrp);
}
linkp->ll_head = NULL;
}
if ((flags & DLMGMT_ACTIVE) && linkp->ll_zoneid != GLOBAL_ZONEID) {
(void) zone_remove_datalink(linkp->ll_zoneid, linkp->ll_linkid);
if (linkp->ll_onloan)
avl_remove(&dlmgmt_loan_avl, linkp);
}
if (linkp->ll_flags == 0) {
avl_remove(&dlmgmt_id_avl, linkp);
avl_remove(&dlmgmt_name_avl, linkp);
link_destroy(linkp);
}
return (0);
}
int
dlmgmt_getattr_common(dlmgmt_linkattr_t **headp, const char *attr,
dlmgmt_getattr_retval_t *retvalp)
{
int err;
void *attrval;
size_t attrsz;
dladm_datatype_t attrtype;
err = linkattr_get(headp, attr, &attrval, &attrsz, &attrtype);
if (err != 0)
return (err);
assert(attrsz > 0);
if (attrsz > MAXLINKATTRVALLEN)
return (EINVAL);
retvalp->lr_type = attrtype;
retvalp->lr_attrsz = attrsz;
bcopy(attrval, retvalp->lr_attrval, attrsz);
return (0);
}
void
dlmgmt_dlconf_table_lock(boolean_t write)
{
if (write)
(void) pthread_rwlock_wrlock(&dlmgmt_dlconf_lock);
else
(void) pthread_rwlock_rdlock(&dlmgmt_dlconf_lock);
}
void
dlmgmt_dlconf_table_unlock(void)
{
(void) pthread_rwlock_unlock(&dlmgmt_dlconf_lock);
}
int
dlconf_create(const char *name, datalink_id_t linkid, datalink_class_t class,
uint32_t media, zoneid_t zoneid, dlmgmt_dlconf_t **dlconfpp)
{
dlmgmt_dlconf_t *dlconfp = NULL;
int err = 0;
if (dlmgmt_nextconfid == 0) {
err = ENOSPC;
goto done;
}
if ((dlconfp = calloc(1, sizeof (dlmgmt_dlconf_t))) == NULL) {
err = ENOMEM;
goto done;
}
(void) strlcpy(dlconfp->ld_link, name, MAXLINKNAMELEN);
dlconfp->ld_linkid = linkid;
dlconfp->ld_class = class;
dlconfp->ld_media = media;
dlconfp->ld_id = dlmgmt_nextconfid;
dlconfp->ld_zoneid = zoneid;
done:
*dlconfpp = dlconfp;
return (err);
}
void
dlconf_destroy(dlmgmt_dlconf_t *dlconfp)
{
dlmgmt_linkattr_t *next, *attrp;
for (attrp = dlconfp->ld_head; attrp != NULL; attrp = next) {
next = attrp->lp_next;
free(attrp->lp_val);
free(attrp);
}
free(dlconfp);
}
int
dlmgmt_generate_name(const char *prefix, char *name, size_t size,
zoneid_t zoneid)
{
dlmgmt_prefix_t *lpp, *prev = NULL;
dlmgmt_link_t link, *linkp;
/*
* See whether the requested prefix is already in the list.
*/
for (lpp = &dlmgmt_prefixlist; lpp != NULL;
prev = lpp, lpp = lpp->lp_next) {
if (lpp->lp_zoneid == zoneid &&
strcmp(prefix, lpp->lp_prefix) == 0)
break;
}
/*
* Not found.
*/
if (lpp == NULL) {
assert(prev != NULL);
/*
* First add this new prefix into the prefix list.
*/
if ((lpp = malloc(sizeof (dlmgmt_prefix_t))) == NULL)
return (ENOMEM);
prev->lp_next = lpp;
lpp->lp_next = NULL;
lpp->lp_zoneid = zoneid;
lpp->lp_nextppa = 0;
(void) strlcpy(lpp->lp_prefix, prefix, MAXLINKNAMELEN);
/*
* Now determine this prefix's nextppa.
*/
(void) snprintf(link.ll_link, MAXLINKNAMELEN, "%s%d",
prefix, 0);
link.ll_zoneid = zoneid;
if ((linkp = avl_find(&dlmgmt_name_avl, &link, NULL)) != NULL)
dlmgmt_advance_ppa(linkp);
}
if (lpp->lp_nextppa == (uint_t)-1)
return (ENOSPC);
(void) snprintf(name, size, "%s%d", prefix, lpp->lp_nextppa);
return (0);
}
/*
* Advance the next available ppa value if the name prefix of the current
* link is in the prefix list.
*/
static void
dlmgmt_advance_ppa(dlmgmt_link_t *linkp)
{
dlmgmt_prefix_t *lpp;
char prefix[MAXLINKNAMELEN];
char linkname[MAXLINKNAMELEN];
uint_t start, ppa;
(void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
/*
* See whether the requested prefix is already in the list.
*/
for (lpp = &dlmgmt_prefixlist; lpp != NULL; lpp = lpp->lp_next) {
if (lpp->lp_zoneid == linkp->ll_zoneid &&
strcmp(prefix, lpp->lp_prefix) == 0)
break;
}
/*
* If the link name prefix is in the list, advance the
* next available ppa for the N name.
*/
if (lpp == NULL || lpp->lp_nextppa != ppa)
return;
start = lpp->lp_nextppa++;
linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
while (lpp->lp_nextppa != start) {
if (lpp->lp_nextppa == (uint_t)-1) {
/*
* wrapped around. search from 1.
*/
lpp->lp_nextppa = 0;
(void) snprintf(linkname, MAXLINKNAMELEN,
"%s%d", lpp->lp_prefix, lpp->lp_nextppa);
linkp = link_by_name(linkname, lpp->lp_zoneid);
if (linkp == NULL)
return;
} else {
if (linkp == NULL)
return;
(void) dlpi_parselink(linkp->ll_link, prefix, &ppa);
if ((strcmp(prefix, lpp->lp_prefix) != 0) ||
(ppa != lpp->lp_nextppa)) {
return;
}
}
linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
lpp->lp_nextppa++;
}
lpp->lp_nextppa = (uint_t)-1;
}
/*
* Advance to the next available linkid value.
*/
static void
dlmgmt_advance_linkid(dlmgmt_link_t *linkp)
{
datalink_id_t start;
if (linkp->ll_linkid != dlmgmt_nextlinkid)
return;
start = dlmgmt_nextlinkid;
linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
do {
if (dlmgmt_nextlinkid == DATALINK_MAX_LINKID) {
/*
* wrapped around. search from 1.
*/
dlmgmt_nextlinkid = 1;
if ((linkp = link_by_id(1, GLOBAL_ZONEID)) == NULL)
return;
} else {
dlmgmt_nextlinkid++;
if (linkp == NULL)
return;
if (linkp->ll_linkid != dlmgmt_nextlinkid)
return;
}
linkp = AVL_NEXT(&dlmgmt_id_avl, linkp);
} while (dlmgmt_nextlinkid != start);
dlmgmt_nextlinkid = DATALINK_INVALID_LINKID;
}
/*
* Advance various global values, for example, next linkid value, next ppa for
* various prefix etc.
*/
void
dlmgmt_advance(dlmgmt_link_t *linkp)
{
dlmgmt_advance_linkid(linkp);
dlmgmt_advance_ppa(linkp);
}
/*
* Advance to the next available dlconf id.
*/
void
dlmgmt_advance_dlconfid(dlmgmt_dlconf_t *dlconfp)
{
uint_t start;
start = dlmgmt_nextconfid++;
dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
while (dlmgmt_nextconfid != start) {
if (dlmgmt_nextconfid == 0) {
dlmgmt_dlconf_t dlconf;
/*
* wrapped around. search from 1.
*/
dlconf.ld_id = dlmgmt_nextconfid = 1;
dlconfp = avl_find(&dlmgmt_dlconf_avl, &dlconf, NULL);
if (dlconfp == NULL)
return;
} else {
if ((dlconfp == NULL) ||
(dlconfp->ld_id != dlmgmt_nextconfid)) {
return;
}
}
dlconfp = AVL_NEXT(&dlmgmt_dlconf_avl, dlconfp);
dlmgmt_nextconfid++;
}
dlmgmt_nextconfid = 0;
}
#!/sbin/sh
#
# 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 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
# Copyright 2012 Joyent, Inc. All rights reserved.
#
. /lib/svc/share/smf_include.sh
# The real daemon is not started in a non-global zone. Exit to leave
# an empty contract.
if smf_is_nonglobalzone; then
[ -n "$SMF_EXIT_NODAEMON" ] && exit $SMF_EXIT_NODAEMON
(while true ; do sleep 3600 ; done) &
exit $SMF_EXIT_OK
fi
# Start the dlmgmtd daemon.
/sbin/dlmgmtd
if [ $? = 0 ]; then
exit $SMF_EXIT_OK
else
exit $SMF_EXIT_ERR_FATAL
fi