dlmgmt_db.c revision 2b24ab6b3865caeede9eeb9db6b83e1d89dcd1ea
/*
* 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
* 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.
*/
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <zone.h>
#include <stropts.h>
#include <pthread.h>
#include <unistd.h>
#include <wait.h>
#include <libcontract.h>
#include "dlmgmt_impl.h"
typedef enum dlmgmt_db_op {
typedef struct dlmgmt_db_req_s {
struct dlmgmt_db_req_s *ls_next;
char ls_link[MAXLINKNAMELEN];
/* DLMGMT_PERSIST, not both. */
/*
* List of pending db updates (e.g., because of a read-only filesystem).
*/
/*
* 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 void *dlmgmt_db_update_thread(void *);
#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.
*/
/*
* 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;
} translator_t;
/*
* Translator functions, defined later but declared here so that
* the translator table can be defined.
*/
/*
* Translator table, indexed by dladm_datatype_t.
*/
static translator_t translators[] = {
};
#define LINK_PROPERTY_DELIMINATOR ";"
#define LINK_PROPERTY_TYPE_VALUE_SEP ","
strlen((n)))
/*
* Name of the cache file to keep the active <link name, linkid> mapping
*/
char cachefile[MAXPATHLEN];
#define DLMGMT_PERSISTENT_DB_PATH "/etc/dladm/datalink.conf"
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;
typedef union zfoparg {
} zfoparg_t;
typedef struct zfcbarg {
const char *zfarg_filename;
} zfarg_t;
/* zone file callback */
/*
* 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
{
int ctfd;
int err;
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;
if (ctfd == -1)
return (errno);
return (err);
}
(void) ct_tmpl_clear(ctfd);
switch (childpid) {
case -1:
return (err);
case 0:
/*
* Elevate our privileges as zone_enter() requires all
* privileges.
*/
if ((err = dlmgmt_elevate_privileges()) != 0)
if ((err = dlmgmt_drop_privileges()) != 0)
break;
default:
return (errno);
}
}
if (!zfarg.zfarg_inglobalzone)
return (err);
}
static int
{
int oflags;
int fd = -1;
int err;
/* We only ever open a file for reading or writing, not both. */
/* 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.
*/
else
return (errno);
}
}
return (errno);
if (newfile) {
return (err);
}
}
}
/*
* If we're not in the global zone, send the file-descriptor back to
* our parent in the global zone.
*/
if (!inglobalzone) {
}
/*
* 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) {
return (errno);
}
return (0);
}
static int
{
return (0);
}
static int
{
return (0);
}
/*
* Same as fopen(3C), except that it opens the file relative to zoneid's zone
* root.
*/
static FILE *
int *err)
{
int p[2];
return (NULL);
}
if (zoneid != GLOBAL_ZONEID) {
(void) close(p[0]);
(void) close(p[1]);
}
if (*err == 0) {
}
}
return (fp);
}
/*
* Same as rename(2), except that old and new are relative to zoneid's zone
* root.
*/
static int
{
}
/*
* Same as unlink(2), except that filename is relative to zoneid's zone root.
*/
static int
{
}
static size_t
{
/*
* 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.
*/
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.
*/
}
static size_t
{
/*
* Booleans are either zero or one, so we only need room for two
* characters in the buffer.
*/
return (0);
}
static size_t
{
/*
* Limit checking for uint64_t is a little trickier.
*/
return (0);
}
static size_t
{
>= MAXLINKATTRVALLEN) {
return (0);
}
/* Account for NULL terminator */
return (len + 1);
}
static size_t
{
return (0);
return (sizeof (boolean_t));
}
static size_t
{
return (0);
return (sizeof (int64_t));
}
static dlmgmt_db_req_t *
{
} else {
}
return (req);
}
/*
* Update the db entry with name "entryname" using information from "linkp".
*/
static int
{
int err;
/* It is either a persistent request or an active request, not both. */
return (err);
/*
* If the return error is EINPROGRESS, this request is handled
* asynchronously; return success.
*/
if (err != EINPROGRESS)
else
err = 0;
return (err);
}
#define DLMGMT_DB_OP_STR(op) \
#define DLMGMT_DB_CONF_STR(flag) \
static int
{
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.
*/
(dlmgmt_db_req_head != NULL)) {
return (EINPROGRESS);
}
/*
* Log the error unless the request processing is still in
* progress or if the configuration file hasn't been created
* yet (ENOENT).
*/
"operation on %s configuration failed: %s",
}
if (err == EINPROGRESS) {
if (err == 0)
return (EINPROGRESS);
}
return (err);
}
static int
{
int err = 0;
char file[MAXPATHLEN];
char newfile[MAXPATHLEN];
/*
* 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.
*/
return (err);
if (writeop) {
/*
* 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.
*/
goto done;
}
}
if (writeop) {
} else {
}
done:
if (err != 0)
}
return (err);
}
/*ARGSUSED*/
static void *
dlmgmt_db_update_thread(void *arg)
{
/*
* The filesystem is still read only. Go to sleep and
* try again.
*/
(void) sleep(5);
continue;
}
/*
* The filesystem is no longer read only. Continue processing
* and remove the request from the pending list.
*/
if (dlmgmt_db_req_tail == req) {
}
}
return (NULL);
}
static int
{
int i, len;
int err = 0;
char *curr;
char attr_name[MAXLINKATTRLEN];
size_t attr_buf_len = 0;
attr_name[0] = '\0';
char c = buf[i];
/*
* Move to the next character if there is no match and
* if we have not reached the last character.
*/
continue;
if (match) {
/*
* NUL-terminate the string pointed to by 'curr'.
*/
buf[i] = '\0';
if (*curr == '\0')
goto parse_fail;
}
/*
* We get here after we have processed the "<prop>="
* pattern. The pattern we are now interested in is
* "<val>;".
*/
if (c == '=')
goto parse_fail;
} else {
&attr_buf);
}
attr_name[0] = '\0';
} 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 "<type>,<val>;", we
* want the <type> part of the pattern.
*/
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;
}
}
return (err);
return (-1);
}
static boolean_t
{
char tmpbuf[MAXLINELEN];
/*
* Use a copy of buf for parsing so that we can do whatever we want.
*/
/*
* Skip leading spaces, blank lines, and comments.
*/
for (i = 0; i < len; i++) {
break;
}
return (B_TRUE);
/*
* Find the link name and assign it to the link structure.
*/
goto fail;
/*
* 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=<linkname>". 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.
*/
} else {
goto fail;
}
goto fail;
/*
* Now find the list of link properties.
*/
goto fail;
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
{
char valcp[MAXLINKATTRVALLEN];
return (errno);
}
return (0);
}
return (0);
/* <linkname>:[<linkname>:]... */
return (errno);
} else {
}
}
if (*renamed) {
} else {
}
return (0);
}
static int
{
int err = 0;
char buf[MAXLINELEN];
/*
* find the link in the avl tree with the given linkid.
*/
/*
* 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.
*/
}
/*
* fp can be NULL if the file didn't initially exist and we're
* creating it as part of this write operation.
*/
goto write;
/*
* this is a comment line or we are done updating the
* line for the specified link, write the rest of
* lines out.
*/
continue;
}
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) {
}
link_in_file.ll_link) == 0) {
if (err != 0)
break;
if (attr_renamed) {
buf);
}
}
break;
case DLMGMT_DB_OP_DELETE:
/*
* Delete is simple. If buf does not represent the
* link we're deleting, write it out.
*/
} else {
}
break;
case DLMGMT_DB_OP_READ:
default:
break;
}
}
/*
* If we get to the end of the file and have not seen what linkid
* points to, write it out then.
*/
}
return (err);
}
static int
{
char buf[MAXLINELEN];
int err = 0;
/*
* This loop processes each line of the configuration file.
*/
break;
}
/*
* Skip the comment line.
*/
continue;
continue;
&name_where);
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.
*/
"in the repository: %s",
} else {
/*
* Save the newly read properties into
* the existing link.
*/
}
}
} else {
/*
* This is a new link. Allocate a new dlmgmt_link_t
* and add it to the trees.
*/
"memory to create new link %s",
continue;
}
NULL) {
continue;
}
link_activate(newlink) != 0) {
continue;
}
}
}
return (err);
}
/*
* Generate an entry in the link database.
* Each entry has this format:
* <link name> <prop0>=<type>,<val>;...;<propn>=<type>,<val>;
*/
static void
{
char tmpbuf[MAXLINELEN];
if (!persist) {
/*
* We store the linkid in the active database so that dlmgmtd
* can recover in the event that it is restarted.
*/
}
/*
* The daemon does not keep any active link attribute. Only store the
* attributes if this request is for persistent configuration,
*/
if (persist) {
}
}
}
int
{
flags));
}
int
{
int err;
if (flags & DLMGMT_PERSIST) {
linkp, DLMGMT_PERSIST)) != 0) {
return (err);
}
}
if (flags & DLMGMT_ACTIVE) {
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 ':'.4
*/
static void
{
char *portidstr;
char *new_attr_val;
return;
return;
}
/*
* The old format for "portnames" was
* "<linkid>.[<linkid>.]...". The new format is
* "<linkname>:[<linkname>:]...".
*/
return;
if (new_attr_val == NULL)
return;
while (*portidstr != '\0') {
errno = 0;
NULL) {
return;
}
return;
}
/* skip the '.' delimiter */
portidstr++;
}
}
if (upgraded) {
}
}
static void
{
}
static void
{
}
static void
{
}
}
/*
* Initialize the datalink <link name, linkid> mapping and the link's
* attributes list based on the configuration file /etc/dladm/datalink.conf
* and the active configuration cache file
*/
int
{
int err;
return (err);
/*
* 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.
*/
goto done;
}
goto done;
err = 0;
if (rewrite_needed) {
/*
* First update links in memory, then dump the entire db to
* disk.
*/
err != EINPROGRESS)
goto done;
}
if (boot) {
}
done:
if (err == EINPROGRESS)
err = 0;
else
return (err);
}
/*
* Remove all links in the given zoneid.
*/
void
{
(void) dlmgmt_destroy_common(linkp,
}
linkp = next_linkp;
}
}