1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <ctype.h>
29 #include <unistd.h>
30 #include <stropts.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <strings.h>
34 #include <dirent.h>
35 #include <net/if.h>
36 #include <sys/stat.h>
37 #include <sys/dld.h>
38 #include <libdlpi.h>
39 #include <libdevinfo.h>
40 #include <libdladm_impl.h>
41 #include <libintl.h>
42 #include <sys/vlan.h>
43 
44 typedef struct dladm_dev {
45 	char			dd_name[IFNAMSIZ];
46 	struct dladm_dev	*dd_next;
47 } dladm_dev_t;
48 
49 typedef struct dladm_walk {
50 	dladm_dev_t		*dw_dev_list;
51 } dladm_walk_t;
52 
53 static char		dladm_rootdir[MAXPATHLEN] = "/";
54 
55 /*
56  * Issue an ioctl to the specified file descriptor attached to the
57  * DLD control driver interface.
58  */
59 int
60 i_dladm_ioctl(int fd, int ic_cmd, void *ic_dp, int ic_len)
61 {
62 	struct strioctl	iocb;
63 
64 	iocb.ic_cmd = ic_cmd;
65 	iocb.ic_timout = 0;
66 	iocb.ic_len = ic_len;
67 	iocb.ic_dp = (char *)ic_dp;
68 
69 	return (ioctl(fd, I_STR, &iocb));
70 }
71 
72 /*
73  * Return the attributes of the specified datalink from the DLD driver.
74  */
75 static int
76 i_dladm_info(int fd, const char *name, dladm_attr_t *dap)
77 {
78 	dld_ioc_attr_t	dia;
79 
80 	if (strlen(name) >= IFNAMSIZ) {
81 		errno = EINVAL;
82 		return (-1);
83 	}
84 
85 	(void) strlcpy(dia.dia_name, name, IFNAMSIZ);
86 
87 	if (i_dladm_ioctl(fd, DLDIOCATTR, &dia, sizeof (dia)) < 0)
88 		return (-1);
89 
90 	(void) strlcpy(dap->da_dev, dia.dia_dev, MAXNAMELEN);
91 	dap->da_max_sdu = dia.dia_max_sdu;
92 	dap->da_vid = dia.dia_vid;
93 
94 	return (0);
95 }
96 
97 /*
98  * Adds a datalink to the array corresponding to arg.
99  */
100 static void
101 i_dladm_nt_net_add(void *arg, char *name)
102 {
103 	dladm_walk_t	*dwp = arg;
104 	dladm_dev_t	*ddp = dwp->dw_dev_list;
105 	dladm_dev_t	**lastp = &dwp->dw_dev_list;
106 
107 	while (ddp) {
108 		/*
109 		 * Skip duplicates.
110 		 */
111 		if (strcmp(ddp->dd_name, name) == 0)
112 			return;
113 
114 		lastp = &ddp->dd_next;
115 		ddp = ddp->dd_next;
116 	}
117 
118 	if ((ddp = malloc(sizeof (*ddp))) == NULL)
119 		return;
120 
121 	(void) strlcpy(ddp->dd_name, name, IFNAMSIZ);
122 	ddp->dd_next = NULL;
123 	*lastp = ddp;
124 }
125 
126 /*
127  * Walker callback invoked for each DDI_NT_NET node.
128  */
129 static int
130 i_dladm_nt_net_walk(di_node_t node, di_minor_t minor, void *arg)
131 {
132 	char		linkname[DLPI_LINKNAME_MAX];
133 	dlpi_handle_t	dh;
134 
135 	if (dlpi_makelink(linkname, di_minor_name(minor),
136 	    di_instance(node)) != DLPI_SUCCESS)
137 		return (DI_WALK_CONTINUE);
138 
139 	if (dlpi_open(linkname, &dh, 0) == DLPI_SUCCESS) {
140 		i_dladm_nt_net_add(arg, linkname);
141 		dlpi_close(dh);
142 	}
143 	return (DI_WALK_CONTINUE);
144 }
145 
146 /*
147  * Hold a data-link.
148  */
149 static int
150 i_dladm_hold_link(const char *name, zoneid_t zoneid, boolean_t docheck)
151 {
152 	int		fd;
153 	dld_hold_vlan_t	dhv;
154 
155 	if (strlen(name) >= IFNAMSIZ) {
156 		errno = EINVAL;
157 		return (-1);
158 	}
159 
160 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
161 		return (-1);
162 
163 	bzero(&dhv, sizeof (dld_hold_vlan_t));
164 	(void) strlcpy(dhv.dhv_name, name, IFNAMSIZ);
165 	dhv.dhv_zid = zoneid;
166 	dhv.dhv_docheck = docheck;
167 
168 	if (i_dladm_ioctl(fd, DLDIOCHOLDVLAN, &dhv, sizeof (dhv)) < 0) {
169 		int olderrno = errno;
170 
171 		(void) close(fd);
172 		errno = olderrno;
173 		return (-1);
174 	}
175 
176 	(void) close(fd);
177 	return (0);
178 }
179 
180 /*
181  * Release a data-link.
182  */
183 static int
184 i_dladm_rele_link(const char *name, zoneid_t zoneid, boolean_t docheck)
185 {
186 	int		fd;
187 	dld_hold_vlan_t	dhv;
188 
189 	if (strlen(name) >= IFNAMSIZ) {
190 		errno = EINVAL;
191 		return (-1);
192 	}
193 
194 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
195 		return (-1);
196 
197 	bzero(&dhv, sizeof (dld_hold_vlan_t));
198 	(void) strlcpy(dhv.dhv_name, name, IFNAMSIZ);
199 	dhv.dhv_zid = zoneid;
200 	dhv.dhv_docheck = docheck;
201 
202 	if (i_dladm_ioctl(fd, DLDIOCRELEVLAN, &dhv, sizeof (dhv)) < 0) {
203 		int olderrno = errno;
204 
205 		(void) close(fd);
206 		errno = olderrno;
207 		return (-1);
208 	}
209 
210 	(void) close(fd);
211 	return (0);
212 }
213 
214 /*
215  * Invoke the specified callback function for each active DDI_NT_NET
216  * node.
217  */
218 int
219 dladm_walk(void (*fn)(void *, const char *), void *arg)
220 {
221 	di_node_t	root;
222 	dladm_walk_t	dw;
223 	dladm_dev_t	*ddp, *last_ddp;
224 
225 	if ((root = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
226 		errno = EFAULT;
227 		return (-1);
228 	}
229 	dw.dw_dev_list = NULL;
230 
231 	(void) di_walk_minor(root, DDI_NT_NET, DI_CHECK_ALIAS, &dw,
232 	    i_dladm_nt_net_walk);
233 
234 	di_fini(root);
235 
236 	ddp = dw.dw_dev_list;
237 	while (ddp) {
238 		fn(arg, ddp->dd_name);
239 		last_ddp = ddp;
240 		ddp = ddp->dd_next;
241 		free(last_ddp);
242 	}
243 
244 	return (0);
245 }
246 
247 /*
248  * Invoke the specified callback function for each vlan managed by dld
249  */
250 int
251 dladm_walk_vlan(void (*fn)(void *, const char *), void *arg, const char *name)
252 {
253 	int		fd, bufsize, i;
254 	int		nvlan = 4094;
255 	dld_ioc_vlan_t	*iocp = NULL;
256 	dld_vlan_info_t	*dvip;
257 
258 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
259 		return (-1);
260 
261 	bufsize = sizeof (dld_ioc_vlan_t) + nvlan * sizeof (dld_vlan_info_t);
262 
263 	if ((iocp = (dld_ioc_vlan_t *)calloc(1, bufsize)) == NULL)
264 		return (-1);
265 
266 	(void) strlcpy((char *)iocp->div_name, name, IFNAMSIZ);
267 	if (i_dladm_ioctl(fd, DLDIOCVLAN, iocp, bufsize) == 0) {
268 		dvip = (dld_vlan_info_t *)(iocp + 1);
269 		for (i = 0; i < iocp->div_count; i++)
270 			(*fn)(arg, dvip[i].dvi_name);
271 	}
272 	/*
273 	 * Note: Callers of dladm_walk_vlan() ignore the return
274 	 * value of this routine. So ignoring ioctl failure case
275 	 * and just returning 0.
276 	 */
277 	free(iocp);
278 	(void) close(fd);
279 	return (0);
280 }
281 
282 
283 /*
284  * Returns the current attributes of the specified datalink.
285  */
286 int
287 dladm_info(const char *name, dladm_attr_t *dap)
288 {
289 	int		fd;
290 
291 	if ((fd = open(DLD_CONTROL_DEV, O_RDWR)) < 0)
292 		return (-1);
293 
294 	if (i_dladm_info(fd, name, dap) < 0)
295 		goto failed;
296 
297 	(void) close(fd);
298 	return (0);
299 
300 failed:
301 	(void) close(fd);
302 	return (-1);
303 }
304 
305 const char *
306 dladm_status2str(dladm_status_t status, char *buf)
307 {
308 	const char	*s;
309 
310 	switch (status) {
311 	case DLADM_STATUS_OK:
312 		s = "ok";
313 		break;
314 	case DLADM_STATUS_BADARG:
315 		s = "invalid argument";
316 		break;
317 	case DLADM_STATUS_FAILED:
318 		s = "operation failed";
319 		break;
320 	case DLADM_STATUS_TOOSMALL:
321 		s = "buffer size too small";
322 		break;
323 	case DLADM_STATUS_NOTSUP:
324 		s = "operation not supported";
325 		break;
326 	case DLADM_STATUS_NOTFOUND:
327 		s = "object not found";
328 		break;
329 	case DLADM_STATUS_BADVAL:
330 		s = "invalid value";
331 		break;
332 	case DLADM_STATUS_NOMEM:
333 		s = "insufficient memory";
334 		break;
335 	case DLADM_STATUS_EXIST:
336 		s = "object already exists";
337 		break;
338 	case DLADM_STATUS_LINKINVAL:
339 		s = "invalid link";
340 		break;
341 	case DLADM_STATUS_PROPRDONLY:
342 		s = "read-only property";
343 		break;
344 	case DLADM_STATUS_BADVALCNT:
345 		s = "invalid number of values";
346 		break;
347 	case DLADM_STATUS_DBNOTFOUND:
348 		s = "database not found";
349 		break;
350 	case DLADM_STATUS_DENIED:
351 		s = "permission denied";
352 		break;
353 	case DLADM_STATUS_IOERR:
354 		s = "I/O error";
355 		break;
356 	case DLADM_STATUS_TEMPONLY:
357 		s = "change cannot be persistent, specify -t please";
358 		break;
359 	default:
360 		s = "<unknown error>";
361 		break;
362 	}
363 	(void) snprintf(buf, DLADM_STRSIZE, "%s", dgettext(TEXT_DOMAIN, s));
364 	return (buf);
365 }
366 
367 /*
368  * Convert a unix errno to a dladm_status_t.
369  * We only convert errnos that are likely to be encountered. All others
370  * are mapped to DLADM_STATUS_FAILED.
371  */
372 dladm_status_t
373 dladm_errno2status(int err)
374 {
375 	switch (err) {
376 	case EINVAL:
377 		return (DLADM_STATUS_BADARG);
378 	case EEXIST:
379 		return (DLADM_STATUS_EXIST);
380 	case ENOENT:
381 		return (DLADM_STATUS_NOTFOUND);
382 	case ENOSPC:
383 		return (DLADM_STATUS_TOOSMALL);
384 	case ENOMEM:
385 		return (DLADM_STATUS_NOMEM);
386 	case ENOTSUP:
387 		return (DLADM_STATUS_NOTSUP);
388 	case EACCES:
389 		return (DLADM_STATUS_DENIED);
390 	case EIO:
391 		return (DLADM_STATUS_IOERR);
392 	default:
393 		return (DLADM_STATUS_FAILED);
394 	}
395 }
396 
397 /*
398  * These are the uid and gid of the user 'dladm'.
399  * The directory /etc/dladm and all files under it are owned by this user.
400  */
401 #define	DLADM_DB_OWNER		15
402 #define	DLADM_DB_GROUP		3
403 #define	LOCK_DB_PERMS		S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
404 
405 static int
406 i_dladm_lock_db(const char *lock_file, short type)
407 {
408 	int	lock_fd;
409 	struct  flock lock;
410 
411 	if ((lock_fd = open(lock_file, O_RDWR | O_CREAT | O_TRUNC,
412 	    LOCK_DB_PERMS)) < 0)
413 		return (-1);
414 
415 	lock.l_type = type;
416 	lock.l_whence = SEEK_SET;
417 	lock.l_start = 0;
418 	lock.l_len = 0;
419 
420 	if (fcntl(lock_fd, F_SETLKW, &lock) < 0) {
421 		int err = errno;
422 
423 		(void) close(lock_fd);
424 		(void) unlink(lock_file);
425 		errno = err;
426 		return (-1);
427 	}
428 	return (lock_fd);
429 }
430 
431 static void
432 i_dladm_unlock_db(const char *lock_file, int fd)
433 {
434 	struct flock lock;
435 
436 	if (fd < 0)
437 		return;
438 
439 	lock.l_type = F_UNLCK;
440 	lock.l_whence = SEEK_SET;
441 	lock.l_start = 0;
442 	lock.l_len = 0;
443 
444 	(void) fcntl(fd, F_SETLKW, &lock);
445 	(void) close(fd);
446 	(void) unlink(lock_file);
447 }
448 
449 dladm_status_t
450 i_dladm_rw_db(const char *db_file, mode_t db_perms,
451     dladm_status_t (*process_db)(void *, FILE *, FILE *),
452     void *arg, boolean_t writeop)
453 {
454 	dladm_status_t	status = DLADM_STATUS_OK;
455 	FILE		*fp, *nfp = NULL;
456 	char		lock[MAXPATHLEN];
457 	char		file[MAXPATHLEN];
458 	char		newfile[MAXPATHLEN];
459 	char		*db_basename;
460 	int		nfd, lock_fd;
461 
462 	/*
463 	 * If we are called from a boot script such as net-physical,
464 	 * it's quite likely that the root fs is still not writable.
465 	 * For this case, it's ok for the lock creation to fail since
466 	 * no one else could be accessing our configuration file.
467 	 */
468 	db_basename = strrchr(db_file, '/');
469 	if (db_basename == NULL || db_basename[1] == '\0')
470 		return (dladm_errno2status(EINVAL));
471 	db_basename++;
472 	(void) snprintf(lock, MAXPATHLEN, "/tmp/%s.lock", db_basename);
473 	if ((lock_fd = i_dladm_lock_db
474 	    (lock, (writeop ? F_WRLCK : F_RDLCK))) < 0 && errno != EROFS)
475 		return (dladm_errno2status(errno));
476 
477 	(void) snprintf(file, MAXPATHLEN, "%s/%s", dladm_rootdir, db_file);
478 	if ((fp = fopen(file, (writeop ? "r+" : "r"))) == NULL) {
479 		int	err = errno;
480 
481 		i_dladm_unlock_db(lock, lock_fd);
482 		if (err == ENOENT)
483 			return (DLADM_STATUS_DBNOTFOUND);
484 
485 		return (dladm_errno2status(err));
486 	}
487 
488 	if (writeop) {
489 		(void) snprintf(newfile, MAXPATHLEN, "%s/%s.new",
490 		    dladm_rootdir, db_file);
491 		if ((nfd = open(newfile, O_WRONLY | O_CREAT | O_TRUNC,
492 		    db_perms)) < 0) {
493 			(void) fclose(fp);
494 			i_dladm_unlock_db(lock, lock_fd);
495 			return (dladm_errno2status(errno));
496 		}
497 
498 		if ((nfp = fdopen(nfd, "w")) == NULL) {
499 			(void) close(nfd);
500 			(void) fclose(fp);
501 			(void) unlink(newfile);
502 			i_dladm_unlock_db(lock, lock_fd);
503 			return (dladm_errno2status(errno));
504 		}
505 	}
506 	status = (*process_db)(arg, fp, nfp);
507 	if (!writeop || status != DLADM_STATUS_OK)
508 		goto done;
509 
510 	/*
511 	 * Configuration files need to be owned by the 'dladm' user.
512 	 * If we are invoked by root, the file ownership needs to be fixed.
513 	 */
514 	if (getuid() == 0 || geteuid() == 0) {
515 		if (fchown(nfd, DLADM_DB_OWNER, DLADM_DB_GROUP) < 0) {
516 			status = dladm_errno2status(errno);
517 			goto done;
518 		}
519 	}
520 
521 	if (fflush(nfp) == EOF) {
522 		status = dladm_errno2status(errno);
523 		goto done;
524 	}
525 	(void) fclose(fp);
526 	(void) fclose(nfp);
527 
528 	if (rename(newfile, file) < 0) {
529 		(void) unlink(newfile);
530 		i_dladm_unlock_db(lock, lock_fd);
531 		return (dladm_errno2status(errno));
532 	}
533 
534 	i_dladm_unlock_db(lock, lock_fd);
535 	return (DLADM_STATUS_OK);
536 
537 done:
538 	if (nfp != NULL) {
539 		(void) fclose(nfp);
540 		if (status != DLADM_STATUS_OK)
541 			(void) unlink(newfile);
542 	}
543 	(void) fclose(fp);
544 	i_dladm_unlock_db(lock, lock_fd);
545 	return (status);
546 }
547 
548 dladm_status_t
549 dladm_set_rootdir(const char *rootdir)
550 {
551 	DIR	*dp;
552 
553 	if (rootdir == NULL || *rootdir != '/' ||
554 	    (dp = opendir(rootdir)) == NULL)
555 		return (DLADM_STATUS_BADARG);
556 
557 	(void) strncpy(dladm_rootdir, rootdir, MAXPATHLEN);
558 	(void) closedir(dp);
559 	return (DLADM_STATUS_OK);
560 }
561 
562 /*
563  * Do a "hold" operation to a link.
564  */
565 int
566 dladm_hold_link(const char *name, zoneid_t zoneid, boolean_t docheck)
567 {
568 	return (i_dladm_hold_link(name, zoneid, docheck));
569 }
570 
571 /*
572  * Do a "release" operation to a link.
573  */
574 int
575 dladm_rele_link(const char *name, zoneid_t zoneid, boolean_t docheck)
576 {
577 	return (i_dladm_rele_link(name, zoneid, docheck));
578 }
579