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 2010 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*
28 * Device allocation related work.
29 */
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <errno.h>
34#include <string.h>
35#include <strings.h>
36#include <unistd.h>
37#include <fcntl.h>
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <sys/dkio.h>
41#include <sys/wait.h>
42#include <bsm/devalloc.h>
43
44#define	DEALLOCATE	 "/usr/sbin/deallocate"
45#define	MKDEVALLOC	"/usr/sbin/mkdevalloc"
46
47static char *_update_dev(deventry_t *, int, const char *, char *, char *);
48static int _make_db();
49extern int event_driven;
50
51
52/*
53 * _da_check_for_usb
54 *	returns 1 if device pointed by 'link' is a removable hotplugged disk,
55 *	else returns 0.
56 */
57int
58_da_check_for_usb(char *link, char *root_dir)
59{
60	int		fd = -1;
61	int		len, dstsize;
62	int		removable = 0;
63	int		hotpluggable = 0;
64	char		*p = NULL;
65	char		path[MAXPATHLEN + 4];
66	char		rpath[MAXPATHLEN + 4];		/* for ",raw" */
67
68	dstsize = sizeof (path);
69	if (strcmp(root_dir, "") != 0) {
70		if (strlcat(path, root_dir, dstsize) >= dstsize)
71			return (0);
72		len = strlen(path);
73	} else {
74		len = 0;
75	}
76	(void) snprintf(path, dstsize - len, "%s", link);
77	if ((p = realpath(path, rpath)) == NULL) {
78		p = path;
79	} else {
80		if (strstr(link, "rdsk")) {
81			p = rpath;
82		} else {
83			(void) snprintf(path, dstsize, "%s%s", rpath, ",raw");
84			p = path;
85		}
86	}
87	if ((fd = open(p, O_RDONLY | O_NONBLOCK)) < 0)
88		return (0);
89	(void) ioctl(fd, DKIOCREMOVABLE, &removable);
90	(void) ioctl(fd, DKIOCHOTPLUGGABLE, &hotpluggable);
91	(void) close(fd);
92
93	if (removable && hotpluggable)
94		return (1);
95
96	return (0);
97}
98
99/*
100 * _reset_devalloc
101 *	If device allocation is being turned on, creates device_allocate
102 *	device_maps if they do not exist.
103 *	Puts DEVICE_ALLOCATION=ON/OFF in device_allocate to indicate if
104 *	device allocation is on/off.
105 */
106void
107_reset_devalloc(int action)
108{
109	da_args	dargs;
110
111	if (action == DA_ON)
112		(void) _make_db();
113	else if ((action == DA_OFF) && (open(DEVALLOC, O_RDONLY) == -1))
114		return;
115
116	if (action == DA_ON)
117		dargs.optflag = DA_ON;
118	else if (action == DA_OFF)
119		dargs.optflag = DA_OFF | DA_ALLOC_ONLY;
120
121	dargs.rootdir = NULL;
122	dargs.devnames = NULL;
123	dargs.devinfo = NULL;
124
125	(void) da_update_device(&dargs);
126}
127
128/*
129 * _make_db
130 *	execs /usr/sbin/mkdevalloc to create device_allocate and
131 *	device_maps.
132 */
133static int
134_make_db()
135{
136	int	status;
137	pid_t	pid, wpid;
138
139	pid = vfork();
140	switch (pid) {
141	case -1:
142		return (1);
143	case 0:
144		if (execl(MKDEVALLOC, MKDEVALLOC, DA_IS_LABELED, NULL) == -1)
145			exit((errno == ENOENT) ? 0 : 1);
146		return (1);
147	default:
148		for (;;) {
149			wpid = waitpid(pid, &status, 0);
150			if (wpid == (pid_t)-1) {
151				if (errno == EINTR)
152					continue;
153				else
154					return (1);
155			} else {
156				break;
157			}
158		}
159		break;
160	}
161
162	return ((WIFEXITED(status) == 0) ? 1 : WEXITSTATUS(status));
163}
164
165
166/*
167 * _update_devalloc_db
168 * 	Forms allocatable device entries to be written to device_allocate and
169 *	device_maps.
170 *
171 *      Or finds the correct entry to remove, and removes it.
172 *
173 *    Note: devname is a /devices link in the REMOVE case.
174 */
175/* ARGSUSED */
176void
177_update_devalloc_db(devlist_t *devlist, int devflag, int action, char *devname,
178    char *root_dir)
179{
180	int		i;
181	deventry_t	*entry = NULL, *dentry = NULL;
182	char 		*typestring;
183	char 		*nickname;  /* typestring + instance */
184
185	if (action == DA_ADD) {
186		for (i = 0; i < DA_COUNT; i++) {
187			switch (i) {
188			case 0:
189				dentry = devlist->audio;
190				break;
191			case 1:
192				dentry = devlist->cd;
193				break;
194			case 2:
195				dentry = devlist->floppy;
196				break;
197			case 3:
198				dentry = devlist->tape;
199				break;
200			case 4:
201				dentry = devlist->rmdisk;
202				break;
203			default:
204				return;
205			}
206			if (dentry)
207				(void) _update_dev(dentry, action, NULL, NULL,
208				    NULL);
209		}
210	} else if (action == DA_REMOVE) {
211		if (devflag & DA_AUDIO) {
212			dentry = devlist->audio;
213			typestring = DA_AUDIO_TYPE;
214		} else if (devflag & DA_CD) {
215			dentry = devlist->cd;
216			typestring = DA_CD_TYPE;
217		} else if (devflag & DA_FLOPPY) {
218			dentry = devlist->floppy;
219			typestring = DA_FLOPPY_TYPE;
220		} else if (devflag & DA_TAPE) {
221			dentry = devlist->tape;
222			typestring = DA_TAPE_TYPE;
223		} else if (devflag & DA_RMDISK) {
224			dentry = devlist->rmdisk;
225			typestring = DA_RMDISK_TYPE;
226		} else
227			return;
228
229		if (event_driven) {
230			nickname = _update_dev(NULL, action, typestring, NULL,
231			    devname);
232
233			if (nickname != NULL) {
234				(void) da_rm_list_entry(devlist, devname,
235				    devflag, nickname);
236				free(nickname);
237			}
238			return;
239		}
240		/*
241		 * Not reached as of now, could be reached if devfsadm is
242		 * enhanced to clean up devalloc database more thoroughly.
243		 * Will not reliably match for event-driven removes
244		 */
245		for (entry = dentry; entry != NULL; entry = entry->next) {
246			if (strcmp(entry->devinfo.devname, devname) == 0)
247				break;
248		}
249		(void) _update_dev(entry, action, NULL, devname, NULL);
250	}
251}
252
253/*
254 *	_update_dev: Update device_allocate and/or device_maps files
255 *
256 *      If adding a device:
257 *	    dentry:	A linked list of allocatable devices
258 *	    action:	DA_ADD or DA_REMOVE
259 *	    devtype:	type of device linked list to update on removal
260 *	    devname:	short name (i.e. rmdisk5, cdrom0)  of device if known
261 *	    rm_link:	name of real /device from hot_cleanup
262 *
263 *	If the action is ADD or if the action is triggered by an event
264 *      from syseventd,  read the files FIRST and treat their data as
265 *      more-accurate than the dentry list, adjusting dentry contents if needed.
266 *
267 *	For DA_ADD, try to add each device in the list to the files.
268 *
269 *      If the action is DA_REMOVE and not a hotplug remove, adjust the files
270 *	as indicated by the linked list.
271 *
272 *	RETURNS:
273 *          If we successfully remove a device from the files,  returns
274 *          a char * to strdup'd devname of the device removed.
275 *
276 *	    The caller is responsible for freeing the return value.
277 *
278 *	NULL for all other cases, both success and failure.
279 *
280 */
281static char *
282_update_dev(deventry_t *dentry, int action, const char *devtype, char *devname,
283    char *rm_link)
284{
285	da_args		dargs;
286	deventry_t	newentry, *entry;
287	int status;
288
289	dargs.rootdir = NULL;
290	dargs.devnames = NULL;
291
292	if (event_driven)
293		dargs.optflag = DA_EVENT;
294	else
295		dargs.optflag = 0;
296
297	if (action == DA_ADD) {
298		dargs.optflag |= DA_ADD;
299		/*
300		 * Add Events do not have enough information to overrride the
301		 * existing file contents.
302		 */
303
304		for (entry = dentry; entry != NULL; entry = entry->next) {
305			dargs.devinfo = &(entry->devinfo);
306			(void) da_update_device(&dargs);
307		}
308	} else if (action == DA_REMOVE) {
309		dargs.optflag |= DA_REMOVE;
310		if (dentry) {
311			entry = dentry;
312		} else if (dargs.optflag & DA_EVENT) {
313			if (devname == NULL)
314				newentry.devinfo.devname = NULL;
315			else
316				newentry.devinfo.devname = strdup(devname);
317			newentry.devinfo.devtype = (char *)devtype;
318			newentry.devinfo.devauths =
319			    newentry.devinfo.devopts =
320			    newentry.devinfo.devexec = NULL;
321			newentry.devinfo.devlist = strdup(rm_link);
322			newentry.devinfo.instance = 0;
323			newentry.next = NULL;
324			entry = &newentry;
325		} else {
326			newentry.devinfo.devname = strdup(devname);
327			newentry.devinfo.devtype = (char *)devtype;
328			newentry.devinfo.devauths =
329			    newentry.devinfo.devexec =
330			    newentry.devinfo.devopts =
331			    newentry.devinfo.devlist = NULL;
332			newentry.devinfo.instance = 0;
333			newentry.next = NULL;
334			entry = &newentry;
335		}
336		dargs.devinfo = &(entry->devinfo);
337		/*
338		 * da_update_device will fill in entry devname if
339		 * event_driven is true and device is in the file
340		 */
341		status = da_update_device(&dargs);
342		if (event_driven)
343			if (newentry.devinfo.devlist != NULL)
344				free(newentry.devinfo.devlist);
345		if (status == 0)
346			return (dargs.devinfo->devname);
347		else free(dargs.devinfo->devname);
348	}
349	return (NULL);
350}
351