8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / dlmgmtd / dlmgmt_db.c
blob99307dbc032476e99c97e760161da1a7cb37b754
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
23 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
26 #include <assert.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 #include <syslog.h>
35 #include <zone.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <stropts.h>
39 #include <sys/conf.h>
40 #include <pthread.h>
41 #include <unistd.h>
42 #include <wait.h>
43 #include <libcontract.h>
44 #include <libcontract_priv.h>
45 #include <sys/contract/process.h>
46 #include "dlmgmt_impl.h"
48 typedef enum dlmgmt_db_op {
49 DLMGMT_DB_OP_WRITE,
50 DLMGMT_DB_OP_DELETE,
51 DLMGMT_DB_OP_READ
52 } dlmgmt_db_op_t;
54 typedef struct dlmgmt_db_req_s {
55 struct dlmgmt_db_req_s *ls_next;
56 dlmgmt_db_op_t ls_op;
57 char ls_link[MAXLINKNAMELEN];
58 datalink_id_t ls_linkid;
59 zoneid_t ls_zoneid;
60 uint32_t ls_flags; /* Either DLMGMT_ACTIVE or */
61 /* DLMGMT_PERSIST, not both. */
62 } dlmgmt_db_req_t;
65 * List of pending db updates (e.g., because of a read-only filesystem).
67 static dlmgmt_db_req_t *dlmgmt_db_req_head = NULL;
68 static dlmgmt_db_req_t *dlmgmt_db_req_tail = NULL;
71 * rewrite_needed is set to B_TRUE by process_link_line() if it encounters a
72 * line with an old format. This will cause the file being read to be
73 * re-written with the current format.
75 static boolean_t rewrite_needed;
77 static int dlmgmt_db_update(dlmgmt_db_op_t, const char *,
78 dlmgmt_link_t *, uint32_t);
79 static int dlmgmt_process_db_req(dlmgmt_db_req_t *);
80 static int dlmgmt_process_db_onereq(dlmgmt_db_req_t *, boolean_t);
81 static void *dlmgmt_db_update_thread(void *);
82 static boolean_t process_link_line(char *, dlmgmt_link_t *);
83 static int process_db_write(dlmgmt_db_req_t *, FILE *, FILE *);
84 static int process_db_read(dlmgmt_db_req_t *, FILE *);
85 static void generate_link_line(dlmgmt_link_t *, boolean_t, char *);
87 #define BUFLEN(lim, ptr) (((lim) > (ptr)) ? ((lim) - (ptr)) : 0)
88 #define MAXLINELEN 1024
90 typedef void db_walk_func_t(dlmgmt_link_t *);
93 * Translator functions to go from dladm_datatype_t to character strings.
94 * Each function takes a pointer to a buffer, the size of the buffer,
95 * the name of the attribute, and the value to be written. The functions
96 * return the number of bytes written to the buffer. If the buffer is not big
97 * enough to hold the string representing the value, then nothing is written
98 * and 0 is returned.
100 typedef size_t write_func_t(char *, size_t, char *, void *);
103 * Translator functions to read from a NULL terminated string buffer into
104 * something of the given DLADM_TYPE_*. The functions each return the number
105 * of bytes read from the string buffer. If there is an error reading data
106 * from the buffer, then 0 is returned. It is the caller's responsibility
107 * to free the data allocated by these functions.
109 typedef size_t read_func_t(char *, void **);
111 typedef struct translator_s {
112 const char *type_name;
113 write_func_t *write_func;
114 read_func_t *read_func;
115 } translator_t;
118 * Translator functions, defined later but declared here so that
119 * the translator table can be defined.
121 static write_func_t write_str, write_boolean, write_uint64;
122 static read_func_t read_str, read_boolean, read_int64;
125 * Translator table, indexed by dladm_datatype_t.
127 static translator_t translators[] = {
128 { "string", write_str, read_str },
129 { "boolean", write_boolean, read_boolean },
130 { "int", write_uint64, read_int64 }
133 static size_t ntranslators = sizeof (translators) / sizeof (translator_t);
135 #define LINK_PROPERTY_DELIMINATOR ";"
136 #define LINK_PROPERTY_TYPE_VALUE_SEP ","
137 #define BASE_PROPERTY_LENGTH(t, n) (strlen(translators[(t)].type_name) +\
138 strlen(LINK_PROPERTY_TYPE_VALUE_SEP) +\
139 strlen(LINK_PROPERTY_DELIMINATOR) +\
140 strlen((n)))
141 #define GENERATE_PROPERTY_STRING(buf, length, conv, name, type, val) \
142 (snprintf((buf), (length), "%s=%s%s" conv "%s", (name), \
143 translators[(type)].type_name, \
144 LINK_PROPERTY_TYPE_VALUE_SEP, (val), LINK_PROPERTY_DELIMINATOR))
147 * Name of the cache file to keep the active <link name, linkid> mapping
149 char cachefile[MAXPATHLEN];
151 #define DLMGMT_PERSISTENT_DB_PATH "/etc/dladm/datalink.conf"
152 #define DLMGMT_MAKE_FILE_DB_PATH(buffer, persistent) \
153 (void) snprintf((buffer), MAXPATHLEN, "%s", \
154 (persistent) ? DLMGMT_PERSISTENT_DB_PATH : cachefile);
156 typedef struct zopen_arg {
157 const char *zopen_modestr;
158 int *zopen_pipe;
159 int zopen_fd;
160 } zopen_arg_t;
162 typedef struct zrename_arg {
163 const char *zrename_newname;
164 } zrename_arg_t;
166 typedef union zfoparg {
167 zopen_arg_t zfop_openarg;
168 zrename_arg_t zfop_renamearg;
169 } zfoparg_t;
171 typedef struct zfcbarg {
172 boolean_t zfarg_inglobalzone; /* is callback in global zone? */
173 zoneid_t zfarg_finglobalzone; /* is file in global zone? */
174 const char *zfarg_filename;
175 zfoparg_t *zfarg_oparg;
176 } zfarg_t;
177 #define zfarg_openarg zfarg_oparg->zfop_openarg
178 #define zfarg_renamearg zfarg_oparg->zfop_renamearg
180 /* zone file callback */
181 typedef int zfcb_t(zfarg_t *);
184 * Execute an operation on filename relative to zoneid's zone root. If the
185 * file is in the global zone, then the zfcb() callback will simply be called
186 * directly. If the file is in a non-global zone, then zfcb() will be called
187 * both from the global zone's context, and from the non-global zone's context
188 * (from a fork()'ed child that has entered the non-global zone). This is
189 * done to allow the callback to communicate with itself if needed (e.g. to
190 * pass back the file descriptor of an opened file).
192 static int
193 dlmgmt_zfop(const char *filename, zoneid_t zoneid, zfcb_t *zfcb,
194 zfoparg_t *zfoparg)
196 int ctfd;
197 int err;
198 pid_t childpid;
199 siginfo_t info;
200 zfarg_t zfarg;
201 ctid_t ct;
203 if (zoneid != GLOBAL_ZONEID) {
205 * We need to access a file that isn't in the global zone.
206 * Accessing non-global zone files from the global zone is
207 * unsafe (due to symlink attacks), we'll need to fork a child
208 * that enters the zone in question and executes the callback
209 * that will operate on the file.
211 * Before we proceed with this zone tango, we need to create a
212 * new process contract for the child, as required by
213 * zone_enter().
215 errno = 0;
216 ctfd = open64("/system/contract/process/template", O_RDWR);
217 if (ctfd == -1)
218 return (errno);
219 if ((err = ct_tmpl_set_critical(ctfd, 0)) != 0 ||
220 (err = ct_tmpl_set_informative(ctfd, 0)) != 0 ||
221 (err = ct_pr_tmpl_set_fatal(ctfd, CT_PR_EV_HWERR)) != 0 ||
222 (err = ct_pr_tmpl_set_param(ctfd, CT_PR_PGRPONLY)) != 0 ||
223 (err = ct_tmpl_activate(ctfd)) != 0) {
224 (void) close(ctfd);
225 return (err);
227 childpid = fork();
228 switch (childpid) {
229 case -1:
230 (void) ct_tmpl_clear(ctfd);
231 (void) close(ctfd);
232 return (err);
233 case 0:
234 (void) ct_tmpl_clear(ctfd);
235 (void) close(ctfd);
237 * Elevate our privileges as zone_enter() requires all
238 * privileges.
240 if ((err = dlmgmt_elevate_privileges()) != 0)
241 _exit(err);
242 if (zone_enter(zoneid) == -1)
243 _exit(errno);
244 if ((err = dlmgmt_drop_privileges()) != 0)
245 _exit(err);
246 break;
247 default:
248 if (contract_latest(&ct) == -1)
249 ct = -1;
250 (void) ct_tmpl_clear(ctfd);
251 (void) close(ctfd);
252 if (waitid(P_PID, childpid, &info, WEXITED) == -1) {
253 (void) contract_abandon_id(ct);
254 return (errno);
256 (void) contract_abandon_id(ct);
257 if (info.si_status != 0)
258 return (info.si_status);
262 zfarg.zfarg_inglobalzone = (zoneid == GLOBAL_ZONEID || childpid != 0);
263 zfarg.zfarg_finglobalzone = (zoneid == GLOBAL_ZONEID);
264 zfarg.zfarg_filename = filename;
265 zfarg.zfarg_oparg = zfoparg;
266 err = zfcb(&zfarg);
267 if (!zfarg.zfarg_inglobalzone)
268 _exit(err);
269 return (err);
272 static int
273 dlmgmt_zopen_cb(zfarg_t *zfarg)
275 struct strrecvfd recvfd;
276 boolean_t newfile = B_FALSE;
277 boolean_t inglobalzone = zfarg->zfarg_inglobalzone;
278 zoneid_t finglobalzone = zfarg->zfarg_finglobalzone;
279 const char *filename = zfarg->zfarg_filename;
280 const char *modestr = zfarg->zfarg_openarg.zopen_modestr;
281 int *p = zfarg->zfarg_openarg.zopen_pipe;
282 struct stat statbuf;
283 int oflags;
284 mode_t mode;
285 int fd = -1;
286 int err;
288 /* We only ever open a file for reading or writing, not both. */
289 oflags = (modestr[0] == 'r') ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC;
290 mode = (modestr[0] == 'r') ? 0 : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
292 /* Open the file if we're in the same zone as the file. */
293 if (inglobalzone == finglobalzone) {
295 * First determine if we will be creating the file as part of
296 * opening it. If so, then we'll need to ensure that it has
297 * the proper ownership after having opened it.
299 if (oflags & O_CREAT) {
300 if (stat(filename, &statbuf) == -1) {
301 if (errno == ENOENT)
302 newfile = B_TRUE;
303 else
304 return (errno);
307 if ((fd = open(filename, oflags, mode)) == -1)
308 return (errno);
309 if (newfile) {
310 if (chown(filename, UID_DLADM, GID_NETADM) == -1) {
311 err = errno;
312 (void) close(fd);
313 return (err);
319 * If we're not in the global zone, send the file-descriptor back to
320 * our parent in the global zone.
322 if (!inglobalzone) {
323 assert(!finglobalzone);
324 assert(fd != -1);
325 return (ioctl(p[1], I_SENDFD, fd) == -1 ? errno : 0);
329 * At this point, we know we're in the global zone. If the file was
330 * in a non-global zone, receive the file-descriptor from our child in
331 * the non-global zone.
333 if (!finglobalzone) {
334 if (ioctl(p[0], I_RECVFD, &recvfd) == -1)
335 return (errno);
336 fd = recvfd.fd;
339 zfarg->zfarg_openarg.zopen_fd = fd;
340 return (0);
343 static int
344 dlmgmt_zunlink_cb(zfarg_t *zfarg)
346 if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
347 return (0);
348 return (unlink(zfarg->zfarg_filename) == 0 ? 0 : errno);
351 static int
352 dlmgmt_zrename_cb(zfarg_t *zfarg)
354 if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
355 return (0);
356 return (rename(zfarg->zfarg_filename,
357 zfarg->zfarg_renamearg.zrename_newname) == 0 ? 0 : errno);
361 * Same as fopen(3C), except that it opens the file relative to zoneid's zone
362 * root.
364 static FILE *
365 dlmgmt_zfopen(const char *filename, const char *modestr, zoneid_t zoneid,
366 int *err)
368 int p[2];
369 zfoparg_t zfoparg;
370 FILE *fp = NULL;
372 if (zoneid != GLOBAL_ZONEID && pipe(p) == -1) {
373 *err = errno;
374 return (NULL);
377 zfoparg.zfop_openarg.zopen_modestr = modestr;
378 zfoparg.zfop_openarg.zopen_pipe = p;
379 *err = dlmgmt_zfop(filename, zoneid, dlmgmt_zopen_cb, &zfoparg);
380 if (zoneid != GLOBAL_ZONEID) {
381 (void) close(p[0]);
382 (void) close(p[1]);
384 if (*err == 0) {
385 fp = fdopen(zfoparg.zfop_openarg.zopen_fd, modestr);
386 if (fp == NULL) {
387 *err = errno;
388 (void) close(zfoparg.zfop_openarg.zopen_fd);
391 return (fp);
395 * Same as rename(2), except that old and new are relative to zoneid's zone
396 * root.
398 static int
399 dlmgmt_zrename(const char *old, const char *new, zoneid_t zoneid)
401 zfoparg_t zfoparg;
403 zfoparg.zfop_renamearg.zrename_newname = new;
404 return (dlmgmt_zfop(old, zoneid, dlmgmt_zrename_cb, &zfoparg));
408 * Same as unlink(2), except that filename is relative to zoneid's zone root.
410 static int
411 dlmgmt_zunlink(const char *filename, zoneid_t zoneid)
413 return (dlmgmt_zfop(filename, zoneid, dlmgmt_zunlink_cb, NULL));
416 static size_t
417 write_str(char *buffer, size_t buffer_length, char *name, void *value)
419 char *ptr = value;
420 size_t data_length = strnlen(ptr, buffer_length);
423 * Strings are assumed to be NULL terminated. In order to fit in
424 * the buffer, the string's length must be less then buffer_length.
425 * If the value is empty, there's no point in writing it, in fact,
426 * we shouldn't even see that case.
428 if (data_length + BASE_PROPERTY_LENGTH(DLADM_TYPE_STR, name) ==
429 buffer_length || data_length == 0)
430 return (0);
433 * Since we know the string will fit in the buffer, snprintf will
434 * always return less than buffer_length, so we can just return
435 * whatever snprintf returns.
437 return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%s",
438 name, DLADM_TYPE_STR, ptr));
441 static size_t
442 write_boolean(char *buffer, size_t buffer_length, char *name, void *value)
444 boolean_t *ptr = value;
447 * Booleans are either zero or one, so we only need room for two
448 * characters in the buffer.
450 if (buffer_length <= 1 + BASE_PROPERTY_LENGTH(DLADM_TYPE_BOOLEAN, name))
451 return (0);
453 return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%d",
454 name, DLADM_TYPE_BOOLEAN, *ptr));
457 static size_t
458 write_uint64(char *buffer, size_t buffer_length, char *name, void *value)
460 uint64_t *ptr = value;
463 * Limit checking for uint64_t is a little trickier.
465 if (snprintf(NULL, 0, "%lld", *ptr) +
466 BASE_PROPERTY_LENGTH(DLADM_TYPE_UINT64, name) >= buffer_length)
467 return (0);
469 return (GENERATE_PROPERTY_STRING(buffer, buffer_length, "%lld",
470 name, DLADM_TYPE_UINT64, *ptr));
473 static size_t
474 read_str(char *buffer, void **value)
476 char *ptr = calloc(MAXLINKATTRVALLEN, sizeof (char));
477 ssize_t len;
479 if (ptr == NULL || (len = strlcpy(ptr, buffer, MAXLINKATTRVALLEN))
480 >= MAXLINKATTRVALLEN) {
481 free(ptr);
482 return (0);
485 *(char **)value = ptr;
487 /* Account for NULL terminator */
488 return (len + 1);
491 static size_t
492 read_boolean(char *buffer, void **value)
494 boolean_t *ptr = calloc(1, sizeof (boolean_t));
496 if (ptr == NULL)
497 return (0);
499 *ptr = atoi(buffer);
500 *(boolean_t **)value = ptr;
502 return (sizeof (boolean_t));
505 static size_t
506 read_int64(char *buffer, void **value)
508 int64_t *ptr = calloc(1, sizeof (int64_t));
510 if (ptr == NULL)
511 return (0);
513 *ptr = (int64_t)atoll(buffer);
514 *(int64_t **)value = ptr;
516 return (sizeof (int64_t));
519 static dlmgmt_db_req_t *
520 dlmgmt_db_req_alloc(dlmgmt_db_op_t op, const char *linkname,
521 datalink_id_t linkid, zoneid_t zoneid, uint32_t flags, int *err)
523 dlmgmt_db_req_t *req;
525 if ((req = calloc(1, sizeof (dlmgmt_db_req_t))) == NULL) {
526 *err = errno;
527 } else {
528 req->ls_op = op;
529 if (linkname != NULL)
530 (void) strlcpy(req->ls_link, linkname, MAXLINKNAMELEN);
531 req->ls_linkid = linkid;
532 req->ls_zoneid = zoneid;
533 req->ls_flags = flags;
535 return (req);
539 * Update the db entry with name "entryname" using information from "linkp".
541 static int
542 dlmgmt_db_update(dlmgmt_db_op_t op, const char *entryname, dlmgmt_link_t *linkp,
543 uint32_t flags)
545 dlmgmt_db_req_t *req;
546 int err;
548 /* It is either a persistent request or an active request, not both. */
549 assert((flags == DLMGMT_PERSIST) || (flags == DLMGMT_ACTIVE));
551 if ((req = dlmgmt_db_req_alloc(op, entryname, linkp->ll_linkid,
552 linkp->ll_zoneid, flags, &err)) == NULL)
553 return (err);
556 * If the return error is EINPROGRESS, this request is handled
557 * asynchronously; return success.
559 err = dlmgmt_process_db_req(req);
560 if (err != EINPROGRESS)
561 free(req);
562 else
563 err = 0;
564 return (err);
567 #define DLMGMT_DB_OP_STR(op) \
568 (((op) == DLMGMT_DB_OP_READ) ? "read" : \
569 (((op) == DLMGMT_DB_OP_WRITE) ? "write" : "delete"))
571 #define DLMGMT_DB_CONF_STR(flag) \
572 (((flag) == DLMGMT_ACTIVE) ? "active" : \
573 (((flag) == DLMGMT_PERSIST) ? "persistent" : ""))
575 static int
576 dlmgmt_process_db_req(dlmgmt_db_req_t *req)
578 pthread_t tid;
579 boolean_t writeop;
580 int err;
583 * If there are already pending "write" requests, queue this request in
584 * the pending list. Note that this function is called while the
585 * dlmgmt_rw_lock is held, so it is safe to access the global variables.
587 writeop = (req->ls_op != DLMGMT_DB_OP_READ);
588 if (writeop && (req->ls_flags == DLMGMT_PERSIST) &&
589 (dlmgmt_db_req_head != NULL)) {
590 dlmgmt_db_req_tail->ls_next = req;
591 dlmgmt_db_req_tail = req;
592 return (EINPROGRESS);
595 err = dlmgmt_process_db_onereq(req, writeop);
596 if (err != EINPROGRESS && err != 0 && err != ENOENT) {
598 * Log the error unless the request processing is still in
599 * progress or if the configuration file hasn't been created
600 * yet (ENOENT).
602 dlmgmt_log(LOG_WARNING, "dlmgmt_process_db_onereq() %s "
603 "operation on %s configuration failed: %s",
604 DLMGMT_DB_OP_STR(req->ls_op),
605 DLMGMT_DB_CONF_STR(req->ls_flags), strerror(err));
608 if (err == EINPROGRESS) {
609 assert(req->ls_flags == DLMGMT_PERSIST);
610 assert(writeop && dlmgmt_db_req_head == NULL);
611 dlmgmt_db_req_tail = dlmgmt_db_req_head = req;
612 err = pthread_create(&tid, NULL, dlmgmt_db_update_thread, NULL);
613 if (err == 0)
614 return (EINPROGRESS);
616 return (err);
619 static int
620 dlmgmt_process_db_onereq(dlmgmt_db_req_t *req, boolean_t writeop)
622 int err = 0;
623 FILE *fp, *nfp = NULL;
624 char file[MAXPATHLEN];
625 char newfile[MAXPATHLEN];
627 DLMGMT_MAKE_FILE_DB_PATH(file, (req->ls_flags == DLMGMT_PERSIST));
628 fp = dlmgmt_zfopen(file, "r", req->ls_zoneid, &err);
630 * Note that it is not an error if the file doesn't exist. If we're
631 * reading, we treat this case the same way as an empty file. If
632 * we're writing, the file will be created when we open the file for
633 * writing below.
635 if (fp == NULL && !writeop)
636 return (err);
638 if (writeop) {
639 (void) snprintf(newfile, MAXPATHLEN, "%s.new", file);
640 nfp = dlmgmt_zfopen(newfile, "w", req->ls_zoneid, &err);
641 if (nfp == NULL) {
643 * EROFS can happen at boot when the file system is
644 * read-only. Return EINPROGRESS so that the caller
645 * can add this request to the pending request list
646 * and start a retry thread.
648 err = (errno == EROFS ? EINPROGRESS : errno);
649 goto done;
652 if (writeop) {
653 if ((err = process_db_write(req, fp, nfp)) == 0)
654 err = dlmgmt_zrename(newfile, file, req->ls_zoneid);
655 } else {
656 err = process_db_read(req, fp);
659 done:
660 if (nfp != NULL) {
661 (void) fclose(nfp);
662 if (err != 0)
663 (void) dlmgmt_zunlink(newfile, req->ls_zoneid);
665 (void) fclose(fp);
666 return (err);
669 /*ARGSUSED*/
670 static void *
671 dlmgmt_db_update_thread(void *arg)
673 dlmgmt_db_req_t *req;
675 dlmgmt_table_lock(B_TRUE);
677 assert(dlmgmt_db_req_head != NULL);
678 while ((req = dlmgmt_db_req_head) != NULL) {
679 assert(req->ls_flags == DLMGMT_PERSIST);
680 if (dlmgmt_process_db_onereq(req, B_TRUE) == EINPROGRESS) {
682 * The filesystem is still read only. Go to sleep and
683 * try again.
685 dlmgmt_table_unlock();
686 (void) sleep(5);
687 dlmgmt_table_lock(B_TRUE);
688 continue;
692 * The filesystem is no longer read only. Continue processing
693 * and remove the request from the pending list.
695 dlmgmt_db_req_head = req->ls_next;
696 if (dlmgmt_db_req_tail == req) {
697 assert(dlmgmt_db_req_head == NULL);
698 dlmgmt_db_req_tail = NULL;
700 free(req);
703 dlmgmt_table_unlock();
704 return (NULL);
707 static int
708 parse_linkprops(char *buf, dlmgmt_link_t *linkp)
710 boolean_t found_type = B_FALSE;
711 dladm_datatype_t type = DLADM_TYPE_STR;
712 int i, len;
713 char *curr;
714 char attr_name[MAXLINKATTRLEN];
715 size_t attr_buf_len = 0;
716 void *attr_buf = NULL;
718 curr = buf;
719 len = strlen(buf);
720 attr_name[0] = '\0';
721 for (i = 0; i < len; i++) {
722 char c = buf[i];
723 boolean_t match = (c == '=' ||
724 (c == ',' && !found_type) || c == ';');
727 * Move to the next character if there is no match and
728 * if we have not reached the last character.
730 if (!match && i != len - 1)
731 continue;
733 if (match) {
735 * NUL-terminate the string pointed to by 'curr'.
737 buf[i] = '\0';
738 if (*curr == '\0')
739 goto parse_fail;
742 if (attr_name[0] != '\0' && found_type) {
744 * We get here after we have processed the "<prop>="
745 * pattern. The pattern we are now interested in is
746 * "<val>;".
748 if (c == '=')
749 goto parse_fail;
751 if (strcmp(attr_name, "linkid") == 0) {
752 if (read_int64(curr, &attr_buf) == 0)
753 goto parse_fail;
754 linkp->ll_linkid =
755 (datalink_class_t)*(int64_t *)attr_buf;
756 } else if (strcmp(attr_name, "name") == 0) {
757 if (read_str(curr, &attr_buf) == 0)
758 goto parse_fail;
759 (void) snprintf(linkp->ll_link,
760 MAXLINKNAMELEN, "%s", attr_buf);
761 } else if (strcmp(attr_name, "class") == 0) {
762 if (read_int64(curr, &attr_buf) == 0)
763 goto parse_fail;
764 linkp->ll_class =
765 (datalink_class_t)*(int64_t *)attr_buf;
766 } else if (strcmp(attr_name, "media") == 0) {
767 if (read_int64(curr, &attr_buf) == 0)
768 goto parse_fail;
769 linkp->ll_media =
770 (uint32_t)*(int64_t *)attr_buf;
771 } else {
772 attr_buf_len = translators[type].read_func(curr,
773 &attr_buf);
774 if (attr_buf_len == 0)
775 goto parse_fail;
777 if (linkattr_set(&(linkp->ll_head), attr_name,
778 attr_buf, attr_buf_len, type) != 0) {
779 free(attr_buf);
780 goto parse_fail;
784 free(attr_buf);
785 attr_name[0] = '\0';
786 found_type = B_FALSE;
787 } else if (attr_name[0] != '\0') {
789 * Non-zero length attr_name and found_type of false
790 * indicates that we have not found the type for this
791 * attribute. The pattern now is "<type>,<val>;", we
792 * want the <type> part of the pattern.
794 for (type = 0; type < ntranslators; type++) {
795 if (strcmp(curr,
796 translators[type].type_name) == 0) {
797 found_type = B_TRUE;
798 break;
802 if (!found_type)
803 goto parse_fail;
804 } else {
806 * A zero length attr_name indicates we are looking
807 * at the beginning of a link attribute.
809 if (c != '=')
810 goto parse_fail;
812 (void) snprintf(attr_name, MAXLINKATTRLEN, "%s", curr);
814 curr = buf + i + 1;
817 /* Correct any erroneous IPTUN datalink class constant in the file */
818 if (linkp->ll_class == 0x60) {
819 linkp->ll_class = DATALINK_CLASS_IPTUN;
820 rewrite_needed = B_TRUE;
823 return (0);
825 parse_fail:
827 * Free linkp->ll_head (link attribute list)
829 linkattr_destroy(linkp);
830 return (-1);
833 static boolean_t
834 process_link_line(char *buf, dlmgmt_link_t *linkp)
836 int i, len, llen;
837 char *str, *lasts;
838 char tmpbuf[MAXLINELEN];
840 bzero(linkp, sizeof (*linkp));
841 linkp->ll_linkid = DATALINK_INVALID_LINKID;
844 * Use a copy of buf for parsing so that we can do whatever we want.
846 (void) strlcpy(tmpbuf, buf, MAXLINELEN);
849 * Skip leading spaces, blank lines, and comments.
851 len = strlen(tmpbuf);
852 for (i = 0; i < len; i++) {
853 if (!isspace(tmpbuf[i]))
854 break;
856 if (i == len || tmpbuf[i] == '#')
857 return (B_TRUE);
859 str = tmpbuf + i;
861 * Find the link name and assign it to the link structure.
863 if (strtok_r(str, " \n\t", &lasts) == NULL)
864 goto fail;
866 llen = strlen(str);
868 * Note that a previous version of the persistent datalink.conf file
869 * stored the linkid as the first field. In that case, the name will
870 * be obtained through parse_linkprops from a property with the format
871 * "name=<linkname>". If we encounter such a format, we set
872 * rewrite_needed so that dlmgmt_db_init() can rewrite the file with
873 * the new format after it's done reading in the data.
875 if (isdigit(str[0])) {
876 linkp->ll_linkid = atoi(str);
877 rewrite_needed = B_TRUE;
878 } else {
879 if (strlcpy(linkp->ll_link, str, sizeof (linkp->ll_link)) >=
880 sizeof (linkp->ll_link))
881 goto fail;
884 str += llen + 1;
885 if (str >= tmpbuf + len)
886 goto fail;
889 * Now find the list of link properties.
891 if ((str = strtok_r(str, " \n\t", &lasts)) == NULL)
892 goto fail;
894 if (parse_linkprops(str, linkp) < 0)
895 goto fail;
897 return (B_TRUE);
899 fail:
901 * Delete corrupted line.
903 buf[0] = '\0';
904 return (B_FALSE);
908 * Find any properties in linkp that refer to "old", and rename to "new".
909 * Return B_TRUE if any renaming occurred.
911 static int
912 dlmgmt_attr_rename(dlmgmt_link_t *linkp, const char *old, const char *new,
913 boolean_t *renamed)
915 dlmgmt_linkattr_t *attrp;
916 char *newval = NULL, *pname;
917 char valcp[MAXLINKATTRVALLEN];
918 size_t newsize;
920 *renamed = B_FALSE;
922 if ((attrp = linkattr_find(linkp->ll_head, "linkover")) != NULL ||
923 (attrp = linkattr_find(linkp->ll_head, "simnetpeer")) != NULL) {
924 if (strcmp(old, (char *)attrp->lp_val) == 0) {
925 newsize = strlen(new) + 1;
926 if ((newval = malloc(newsize)) == NULL)
927 return (errno);
928 (void) strcpy(newval, new);
929 free(attrp->lp_val);
930 attrp->lp_val = newval;
931 attrp->lp_sz = newsize;
932 *renamed = B_TRUE;
934 return (0);
937 if ((attrp = linkattr_find(linkp->ll_head, "portnames")) == NULL)
938 return (0);
940 /* <linkname>:[<linkname>:]... */
941 if ((newval = calloc(MAXLINKATTRVALLEN, sizeof (char))) == NULL)
942 return (errno);
944 bcopy(attrp->lp_val, valcp, sizeof (valcp));
945 pname = strtok(valcp, ":");
946 while (pname != NULL) {
947 if (strcmp(pname, old) == 0) {
948 (void) strcat(newval, new);
949 *renamed = B_TRUE;
950 } else {
951 (void) strcat(newval, pname);
953 (void) strcat(newval, ":");
954 pname = strtok(NULL, ":");
956 if (*renamed) {
957 free(attrp->lp_val);
958 attrp->lp_val = newval;
959 attrp->lp_sz = strlen(newval) + 1;
960 } else {
961 free(newval);
963 return (0);
966 static int
967 process_db_write(dlmgmt_db_req_t *req, FILE *fp, FILE *nfp)
969 boolean_t done = B_FALSE;
970 int err = 0;
971 dlmgmt_link_t link_in_file, *linkp = NULL, *dblinkp;
972 boolean_t persist = (req->ls_flags == DLMGMT_PERSIST);
973 boolean_t writeall, rename, attr_renamed;
974 char buf[MAXLINELEN];
976 writeall = (req->ls_linkid == DATALINK_ALL_LINKID);
978 if (req->ls_op == DLMGMT_DB_OP_WRITE && !writeall) {
980 * find the link in the avl tree with the given linkid.
982 linkp = link_by_id(req->ls_linkid, req->ls_zoneid);
983 if (linkp == NULL || (linkp->ll_flags & req->ls_flags) == 0) {
985 * This link has already been changed. This could
986 * happen if the request is pending because of
987 * read-only file-system. If so, we are done.
989 return (0);
992 * In the case of a rename, linkp's name has been updated to
993 * the new name, and req->ls_link is the old link name.
995 rename = (strcmp(req->ls_link, linkp->ll_link) != 0);
999 * fp can be NULL if the file didn't initially exist and we're
1000 * creating it as part of this write operation.
1002 if (fp == NULL)
1003 goto write;
1005 while (err == 0 && fgets(buf, sizeof (buf), fp) != NULL &&
1006 process_link_line(buf, &link_in_file)) {
1008 * Only the link name is needed. Free the memory allocated for
1009 * the link attributes list of link_in_file.
1011 linkattr_destroy(&link_in_file);
1013 if (link_in_file.ll_link[0] == '\0' || done) {
1015 * this is a comment line or we are done updating the
1016 * line for the specified link, write the rest of
1017 * lines out.
1019 if (fputs(buf, nfp) == EOF)
1020 err = errno;
1021 continue;
1024 switch (req->ls_op) {
1025 case DLMGMT_DB_OP_WRITE:
1027 * For write operations, we generate a new output line
1028 * if we're either writing all links (writeall) or if
1029 * the name of the link in the file matches the one
1030 * we're looking for. Otherwise, we write out the
1031 * buffer as-is.
1033 * If we're doing a rename operation, ensure that any
1034 * references to the link being renamed in link
1035 * properties are also updated before we write
1036 * anything.
1038 if (writeall) {
1039 linkp = link_by_name(link_in_file.ll_link,
1040 req->ls_zoneid);
1042 if (writeall || strcmp(req->ls_link,
1043 link_in_file.ll_link) == 0) {
1044 generate_link_line(linkp, persist, buf);
1045 if (!writeall && !rename)
1046 done = B_TRUE;
1047 } else if (rename && persist) {
1048 dblinkp = link_by_name(link_in_file.ll_link,
1049 req->ls_zoneid);
1050 err = dlmgmt_attr_rename(dblinkp, req->ls_link,
1051 linkp->ll_link, &attr_renamed);
1052 if (err != 0)
1053 break;
1054 if (attr_renamed) {
1055 generate_link_line(dblinkp, persist,
1056 buf);
1059 if (fputs(buf, nfp) == EOF)
1060 err = errno;
1061 break;
1062 case DLMGMT_DB_OP_DELETE:
1064 * Delete is simple. If buf does not represent the
1065 * link we're deleting, write it out.
1067 if (strcmp(req->ls_link, link_in_file.ll_link) != 0) {
1068 if (fputs(buf, nfp) == EOF)
1069 err = errno;
1070 } else {
1071 done = B_TRUE;
1073 break;
1074 case DLMGMT_DB_OP_READ:
1075 default:
1076 err = EINVAL;
1077 break;
1081 write:
1083 * If we get to the end of the file and have not seen what linkid
1084 * points to, write it out then.
1086 if (req->ls_op == DLMGMT_DB_OP_WRITE && !writeall && !rename && !done) {
1087 generate_link_line(linkp, persist, buf);
1088 done = B_TRUE;
1089 if (fputs(buf, nfp) == EOF)
1090 err = errno;
1093 return (err);
1096 static int
1097 process_db_read(dlmgmt_db_req_t *req, FILE *fp)
1099 avl_index_t name_where, id_where;
1100 dlmgmt_link_t link_in_file, *newlink, *link_in_db;
1101 char buf[MAXLINELEN];
1102 int err = 0;
1105 * This loop processes each line of the configuration file.
1107 while (fgets(buf, MAXLINELEN, fp) != NULL) {
1108 if (!process_link_line(buf, &link_in_file)) {
1109 err = EINVAL;
1110 break;
1114 * Skip the comment line.
1116 if (link_in_file.ll_link[0] == '\0') {
1117 linkattr_destroy(&link_in_file);
1118 continue;
1121 if ((req->ls_flags & DLMGMT_ACTIVE) &&
1122 link_in_file.ll_linkid == DATALINK_INVALID_LINKID) {
1123 linkattr_destroy(&link_in_file);
1124 continue;
1127 link_in_file.ll_zoneid = req->ls_zoneid;
1128 link_in_db = link_by_name(link_in_file.ll_link,
1129 link_in_file.ll_zoneid);
1130 if (link_in_db != NULL) {
1132 * If the link in the database already has the flag
1133 * for this request set, then the entry is a
1134 * duplicate. If it's not a duplicate, then simply
1135 * turn on the appropriate flag on the existing link.
1137 if (link_in_db->ll_flags & req->ls_flags) {
1138 dlmgmt_log(LOG_WARNING, "Duplicate links "
1139 "in the repository: %s",
1140 link_in_file.ll_link);
1141 linkattr_destroy(&link_in_file);
1142 } else {
1143 if (req->ls_flags & DLMGMT_PERSIST) {
1145 * Save the newly read properties into
1146 * the existing link.
1148 assert(link_in_db->ll_head == NULL);
1149 link_in_db->ll_head =
1150 link_in_file.ll_head;
1151 } else {
1152 linkattr_destroy(&link_in_file);
1154 link_in_db->ll_flags |= req->ls_flags;
1156 } else {
1158 * This is a new link. Allocate a new dlmgmt_link_t
1159 * and add it to the trees.
1161 newlink = calloc(1, sizeof (*newlink));
1162 if (newlink == NULL) {
1163 dlmgmt_log(LOG_WARNING, "Unable to allocate "
1164 "memory to create new link %s",
1165 link_in_file.ll_link);
1166 linkattr_destroy(&link_in_file);
1167 continue;
1169 bcopy(&link_in_file, newlink, sizeof (*newlink));
1171 if (newlink->ll_linkid == DATALINK_INVALID_LINKID)
1172 newlink->ll_linkid = dlmgmt_nextlinkid;
1173 if (avl_find(&dlmgmt_id_avl, newlink, &id_where) !=
1174 NULL) {
1175 dlmgmt_log(LOG_WARNING, "Link ID %d is already"
1176 " in use, destroying link %s",
1177 newlink->ll_linkid, newlink->ll_link);
1178 link_destroy(newlink);
1179 continue;
1182 if ((req->ls_flags & DLMGMT_ACTIVE) &&
1183 link_activate(newlink) != 0) {
1184 dlmgmt_log(LOG_WARNING, "Unable to activate %s",
1185 newlink->ll_link);
1186 link_destroy(newlink);
1187 continue;
1190 avl_insert(&dlmgmt_id_avl, newlink, id_where);
1192 * link_activate call above can insert newlink in
1193 * dlmgmt_name_avl tree when activating a link that is
1194 * assigned to a NGZ.
1196 if (avl_find(&dlmgmt_name_avl, newlink,
1197 &name_where) == NULL)
1198 avl_insert(&dlmgmt_name_avl, newlink,
1199 name_where);
1201 dlmgmt_advance(newlink);
1202 newlink->ll_flags |= req->ls_flags;
1206 return (err);
1210 * Generate an entry in the link database.
1211 * Each entry has this format:
1212 * <link name> <prop0>=<type>,<val>;...;<propn>=<type>,<val>;
1214 static void
1215 generate_link_line(dlmgmt_link_t *linkp, boolean_t persist, char *buf)
1217 char tmpbuf[MAXLINELEN];
1218 char *ptr = tmpbuf;
1219 char *lim = tmpbuf + MAXLINELEN;
1220 dlmgmt_linkattr_t *cur_p = NULL;
1221 uint64_t u64;
1223 ptr += snprintf(ptr, BUFLEN(lim, ptr), "%s\t", linkp->ll_link);
1224 if (!persist) {
1226 * We store the linkid in the active database so that dlmgmtd
1227 * can recover in the event that it is restarted.
1229 u64 = linkp->ll_linkid;
1230 ptr += write_uint64(ptr, BUFLEN(lim, ptr), "linkid", &u64);
1232 u64 = linkp->ll_class;
1233 ptr += write_uint64(ptr, BUFLEN(lim, ptr), "class", &u64);
1234 u64 = linkp->ll_media;
1235 ptr += write_uint64(ptr, BUFLEN(lim, ptr), "media", &u64);
1238 * The daemon does not keep any active link attribute. Only store the
1239 * attributes if this request is for persistent configuration,
1241 if (persist) {
1242 for (cur_p = linkp->ll_head; cur_p != NULL;
1243 cur_p = cur_p->lp_next) {
1244 ptr += translators[cur_p->lp_type].write_func(ptr,
1245 BUFLEN(lim, ptr), cur_p->lp_name, cur_p->lp_val);
1249 if (ptr <= lim)
1250 (void) snprintf(buf, MAXLINELEN, "%s\n", tmpbuf);
1254 dlmgmt_delete_db_entry(dlmgmt_link_t *linkp, uint32_t flags)
1256 return (dlmgmt_db_update(DLMGMT_DB_OP_DELETE, linkp->ll_link, linkp,
1257 flags));
1261 dlmgmt_write_db_entry(const char *entryname, dlmgmt_link_t *linkp,
1262 uint32_t flags)
1264 int err;
1266 if (flags & DLMGMT_PERSIST) {
1267 if ((err = dlmgmt_db_update(DLMGMT_DB_OP_WRITE, entryname,
1268 linkp, DLMGMT_PERSIST)) != 0) {
1269 return (err);
1273 if (flags & DLMGMT_ACTIVE) {
1274 if (((err = dlmgmt_db_update(DLMGMT_DB_OP_WRITE, entryname,
1275 linkp, DLMGMT_ACTIVE)) != 0) && (flags & DLMGMT_PERSIST)) {
1276 (void) dlmgmt_db_update(DLMGMT_DB_OP_DELETE, entryname,
1277 linkp, DLMGMT_PERSIST);
1278 return (err);
1282 return (0);
1286 * Upgrade properties that have link IDs as values to link names. Because '.'
1287 * is a valid linkname character, the port separater for link aggregations
1288 * must be changed to ':'.
1290 static void
1291 linkattr_upgrade(dlmgmt_linkattr_t *attrp)
1293 datalink_id_t linkid;
1294 char *portidstr;
1295 char portname[MAXLINKNAMELEN + 1];
1296 dlmgmt_link_t *linkp;
1297 char *new_attr_val;
1298 size_t new_attr_sz;
1299 boolean_t upgraded = B_FALSE;
1301 if (strcmp(attrp->lp_name, "linkover") == 0 ||
1302 strcmp(attrp->lp_name, "simnetpeer") == 0) {
1303 if (attrp->lp_type == DLADM_TYPE_UINT64) {
1304 linkid = (datalink_id_t)*(uint64_t *)attrp->lp_val;
1305 if ((linkp = link_by_id(linkid, GLOBAL_ZONEID)) == NULL)
1306 return;
1307 new_attr_sz = strlen(linkp->ll_link) + 1;
1308 if ((new_attr_val = malloc(new_attr_sz)) == NULL)
1309 return;
1310 (void) strcpy(new_attr_val, linkp->ll_link);
1311 upgraded = B_TRUE;
1313 } else if (strcmp(attrp->lp_name, "portnames") == 0) {
1315 * The old format for "portnames" was
1316 * "<linkid>.[<linkid>.]...". The new format is
1317 * "<linkname>:[<linkname>:]...".
1319 if (!isdigit(((char *)attrp->lp_val)[0]))
1320 return;
1321 new_attr_val = calloc(MAXLINKATTRVALLEN, sizeof (char));
1322 if (new_attr_val == NULL)
1323 return;
1324 portidstr = (char *)attrp->lp_val;
1325 while (*portidstr != '\0') {
1326 errno = 0;
1327 linkid = strtol(portidstr, &portidstr, 10);
1328 if (linkid == 0 || *portidstr != '.' ||
1329 (linkp = link_by_id(linkid, GLOBAL_ZONEID)) ==
1330 NULL) {
1331 free(new_attr_val);
1332 return;
1334 (void) snprintf(portname, sizeof (portname), "%s:",
1335 linkp->ll_link);
1336 if (strlcat(new_attr_val, portname,
1337 MAXLINKATTRVALLEN) >= MAXLINKATTRVALLEN) {
1338 free(new_attr_val);
1339 return;
1341 /* skip the '.' delimiter */
1342 portidstr++;
1344 new_attr_sz = strlen(new_attr_val) + 1;
1345 upgraded = B_TRUE;
1348 if (upgraded) {
1349 attrp->lp_type = DLADM_TYPE_STR;
1350 attrp->lp_sz = new_attr_sz;
1351 free(attrp->lp_val);
1352 attrp->lp_val = new_attr_val;
1356 static void
1357 dlmgmt_db_upgrade(dlmgmt_link_t *linkp)
1359 dlmgmt_linkattr_t *attrp;
1361 for (attrp = linkp->ll_head; attrp != NULL; attrp = attrp->lp_next)
1362 linkattr_upgrade(attrp);
1365 static void
1366 dlmgmt_db_phys_activate(dlmgmt_link_t *linkp)
1368 linkp->ll_flags |= DLMGMT_ACTIVE;
1369 (void) dlmgmt_write_db_entry(linkp->ll_link, linkp, DLMGMT_ACTIVE);
1372 static void
1373 dlmgmt_db_walk(zoneid_t zoneid, datalink_class_t class, db_walk_func_t *func)
1375 dlmgmt_link_t *linkp;
1377 for (linkp = avl_first(&dlmgmt_id_avl); linkp != NULL;
1378 linkp = AVL_NEXT(&dlmgmt_id_avl, linkp)) {
1379 if (linkp->ll_zoneid == zoneid && (linkp->ll_class & class))
1380 func(linkp);
1385 * Initialize the datalink <link name, linkid> mapping and the link's
1386 * attributes list based on the configuration file /etc/dladm/datalink.conf
1387 * and the active configuration cache file
1388 * /etc/svc/volatile/dladm/datalink-management:default.cache.
1391 dlmgmt_db_init(zoneid_t zoneid)
1393 dlmgmt_db_req_t *req;
1394 int err;
1395 boolean_t boot = B_FALSE;
1397 if ((req = dlmgmt_db_req_alloc(DLMGMT_DB_OP_READ, NULL,
1398 DATALINK_INVALID_LINKID, zoneid, DLMGMT_ACTIVE, &err)) == NULL)
1399 return (err);
1401 if ((err = dlmgmt_process_db_req(req)) != 0) {
1403 * If we get back ENOENT, that means that the active
1404 * configuration file doesn't exist yet, and is not an error.
1405 * We'll create it down below after we've loaded the
1406 * persistent configuration.
1408 if (err != ENOENT)
1409 goto done;
1410 boot = B_TRUE;
1413 req->ls_flags = DLMGMT_PERSIST;
1414 err = dlmgmt_process_db_req(req);
1415 if (err != 0 && err != ENOENT)
1416 goto done;
1417 err = 0;
1418 if (rewrite_needed) {
1420 * First update links in memory, then dump the entire db to
1421 * disk.
1423 dlmgmt_db_walk(zoneid, DATALINK_CLASS_ALL, dlmgmt_db_upgrade);
1424 req->ls_op = DLMGMT_DB_OP_WRITE;
1425 req->ls_linkid = DATALINK_ALL_LINKID;
1426 if ((err = dlmgmt_process_db_req(req)) != 0 &&
1427 err != EINPROGRESS)
1428 goto done;
1430 if (boot) {
1431 dlmgmt_db_walk(zoneid, DATALINK_CLASS_PHYS,
1432 dlmgmt_db_phys_activate);
1435 done:
1436 if (err == EINPROGRESS)
1437 err = 0;
1438 else
1439 free(req);
1440 return (err);
1444 * Remove all links in the given zoneid.
1446 void
1447 dlmgmt_db_fini(zoneid_t zoneid)
1449 dlmgmt_link_t *linkp = avl_first(&dlmgmt_name_avl), *next_linkp;
1451 while (linkp != NULL) {
1452 next_linkp = AVL_NEXT(&dlmgmt_name_avl, linkp);
1453 if (linkp->ll_zoneid == zoneid) {
1454 (void) dlmgmt_destroy_common(linkp,
1455 DLMGMT_ACTIVE | DLMGMT_PERSIST);
1457 linkp = next_linkp;