xref: /illumos-gate/usr/src/cmd/devfsadm/port_link.c (revision 4c28a617e3922d92a58e813a5b955eb526b9c386)
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 /*
23  * Copyright 2013 Garrett D'Amore <garrett@damore.org>
24  * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
25  */
26 
27 #include <unistd.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <regex.h>
32 #include <sac.h>
33 #include <errno.h>
34 #include <dirent.h>
35 #include <limits.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/wait.h>
39 #include <fcntl.h>
40 #include <devfsadm.h>
41 #include <syslog.h>
42 
43 /*
44  * sacadm output parsing
45  */
46 #define	PMTAB_MAXLINE		512
47 #define	PMTAB_SEPR		':'
48 #define	PMTAB_DEVNAME_FIELD	7	/* field containing /dev/term/n */
49 #define	DIALOUT_SUFFIX		",cu"
50 #define	DEVNAME_SEPR		'/'
51 #define	MN_SEPR			','
52 #define	MN_NULLCHAR		'\0'
53 
54 /*
55  * sacadm/pmadm exit codes (see /usr/include/sac.h)
56  */
57 static char *sacerrs[] = {
58 	"UNKNOWN", "Unknown exit code",
59 	"E_BADARGS", "Invalid arguments",
60 	"E_NOPRIV", "Not privileged",
61 	"E_SAFERR", "SAF error",
62 	"E_SYSERR",  "System error",
63 	"E_NOEXIST", "Entry does not exist",
64 	"E_DUP", "Entry already exists",
65 	"E_PMRUN", "Port monitor already running",
66 	"E_PMNOTRUN", "Port monitor not running",
67 	"E_RECOVER", "In recovery",
68 	"E_SACNOTRUN", "SAC daemon not running",
69 };
70 
71 #define	SAC_EXITVAL(x)		((x) >> 8)
72 #define	SAC_EID(x)	\
73 	(sacerrs[((uint_t)(x) > E_SACNOTRUN ? 0 : ((x)<<1))])
74 #define	SAC_EMSG(x) \
75 	(sacerrs[((uint_t)(x) > E_SACNOTRUN ? 1 : (((x)<<1) + 1))])
76 
77 
78 
79 /*
80  * create port monitors for each group of PM_GRPSZ port devices.
81  */
82 #define	PM_GRPSZ		64
83 
84 /*
85  * compute port monitor # and base index
86  */
87 #define	PM_NUM(p)	((p) / PM_GRPSZ)
88 #define	PM_SLOT(p)	(PM_NUM(p) * PM_GRPSZ)
89 
90 
91 /*
92  * default maxports value
93  * override by setting SUNW_port_link.maxports in default/devfsadm
94  */
95 #define	MAXPORTS_DEFAULT	2048
96 
97 /*
98  * command line buffer size for sacadm
99  */
100 #define	CMDLEN			1024
101 
102 struct pm_alloc {
103 	uint_t	flags;
104 	char	*pm_tag;
105 };
106 
107 /* port monitor entry flags */
108 #define	PM_HAS_ENTRY	0x1		/* pm entry for this port */
109 #define	HAS_PORT_DEVICE	0x2		/* device exists */
110 #define	PORT_REMOVED	0x4		/* dangling port */
111 #define	HAS_PORT_MON	0x8		/* port monitor active */
112 #define	PM_NEEDED	0x10		/* port monitor needed */
113 
114 static int maxports;
115 static struct pm_alloc *pma;
116 static char *modname = "SUNW_port_link";
117 
118 /*
119  * devfsadm_print message id
120  */
121 #define	PORT_MID	"SUNW_port_link"
122 
123 /*
124  * enumeration regular expressions, port and onboard port devices
125  * On x86, /dev/term|cua/[a..z] namespace is split into 2:
126  * a-d are assigned based on minor name. e-z are
127  * assigned via enumeration.
128  */
129 static devfsadm_enumerate_t port_rules[] =
130 	{"^(term|cua)$/^([0-9]+)$", 1, MATCH_MINOR, "1"};
131 
132 #ifdef __i386
133 static devfsadm_enumerate_t obport_rules[] =
134 	{"^(term|cua)$/^([e-z])$", 1, MATCH_MINOR, "1"};
135 static char start_id[] = "e";
136 #else
137 static devfsadm_enumerate_t obport_rules[] =
138 	{"^(term|cua)$/^([a-z])$", 1, MATCH_MINOR, "1"};
139 static char start_id[] = "a";
140 #endif
141 
142 static int serial_port_create(di_minor_t minor, di_node_t node);
143 static int onbrd_port_create(di_minor_t minor, di_node_t node);
144 static int dialout_create(di_minor_t minor, di_node_t node);
145 static int onbrd_dialout_create(di_minor_t minor, di_node_t node);
146 static int rsc_port_create(di_minor_t minor, di_node_t node);
147 static int lom_port_create(di_minor_t minor, di_node_t node);
148 static void rm_dangling_port(char *devname);
149 static void update_sacadm_db(void);
150 static int parse_portno(char *dname);
151 static int is_dialout(char *dname);
152 static int load_ttymondb(void);
153 static void remove_pm_entry(char *pmtag, int port);
154 static void add_pm_entry(int port);
155 static void delete_port_monitor(int port);
156 static void add_port_monitor(int port);
157 static int execute(const char *s);
158 static char *pmtab_parse_portname(char *cmdbuf);
159 static void *pma_alloc(void);
160 static void pma_free(void);
161 extern char *defread(char *varname);
162 extern int defopen(char *fname);
163 
164 /*
165  * devfs create callback register
166  */
167 static devfsadm_create_t ports_cbt[] = {
168 	{"pseudo", "ddi_pseudo", "su",
169 	    TYPE_EXACT | DRV_EXACT, ILEVEL_1, rsc_port_create},
170 	{"port", "ddi_serial:lomcon", "su",
171 	    TYPE_EXACT | DRV_EXACT, ILEVEL_1, lom_port_create},
172 	{"port", "ddi_serial", NULL,
173 	    TYPE_EXACT, ILEVEL_0, serial_port_create},
174 	{"port", "ddi_serial:mb", NULL,
175 	    TYPE_EXACT, ILEVEL_0, onbrd_port_create},
176 	{"port", "ddi_serial:dialout", NULL,
177 	    TYPE_EXACT, ILEVEL_0, dialout_create},
178 	{"port", "ddi_serial:dialout,mb", NULL,
179 	    TYPE_EXACT, ILEVEL_0, onbrd_dialout_create},
180 };
181 DEVFSADM_CREATE_INIT_V0(ports_cbt);
182 
183 /*
184  * devfs cleanup register
185  * no cleanup rules for PCMCIA port devices
186  */
187 static devfsadm_remove_t ports_remove_cbt[] = {
188 	{"port", "^term/[0-9]+$", RM_PRE | RM_ALWAYS | RM_HOT, ILEVEL_0,
189 	    rm_dangling_port},
190 	{"port", "^cua/[0-9]+$", RM_PRE | RM_ALWAYS | RM_HOT, ILEVEL_0,
191 	    devfsadm_rm_all},
192 	{"port", "^(term|cua)/[a-z]$",
193 	    RM_PRE | RM_ALWAYS, ILEVEL_0, devfsadm_rm_all},
194 };
195 DEVFSADM_REMOVE_INIT_V0(ports_remove_cbt);
196 
197 int
198 minor_init()
199 {
200 	char *maxport_str;
201 
202 	if (defopen("/etc/default/devfsadm") == 0) {
203 		maxport_str = defread("SUNW_port_link.maxports");
204 		if ((maxport_str == NULL) ||
205 		    (sscanf(maxport_str, "%d", &maxports) != 1)) {
206 			maxports = MAXPORTS_DEFAULT;
207 		}
208 		/* close defaults file */
209 		(void) defopen(NULL);
210 	} else {
211 		maxports = MAXPORTS_DEFAULT;
212 	}
213 
214 	devfsadm_print(CHATTY_MID, "%s: maximum number of port devices (%d)\n",
215 	    modname, maxports);
216 
217 	if (pma_alloc() == NULL)
218 		return (DEVFSADM_FAILURE);
219 
220 	return (DEVFSADM_SUCCESS);
221 }
222 
223 int
224 minor_fini()
225 {
226 	/*
227 	 * update the sacadm database only if we are updating
228 	 * this platform (no -r option)
229 	 */
230 	if (strcmp(devfsadm_root_path(), "/") == 0)
231 		update_sacadm_db();
232 
233 	pma_free();
234 
235 	return (DEVFSADM_SUCCESS);
236 }
237 
238 /*
239  * Called for all serial devices that are NOT onboard
240  * Creates links of the form "/dev/term/[0..n]"
241  * Schedules an update the sacadm (portmon).
242  */
243 static int
244 serial_port_create(di_minor_t minor, di_node_t node)
245 {
246 	char l_path[MAXPATHLEN], p_path[MAXPATHLEN];
247 	char *devfspath, *buf, *minor_name;
248 	int port_num;
249 
250 	devfspath = di_devfs_path(node);
251 	if (devfspath == NULL) {
252 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
253 		return (DEVFSADM_CONTINUE);
254 	}
255 
256 	if ((minor_name = di_minor_name(minor)) == NULL) {
257 		devfsadm_errprint("%s: NULL minor name\n\t%s\n", modname,
258 		    devfspath);
259 		di_devfs_path_free(devfspath);
260 		return (DEVFSADM_CONTINUE);
261 	}
262 
263 	/*
264 	 * verify dialout ports do not come in on this nodetype
265 	 */
266 	if (is_dialout(minor_name)) {
267 		devfsadm_errprint("%s: dialout device\n\t%s:%s\n",
268 		    modname, devfspath, minor_name);
269 		di_devfs_path_free(devfspath);
270 		return (DEVFSADM_CONTINUE);
271 	}
272 
273 	/*
274 	 * add the minor name to the physical path so we can
275 	 * enum the port# and create the link.
276 	 */
277 	(void) strcpy(p_path, devfspath);
278 	(void) strcat(p_path, ":");
279 	(void) strcat(p_path, minor_name);
280 	di_devfs_path_free(devfspath);
281 
282 	if (devfsadm_enumerate_int(p_path, 0, &buf, port_rules, 1)) {
283 		devfsadm_errprint("%s:serial_port_create:"
284 		    " enumerate_int() failed\n\t%s\n",
285 		    modname, p_path);
286 		return (DEVFSADM_CONTINUE);
287 	}
288 
289 	(void) strcpy(l_path, "term/");
290 	(void) strcat(l_path, buf);
291 	(void) devfsadm_mklink(l_path, node, minor, 0);
292 
293 	/*
294 	 * This is probably a USB serial port coming into the system
295 	 * because someone just plugged one in.  Log an indication of
296 	 * this to syslog just in case someone wants to know what the
297 	 * name of the new serial device is ..
298 	 */
299 	(void) syslog(LOG_INFO, "serial device /dev/%s present", l_path);
300 
301 	/*
302 	 * update the portmon database if this port falls within
303 	 * the valid range of ports.
304 	 */
305 	if ((port_num = parse_portno(buf)) != -1) {
306 		pma[port_num].flags |= HAS_PORT_DEVICE;
307 	}
308 
309 	free(buf);
310 	return (DEVFSADM_CONTINUE);
311 }
312 
313 /*
314  * Called for all dialout devices that are NOT onboard
315  * Creates links of the form "/dev/cua/[0..n]"
316  */
317 static int
318 dialout_create(di_minor_t minor, di_node_t node)
319 {
320 	char l_path[MAXPATHLEN], p_path[MAXPATHLEN];
321 	char  *devfspath, *buf, *mn;
322 
323 	devfspath = di_devfs_path(node);
324 	if (devfspath == NULL) {
325 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
326 		return (DEVFSADM_CONTINUE);
327 	}
328 
329 	if ((mn = di_minor_name(minor)) == NULL) {
330 		devfsadm_errprint("%s: NULL minorname\n\t%s\n",
331 		    modname, devfspath);
332 		di_devfs_path_free(devfspath);
333 		return (DEVFSADM_CONTINUE);
334 	}
335 
336 	if (!is_dialout(mn)) {
337 		devfsadm_errprint("%s: invalid minor name\n\t%s:%s\n",
338 		    modname, devfspath, mn);
339 		di_devfs_path_free(devfspath);
340 		return (DEVFSADM_CONTINUE);
341 	}
342 
343 	(void) strcpy(p_path, devfspath);
344 	(void) strcat(p_path, ":");
345 	(void) strcat(p_path, mn);
346 	di_devfs_path_free(devfspath);
347 
348 	if (devfsadm_enumerate_int(p_path, 0, &buf, port_rules, 1)) {
349 		devfsadm_errprint("%s:dialout_create:"
350 		    " enumerate_int() failed\n\t%s\n",
351 		    modname, p_path);
352 		return (DEVFSADM_CONTINUE);
353 	}
354 	(void) strcpy(l_path, "cua/");
355 	(void) strcat(l_path, buf);
356 
357 	/*
358 	 *  add the minor name to the physical path so we can create
359 	 *  the link.
360 	 */
361 	(void) devfsadm_mklink(l_path, node, minor, 0);
362 
363 	free(buf);
364 	return (DEVFSADM_CONTINUE);
365 }
366 
367 #ifdef __i386
368 
369 static int
370 portcmp(char *devfs_path, char *phys_path)
371 {
372 	char *p1, *p2;
373 	int rv;
374 
375 	p2 = NULL;
376 
377 	p1 = strrchr(devfs_path, ':');
378 	if (p1 == NULL)
379 		return (1);
380 
381 	p1 = strchr(p1, ',');
382 	if (p1)
383 		*p1 = '\0';
384 
385 	p2 = strrchr(phys_path, ':');
386 	if (p2 == NULL) {
387 		rv = -1;
388 		goto out;
389 	}
390 
391 	p2 = strchr(p2, ',');
392 	if (p2)
393 		*p2 = '\0';
394 
395 	rv = strcmp(devfs_path, phys_path);
396 
397 out:
398 	if (p1)
399 		*p1 = ',';
400 	if (p2)
401 		*p2 = ',';
402 
403 	return (rv);
404 }
405 
406 /*
407  * If the minor name begins with [a-d] and the
408  * links in /dev/term/<char> and /dev/cua/<char>
409  * don't point at a different minor, then we can
410  * create compatibility links for this minor.
411  * Returns:
412  *	port id if a compatibility link can be created.
413  *	NULL otherwise
414  */
415 static char *
416 check_compat_ports(di_node_t node, char *phys_path, char *minor)
417 {
418 	char portid = *minor;
419 	char port[PATH_MAX];
420 	char *devfs_path;
421 
422 	if (portid < 'a' || portid >  'd')
423 		return (NULL);
424 
425 	(void) snprintf(port, sizeof (port), "term/%c", portid);
426 	if (devfsadm_read_link(node, port, &devfs_path) == DEVFSADM_SUCCESS &&
427 	    portcmp(devfs_path, phys_path) != 0) {
428 		free(devfs_path);
429 		return (NULL);
430 	}
431 
432 	free(devfs_path);
433 
434 	(void) snprintf(port, sizeof (port), "cua/%c", portid);
435 	if (devfsadm_read_link(node, port, &devfs_path) == DEVFSADM_SUCCESS &&
436 	    portcmp(devfs_path, phys_path) != 0) {
437 		free(devfs_path);
438 		return (NULL);
439 	}
440 
441 	free(devfs_path);
442 
443 	/*
444 	 * Neither link exists or both links point at "phys_path"
445 	 * We can safely create compatibility links.
446 	 */
447 	port[0] = portid;
448 	port[1] = '\0';
449 
450 	return (s_strdup(port));
451 }
452 
453 #endif
454 
455 /*
456  * Called for all Onboard serial devices
457  * Creates links of the form "/dev/term/[a..z]"
458  */
459 static int
460 onbrd_port_create(di_minor_t minor, di_node_t node)
461 {
462 	char l_path[MAXPATHLEN], p_path[MAXPATHLEN];
463 	char  *devfspath, *buf, *minor_name;
464 
465 	devfspath = di_devfs_path(node);
466 	if (devfspath == NULL) {
467 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
468 		return (DEVFSADM_CONTINUE);
469 	}
470 
471 	if ((minor_name = di_minor_name(minor)) == NULL) {
472 		devfsadm_errprint("%s: NULL minor name\n\t%s\n",
473 		    modname, devfspath);
474 		di_devfs_path_free(devfspath);
475 		return (DEVFSADM_CONTINUE);
476 	}
477 
478 	/*
479 	 * verify dialout ports do not come in on this nodetype
480 	 */
481 	if (is_dialout(minor_name)) {
482 		devfsadm_errprint("%s: dialout device\n\t%s:%s\n", modname,
483 		    devfspath, minor_name);
484 		di_devfs_path_free(devfspath);
485 		return (DEVFSADM_CONTINUE);
486 	}
487 
488 	(void) strcpy(p_path, devfspath);
489 	(void) strcat(p_path, ":");
490 	(void) strcat(p_path, minor_name);
491 	di_devfs_path_free(devfspath);
492 
493 
494 	buf = NULL;
495 
496 #ifdef __i386
497 	buf = check_compat_ports(node, p_path, minor_name);
498 #endif
499 
500 	/*
501 	 * devfsadm_enumerate_char_start() is a private interface for use by the
502 	 * ports module only
503 	 */
504 	if (!buf && devfsadm_enumerate_char_start(p_path, 0, &buf, obport_rules,
505 	    1, start_id)) {
506 		devfsadm_errprint("%s: devfsadm_enumerate_char_start() failed"
507 		    "\n\t%s\n", modname, p_path);
508 		return (DEVFSADM_CONTINUE);
509 	}
510 
511 	(void) strcpy(l_path, "term/");
512 	(void) strcat(l_path, buf);
513 	(void) devfsadm_mklink(l_path, node, minor, 0);
514 	free(buf);
515 	return (DEVFSADM_CONTINUE);
516 }
517 
518 /*
519  * Onboard dialout devices
520  * Creates links of the form "/dev/cua/[a..z]"
521  */
522 static int
523 onbrd_dialout_create(di_minor_t minor, di_node_t node)
524 {
525 	char l_path[MAXPATHLEN], p_path[MAXPATHLEN];
526 	char  *devfspath, *buf, *mn;
527 
528 	devfspath = di_devfs_path(node);
529 	if (devfspath == NULL) {
530 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
531 		return (DEVFSADM_CONTINUE);
532 	}
533 
534 	if ((mn = di_minor_name(minor)) == NULL) {
535 		devfsadm_errprint("%s: NULL minor name\n\t%s\n",
536 		    modname, devfspath);
537 		di_devfs_path_free(devfspath);
538 		return (DEVFSADM_CONTINUE);
539 	}
540 
541 	/*
542 	 * verify this is a dialout port
543 	 */
544 	if (!is_dialout(mn)) {
545 		devfsadm_errprint("%s: not a dialout device\n\t%s:%s\n",
546 		    modname, devfspath, mn);
547 		di_devfs_path_free(devfspath);
548 		return (DEVFSADM_CONTINUE);
549 	}
550 
551 	(void) strcpy(p_path, devfspath);
552 	(void) strcat(p_path, ":");
553 	(void) strcat(p_path, mn);
554 	di_devfs_path_free(devfspath);
555 
556 	buf = NULL;
557 
558 #ifdef __i386
559 	buf = check_compat_ports(node, p_path, mn);
560 #endif
561 
562 	/*
563 	 * devfsadm_enumerate_char_start() is a private interface
564 	 * for use by the ports module only.
565 	 */
566 	if (!buf && devfsadm_enumerate_char_start(p_path, 0, &buf, obport_rules,
567 	    1, start_id)) {
568 		devfsadm_errprint("%s: devfsadm_enumerate_char_start() failed"
569 		    "\n\t%s\n", modname, p_path);
570 		return (DEVFSADM_CONTINUE);
571 	}
572 
573 	/*
574 	 * create the logical link
575 	 */
576 	(void) strcpy(l_path, "cua/");
577 	(void) strcat(l_path, buf);
578 	(void) devfsadm_mklink(l_path, node, minor, 0);
579 	free(buf);
580 	return (DEVFSADM_CONTINUE);
581 }
582 
583 
584 /*
585  * Remote System Controller (RSC) serial ports
586  * Creates links of the form "/dev/rsc-control" | "/dev/term/rsc-console".
587  */
588 static int
589 rsc_port_create(di_minor_t minor, di_node_t node)
590 {
591 	char  *devfspath;
592 	char  *minor_name;
593 
594 
595 	devfspath = di_devfs_path(node);
596 	if (devfspath == NULL) {
597 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
598 		return (DEVFSADM_CONTINUE);
599 	}
600 
601 	if ((minor_name = di_minor_name(minor)) == NULL) {
602 		devfsadm_errprint("%s: NULL minor name\n\t%s\n",
603 		    modname, devfspath);
604 		di_devfs_path_free(devfspath);
605 		return (DEVFSADM_CONTINUE);
606 	}
607 
608 	/*
609 	 * if this is the RSC console serial port (i.e. the minor name == ssp),
610 	 * create /dev/term/rsc-console link and then we are done with this
611 	 * node.
612 	 */
613 	if (strcmp(minor_name, "ssp") == 0) {
614 		(void) devfsadm_mklink("term/rsc-console", node, minor, 0);
615 		di_devfs_path_free(devfspath);
616 		return (DEVFSADM_TERMINATE);
617 
618 	/*
619 	 * else if this is the RSC control serial port (i.e. the minor name ==
620 	 * sspctl), create /dev/rsc-control link and then we are done with this
621 	 * node.
622 	 */
623 	} else if (strcmp(minor_name, "sspctl") == 0) {
624 		(void) devfsadm_mklink("rsc-control", node, minor, 0);
625 		di_devfs_path_free(devfspath);
626 		return (DEVFSADM_TERMINATE);
627 	}
628 
629 	/* This is not an RSC node, continue... */
630 	di_devfs_path_free(devfspath);
631 	return (DEVFSADM_CONTINUE);
632 }
633 
634 /*
635  * Lights Out Management (LOM) serial ports
636  * Creates links of the form "/dev/term/lom-console".
637  */
638 static int
639 lom_port_create(di_minor_t minor, di_node_t node)
640 {
641 	char  *devfspath;
642 	char  *minor_name;
643 
644 	devfspath = di_devfs_path(node);
645 	if (devfspath == NULL) {
646 		devfsadm_errprint("%s: di_devfs_path() failed\n", modname);
647 		return (DEVFSADM_CONTINUE);
648 	}
649 
650 	if ((minor_name = di_minor_name(minor)) == NULL) {
651 		devfsadm_errprint("%s: NULL minor name\n\t%s\n",
652 		    modname, devfspath);
653 		di_devfs_path_free(devfspath);
654 		return (DEVFSADM_CONTINUE);
655 	}
656 
657 	/*
658 	 * if this is the LOM console serial port (i.e. the minor
659 	 * name == lom-console ), create /dev/term/lom-console link and
660 	 * then we are done with this node.
661 	 */
662 	if (strcmp(minor_name, "lom-console") == 0) {
663 		(void) devfsadm_mklink("term/lom-console", node, minor, 0);
664 		di_devfs_path_free(devfspath);
665 		return (DEVFSADM_TERMINATE);
666 	}
667 
668 	/* This is not a LOM node, continue... */
669 	di_devfs_path_free(devfspath);
670 	return (DEVFSADM_CONTINUE);
671 }
672 
673 
674 /*
675  * Removes port entries that no longer have devices
676  * backing them
677  * Schedules an update the sacadm (portmon) database
678  */
679 static void
680 rm_dangling_port(char *devname)
681 {
682 	char *portstr;
683 	int  portnum;
684 
685 	devfsadm_print(PORT_MID, "%s:rm_stale_port: %s\n",
686 	    modname, devname);
687 
688 	if ((portstr = strrchr(devname, (int)'/')) == NULL) {
689 		devfsadm_errprint("%s: invalid name: %s\n",
690 		    modname, devname);
691 		return;
692 	}
693 	portstr++;
694 
695 	/*
696 	 * mark for removal from sacadm database
697 	 */
698 	if ((portnum = parse_portno(portstr)) != -1)
699 		pma[portnum].flags |= PORT_REMOVED;
700 
701 	devfsadm_rm_all(devname);
702 }
703 
704 /*
705  * Algorithm is to step through ports; checking for unneeded PM entries
706  * entries that should be there but are not.  Every PM_GRPSZ entries
707  * check to see if there are any entries for the port monitor group;
708  * if not, delete the group.
709  */
710 static void
711 update_sacadm_db(void)
712 {
713 	int i;
714 
715 	if (load_ttymondb() != DEVFSADM_SUCCESS)
716 		return;
717 
718 	for (i = 0; i < maxports; i++) {
719 		/*
720 		 * if this port was removed and has a port
721 		 * monitor entry, remove the entry from the sacadm db
722 		 */
723 		if ((pma[i].flags & PORT_REMOVED) != 0) {
724 			if ((pma[i].flags & PM_HAS_ENTRY) != 0)
725 				remove_pm_entry(pma[i].pm_tag, i);
726 		}
727 
728 		/*
729 		 * if this port is present and lacks a port monitor
730 		 * add an entry to the sacadm db
731 		 */
732 		if (pma[i].flags & HAS_PORT_DEVICE) {
733 			if (!(pma[i].flags & PM_HAS_ENTRY))
734 				add_pm_entry(i);
735 		}
736 
737 		/*
738 		 * if this port has a pm entry, mark as needing
739 		 * a port monitor within this range of ports
740 		 */
741 		if ((pma[i].flags & PM_HAS_ENTRY))
742 			pma[PM_SLOT(i)].flags |= PM_NEEDED;
743 
744 		/*
745 		 * continue for the range of ports per-portmon
746 		 */
747 		if (((i + 1) % PM_GRPSZ) != 0)
748 			continue;
749 
750 		/*
751 		 * if there are no ports active on the range we have
752 		 * just completed, remove the port monitor entry if
753 		 * it exists
754 		 */
755 		if ((pma[PM_SLOT(i)].flags & (PM_NEEDED | HAS_PORT_MON)) ==
756 		    HAS_PORT_MON) {
757 			delete_port_monitor(i);
758 		}
759 
760 	}
761 
762 	/*
763 	 * cleanup remaining port monitor, if active
764 	 */
765 	if ((i % PM_GRPSZ != 0) &&
766 	    ((pma[PM_SLOT(i)].flags & (PM_NEEDED | HAS_PORT_MON)) ==
767 	    HAS_PORT_MON)) {
768 		delete_port_monitor(i);
769 	}
770 }
771 
772 /*
773  * Determine which port monitor entries already exist by invoking pmadm(1m)
774  * to list all configured 'ttymon' port monitor entries.
775  * Do not explicitly report errors from executing pmadm(1m) or sacadm(1m)
776  * commands to remain compatible with the ports(1m) implementation.
777  */
778 static int
779 load_ttymondb(void)
780 {
781 	char	cmdline[CMDLEN];
782 	char	cmdbuf[PMTAB_MAXLINE+1];
783 	int	sac_exitval;
784 	FILE	*fs_popen;
785 	char	*portname;	/* pointer to a tty name */
786 	int	portnum;
787 	char	*ptr;
788 	char	*error_msg = "%s: failed to load port monitor database\n";
789 
790 	(void) strcpy(cmdline, "/usr/sbin/pmadm -L -t ttymon");
791 	fs_popen = popen(cmdline, "r");
792 	if (fs_popen == NULL) {
793 		devfsadm_print(VERBOSE_MID, error_msg, modname);
794 		return (DEVFSADM_FAILURE);
795 	}
796 
797 	while (fgets(cmdbuf, PMTAB_MAXLINE, fs_popen) != NULL) {
798 		if ((portname = pmtab_parse_portname(cmdbuf)) == NULL) {
799 			devfsadm_print(VERBOSE_MID,
800 			    "load_ttymondb: failed to parse portname\n");
801 			devfsadm_print(VERBOSE_MID,
802 			    "load_ttymondb: buffer \"%s\"\n", cmdbuf);
803 			goto load_failed;
804 		}
805 
806 		devfsadm_print(PORT_MID, "%s:load_ttymondb: port %s ",
807 		    modname, portname);
808 
809 		/*
810 		 * skip onboard ports
811 		 * There is no reliable way to determine if we
812 		 * should start a port monitor on these lines.
813 		 */
814 		if ((portnum = parse_portno(portname)) == -1) {
815 			devfsadm_print(PORT_MID, "ignored\n");
816 			continue;
817 		}
818 
819 		/*
820 		 * the first field of the pmadm output is
821 		 * the port monitor name for this entry
822 		 */
823 		if ((ptr = strchr(cmdbuf, PMTAB_SEPR)) == NULL) {
824 			devfsadm_print(VERBOSE_MID,
825 			    "load_ttymondb: no portmon tag\n");
826 			goto load_failed;
827 		}
828 
829 		*ptr = MN_NULLCHAR;
830 		if ((pma[portnum].pm_tag = strdup(cmdbuf)) == NULL) {
831 			devfsadm_errprint("load_ttymondb: failed strdup\n");
832 			goto load_failed;
833 		}
834 		pma[portnum].flags |= PM_HAS_ENTRY;
835 		pma[PM_SLOT(portnum)].flags |= HAS_PORT_MON;
836 		devfsadm_print(PORT_MID, "present\n");
837 	}
838 	(void) pclose(fs_popen);
839 	return (DEVFSADM_SUCCESS);
840 
841 load_failed:
842 
843 	/*
844 	 * failed to load the port monitor database
845 	 */
846 	devfsadm_print(VERBOSE_MID, error_msg, modname);
847 	sac_exitval = SAC_EXITVAL(pclose(fs_popen));
848 	if (sac_exitval != 0) {
849 		devfsadm_print(VERBOSE_MID,
850 		    "pmadm: (%s) %s\n", SAC_EID(sac_exitval),
851 		    SAC_EMSG(sac_exitval));
852 	}
853 	return (DEVFSADM_FAILURE);
854 }
855 
856 /*
857  * add a port monitor entry for device /dev/term/"port"
858  */
859 static void
860 add_pm_entry(int port)
861 {
862 	char cmdline[CMDLEN];
863 	int sac_exitval;
864 
865 	add_port_monitor(port);
866 	(void) sprintf(cmdline,
867 	    "/usr/sbin/pmadm -a -p ttymon%d -s %d -i root"
868 	    " -v `/usr/sbin/ttyadm -V` -fux -y\"/dev/term/%d\""
869 	    " -m \"`/usr/sbin/ttyadm -d /dev/term/%d -s /usr/bin/login"
870 	    " -l 9600 -p \\\"login: \\\"`\"", PM_NUM(port), port, port, port);
871 
872 	if (devfsadm_noupdate() == DEVFSADM_FALSE) {
873 		sac_exitval = execute(cmdline);
874 		if ((sac_exitval != 0) && (sac_exitval != E_SACNOTRUN)) {
875 			devfsadm_print(VERBOSE_MID,
876 			    "failed to add port monitor entry"
877 			    " for /dev/term/%d\n", port);
878 			devfsadm_print(VERBOSE_MID, "pmadm: (%s) %s\n",
879 			    SAC_EID(sac_exitval), SAC_EMSG(sac_exitval));
880 		}
881 	}
882 	pma[port].flags |= PM_HAS_ENTRY;
883 	devfsadm_print(VERBOSE_MID, "%s: /dev/term/%d added to sacadm\n",
884 	    modname, port);
885 }
886 
887 static void
888 remove_pm_entry(char *pmtag, int port)
889 {
890 
891 	char cmdline[CMDLEN];
892 	int sac_exitval;
893 
894 	if (devfsadm_noupdate() == DEVFSADM_FALSE) {
895 		(void) snprintf(cmdline, sizeof (cmdline),
896 		    "/usr/sbin/pmadm -r -p %s -s %d", pmtag, port);
897 		sac_exitval = execute(cmdline);
898 		if ((sac_exitval != 0) && (sac_exitval != E_SACNOTRUN)) {
899 			devfsadm_print(VERBOSE_MID,
900 			    "failed to remove port monitor entry"
901 			    " for /dev/term/%d\n", port);
902 			devfsadm_print(VERBOSE_MID, "pmadm: (%s) %s\n",
903 			    SAC_EID(sac_exitval), SAC_EMSG(sac_exitval));
904 		}
905 	}
906 	pma[port].flags &= ~PM_HAS_ENTRY;
907 	devfsadm_print(VERBOSE_MID, "%s: /dev/term/%d removed from sacadm\n",
908 	    modname, port);
909 }
910 
911 
912 /*
913  * delete_port_monitor()
914  * Check for the existence of a port monitor for "port" and remove it if
915  * one exists
916  */
917 static void
918 delete_port_monitor(int port)
919 {
920 	char	cmdline[CMDLEN];
921 	int	sac_exitval;
922 
923 	(void) sprintf(cmdline, "/usr/sbin/sacadm -L -p ttymon%d",
924 	    PM_NUM(port));
925 	sac_exitval = execute(cmdline);
926 
927 	/* clear the PM tag and return if the port monitor is not active */
928 	if (sac_exitval == E_NOEXIST) {
929 		pma[PM_SLOT(port)].flags &= ~HAS_PORT_MON;
930 		return;
931 	}
932 
933 	/* some other sacadm(1m) error, log and return */
934 	if (sac_exitval != 0) {
935 		devfsadm_print(VERBOSE_MID, "sacadm: (%s) %s\n",
936 		    SAC_EID(sac_exitval), SAC_EMSG(sac_exitval));
937 		return;
938 	}
939 
940 	if (devfsadm_noupdate() == DEVFSADM_FALSE) {
941 		(void) sprintf(cmdline,
942 		    "/usr/sbin/sacadm -r -p ttymon%d", PM_NUM(port));
943 		if (sac_exitval = execute(cmdline)) {
944 			devfsadm_print(VERBOSE_MID,
945 			    "failed to remove port monitor ttymon%d\n",
946 			    PM_NUM(port));
947 			devfsadm_print(VERBOSE_MID, "sacadm: (%s) %s\n",
948 			    SAC_EID(sac_exitval), SAC_EMSG(sac_exitval));
949 		}
950 	}
951 	devfsadm_print(VERBOSE_MID, "%s: port monitor ttymon%d removed\n",
952 	    modname, PM_NUM(port));
953 	pma[PM_SLOT(port)].flags &= ~HAS_PORT_MON;
954 }
955 
956 static void
957 add_port_monitor(int port)
958 {
959 	char cmdline[CMDLEN];
960 	int sac_exitval;
961 
962 	if ((pma[PM_SLOT(port)].flags & HAS_PORT_MON) != 0) {
963 		return;
964 	}
965 
966 	(void) sprintf(cmdline,
967 	    "/usr/sbin/sacadm -l -p ttymon%d", PM_NUM(port));
968 	sac_exitval = execute(cmdline);
969 	if (sac_exitval == E_NOEXIST) {
970 		(void) sprintf(cmdline,
971 		    "/usr/sbin/sacadm -a -n 2 -p ttymon%d -t ttymon"
972 		    " -c /usr/lib/saf/ttymon -v \"`/usr/sbin/ttyadm"
973 		    " -V`\" -y \"Ports %d-%d\"", PM_NUM(port), PM_SLOT(port),
974 		    PM_SLOT(port) + (PM_GRPSZ - 1));
975 		if (devfsadm_noupdate() == DEVFSADM_FALSE) {
976 			if (sac_exitval = execute(cmdline)) {
977 				devfsadm_print(VERBOSE_MID,
978 				    "failed to add port monitor ttymon%d\n",
979 				    PM_NUM(port));
980 				devfsadm_print(VERBOSE_MID, "sacadm: (%s) %s\n",
981 				    SAC_EID(sac_exitval),
982 				    SAC_EMSG(sac_exitval));
983 			}
984 		}
985 		devfsadm_print(VERBOSE_MID, "%s: port monitor ttymon%d added\n",
986 		    modname, PM_NUM(port));
987 	}
988 	pma[PM_SLOT(port)].flags |= HAS_PORT_MON;
989 }
990 
991 /*
992  * parse port number from string
993  * returns port number if in range [0..maxports]
994  */
995 static int
996 parse_portno(char *dname)
997 {
998 	int pn;
999 
1000 	if (sscanf(dname, "%d", &pn) != 1)
1001 		return (-1);
1002 
1003 	if ((pn < 0) || (pn > maxports)) {
1004 		devfsadm_print(VERBOSE_MID,
1005 		    "%s:parse_portno: %d not in range (0..%d)\n",
1006 		    modname, pn, maxports);
1007 		return (-1);
1008 	}
1009 
1010 	return (pn);
1011 }
1012 
1013 
1014 /*
1015  * fork and exec a command, waiting for the command to
1016  * complete and return it's status
1017  */
1018 static int
1019 execute(const char *s)
1020 {
1021 	int	status;
1022 	int	fd;
1023 	pid_t	pid;
1024 	pid_t	w;
1025 
1026 	/*
1027 	 * fork a single threaded child proc to execute the
1028 	 * sacadm command string
1029 	 */
1030 	devfsadm_print(PORT_MID, "%s: execute:\n\t%s\n", modname, s);
1031 	if ((pid = fork1()) == 0) {
1032 		(void) close(0);
1033 		(void) close(1);
1034 		(void) close(2);
1035 		fd = open("/dev/null", O_RDWR);
1036 		(void) dup(fd);
1037 		(void) dup(fd);
1038 		(void) execl("/sbin/sh", "sh", "-c", s, 0);
1039 		/*
1040 		 * return the sacadm exit status (see _exit(2))
1041 		 */
1042 		_exit(127);
1043 	}
1044 
1045 	/*
1046 	 * wait for child process to terminate
1047 	 */
1048 	for (;;) {
1049 		w = wait(&status);
1050 		if (w == pid) {
1051 			devfsadm_print(PORT_MID, "%s:exit status (%d)\n",
1052 			    modname, SAC_EXITVAL(status));
1053 			return (SAC_EXITVAL(status));
1054 		}
1055 		if (w == (pid_t)-1) {
1056 			devfsadm_print(VERBOSE_MID, "%s: exec failed\n",
1057 			    modname);
1058 			return (-1);
1059 		}
1060 	}
1061 
1062 	/* NOTREACHED */
1063 }
1064 
1065 
1066 /*
1067  * check if the minor name is suffixed with ",cu"
1068  */
1069 static int
1070 is_dialout(char *name)
1071 {
1072 	char *s_chr;
1073 
1074 	if ((name == NULL) || (s_chr = strrchr(name, MN_SEPR)) == NULL)
1075 		return (0);
1076 
1077 	if (strcmp(s_chr, DIALOUT_SUFFIX) == 0) {
1078 		return (1);
1079 	} else {
1080 		return (0);
1081 	}
1082 }
1083 
1084 
1085 /*
1086  * Get the name of the port device from a pmtab entry.
1087  * Note the /dev/term/ part is taken off.
1088  */
1089 static char *
1090 pmtab_parse_portname(char *buffer)
1091 {
1092 	int i;
1093 	char *bufp, *devnamep, *portnamep;
1094 
1095 	/*
1096 	 * position to the device name (field 8)
1097 	 */
1098 	bufp = strchr(buffer, PMTAB_SEPR);
1099 	for (i = 0; i < PMTAB_DEVNAME_FIELD; i++) {
1100 		if (bufp == NULL)
1101 			return (NULL);
1102 		bufp = strchr(++bufp, PMTAB_SEPR);
1103 	}
1104 
1105 	/* move past the ':' and locate the end of the devname */
1106 	devnamep = bufp++;
1107 	if ((bufp = strchr(bufp, PMTAB_SEPR)) == NULL)
1108 		return (NULL);
1109 
1110 	*bufp = MN_NULLCHAR;
1111 	if ((portnamep = strrchr(devnamep, DEVNAME_SEPR)) == NULL) {
1112 		*bufp = PMTAB_SEPR;
1113 		return (NULL);
1114 	}
1115 
1116 	/* return with "buffer" chopped after the /dev/term entry */
1117 
1118 	return (++portnamep);
1119 }
1120 
1121 /*
1122  * port monitor array mgmt
1123  */
1124 static void *
1125 pma_alloc(void)
1126 {
1127 
1128 	if (pma != NULL) {
1129 		devfsadm_errprint("%s:pma_alloc:pma != NULL\n", modname);
1130 		return (NULL);
1131 	}
1132 
1133 	if ((pma = calloc(maxports + 1, sizeof (*pma))) == NULL) {
1134 		devfsadm_errprint("%s:pma_alloc:pma alloc failure\n", modname);
1135 		return (NULL);
1136 	}
1137 
1138 	return ((void *)pma);
1139 }
1140 
1141 static void
1142 pma_free(void)
1143 {
1144 
1145 	int i;
1146 
1147 	if (pma == NULL)
1148 		return;
1149 
1150 	/*
1151 	 * free any strings we had allocated
1152 	 */
1153 	for (i = 0; i <= maxports; i++) {
1154 		if (pma[i].pm_tag != NULL)
1155 			free(pma[i].pm_tag);
1156 	}
1157 
1158 	free(pma);
1159 	pma = NULL;
1160 }
1161