# # 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. /* * 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