xref: /illumos-gate/usr/src/cmd/rmvolmgr/rmm_common.c (revision fec509a05ddbf645268fe2e537314def7d1b67c8)
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 <stdio.h>
29 #include <errno.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <stdarg.h>
33 #include <fcntl.h>
34 #include <libintl.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <ctype.h>
38 #include <sys/param.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/mnttab.h>
42 
43 #include <dbus/dbus.h>
44 #include <dbus/dbus-glib.h>
45 #include <dbus/dbus-glib-lowlevel.h>
46 #include <libhal.h>
47 #include <libhal-storage.h>
48 
49 #include "rmm_common.h"
50 
51 #define	RMM_PRINT_DEVICE_WIDTH	20
52 
53 extern int rmm_debug;
54 
55 static const char *action_strings[] = {
56 	"eject",
57 	"mount",
58 	"remount",
59 	"unmount",
60 	"clear_mounts",
61 	"closetray"
62 };
63 
64 
65 LibHalContext *
66 rmm_hal_init(LibHalDeviceAdded devadd_cb, LibHalDeviceRemoved devrem_cb,
67     LibHalDevicePropertyModified propmod_cb,
68     DBusError *error, rmm_error_t *rmm_error)
69 {
70 	DBusConnection	*dbus_conn;
71 	LibHalContext	*ctx;
72 	char		**devices;
73 	int		nr;
74 
75 	dbus_error_init(error);
76 
77 	/*
78 	 * setup D-Bus connection
79 	 */
80 	if (!(dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, error))) {
81 		dprintf("cannot get system bus: %s\n", rmm_strerror(error, -1));
82 		*rmm_error = RMM_EDBUS_CONNECT;
83 		return (NULL);
84 	}
85 	rmm_dbus_error_free(error);
86 
87 	dbus_connection_setup_with_g_main(dbus_conn, NULL);
88 	dbus_connection_set_exit_on_disconnect(dbus_conn, B_TRUE);
89 
90 	if ((ctx = libhal_ctx_new()) == NULL) {
91 		dprintf("libhal_ctx_new failed");
92 		*rmm_error = RMM_EHAL_CONNECT;
93 		return (NULL);
94 	}
95 
96 	libhal_ctx_set_dbus_connection(ctx, dbus_conn);
97 
98 	/*
99 	 * register callbacks
100 	 */
101 	if (devadd_cb != NULL) {
102 		libhal_ctx_set_device_added(ctx, devadd_cb);
103 	}
104 	if (devrem_cb != NULL) {
105 		libhal_ctx_set_device_removed(ctx, devrem_cb);
106 	}
107 	if (propmod_cb != NULL) {
108 		libhal_ctx_set_device_property_modified(ctx, propmod_cb);
109 		if (!libhal_device_property_watch_all(ctx, error)) {
110 			dprintf("property_watch_all failed %s",
111 			    rmm_strerror(error, -1));
112 			libhal_ctx_free(ctx);
113 			*rmm_error = RMM_EHAL_CONNECT;
114 			return (NULL);
115 		}
116 	}
117 
118 	if (!libhal_ctx_init(ctx, error)) {
119 		dprintf("libhal_ctx_init failed: %s", rmm_strerror(error, -1));
120 		libhal_ctx_free(ctx);
121 		*rmm_error = RMM_EHAL_CONNECT;
122 		return (NULL);
123 	}
124 	rmm_dbus_error_free(error);
125 
126 	/*
127 	 * The above functions do not guarantee that HAL is actually running.
128 	 * Check by invoking a method.
129 	 */
130 	if (!(devices = libhal_get_all_devices(ctx, &nr, error))) {
131 		dprintf("HAL is not running: %s", rmm_strerror(error, -1));
132 		libhal_ctx_shutdown(ctx, NULL);
133 		libhal_ctx_free(ctx);
134 		*rmm_error = RMM_EHAL_CONNECT;
135 		return (NULL);
136 	} else {
137 		rmm_dbus_error_free(error);
138 		libhal_free_string_array(devices);
139 	}
140 
141 	return (ctx);
142 }
143 
144 
145 void
146 rmm_hal_fini(LibHalContext *hal_ctx)
147 {
148 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
149 
150 	(void) dbus_connection_unref(dbus_conn);
151 	(void) libhal_ctx_free(hal_ctx);
152 }
153 
154 
155 /*
156  * find volume from any type of name, similar to the old media_findname()
157  * returns the LibHalDrive object and a list of LibHalVolume objects.
158  */
159 LibHalDrive *
160 rmm_hal_volume_find(LibHalContext *hal_ctx, const char *name, DBusError *error,
161     GSList **volumes)
162 {
163 	LibHalDrive	*drive;
164 	char		*p;
165 	char		lastc;
166 
167 	*volumes = NULL;
168 
169 	/* temporarily remove trailing slash */
170 	p = (char *)name + strlen(name) - 1;
171 	if (*p == '/') {
172 		lastc = *p;
173 		*p = '\0';
174 	} else {
175 		p = NULL;
176 	}
177 
178 	if (name[0] == '/') {
179 		if (((drive = rmm_hal_volume_findby(hal_ctx,
180 		    "info.udi", name, volumes)) != NULL) ||
181 		    ((drive = rmm_hal_volume_findby(hal_ctx,
182 		    "block.device", name, volumes)) != NULL) ||
183 		    ((drive = rmm_hal_volume_findby(hal_ctx,
184 		    "block.solaris.raw_device", name, volumes)) != NULL) ||
185 		    ((drive = rmm_hal_volume_findby(hal_ctx,
186 		    "volume.mount_point", name, volumes)) != NULL)) {
187 			goto out;
188 		} else {
189 			goto out;
190 		}
191 	}
192 
193 	/* try volume label */
194 	if ((drive = rmm_hal_volume_findby(hal_ctx,
195 	    "volume.label", name, volumes)) != NULL) {
196 		goto out;
197 	}
198 
199 	drive = rmm_hal_volume_findby_nickname(hal_ctx, name, volumes);
200 
201 out:
202 	if (p != NULL) {
203 		*p = lastc;
204 	}
205 	return (drive);
206 }
207 
208 /*
209  * find default volume. Returns volume pointer and name in 'name'.
210  */
211 LibHalDrive *
212 rmm_hal_volume_find_default(LibHalContext *hal_ctx, DBusError *error,
213     const char **name_out, GSList **volumes)
214 {
215 	LibHalDrive	*drive;
216 	static const char *names[] = { "floppy", "cdrom", "rmdisk" };
217 	int		i;
218 
219 	*volumes = NULL;
220 
221 	for (i = 0; i < NELEM(names); i++) {
222 		if ((drive = rmm_hal_volume_findby_nickname(hal_ctx,
223 		    names[i], volumes)) != NULL) {
224 			/*
225 			 * Skip floppy if it has no media.
226 			 * XXX might want to actually check for media
227 			 * every time instead of relying on volcheck.
228 			 */
229 			if ((strcmp(names[i], "floppy") != 0) ||
230 			    libhal_device_get_property_bool(hal_ctx,
231 			    libhal_drive_get_udi(drive),
232 			    "storage.removable.media_available", NULL)) {
233 				*name_out = names[i];
234 				break;
235 			}
236 		}
237 		rmm_dbus_error_free(error);
238 	}
239 
240 	return (drive);
241 }
242 
243 /*
244  * find volume by property=value
245  * returns the LibHalDrive object and a list of LibHalVolume objects.
246  * XXX add support for multiple properties, reduce D-Bus traffic
247  */
248 LibHalDrive *
249 rmm_hal_volume_findby(LibHalContext *hal_ctx, const char *property,
250     const char *value, GSList **volumes)
251 {
252 	DBusError	error;
253 	LibHalDrive	*drive = NULL;
254 	LibHalVolume	*v = NULL;
255 	char		**udis;
256 	int		num_udis;
257 	int		i;
258 
259 	*volumes = NULL;
260 
261 	dbus_error_init(&error);
262 
263 	/* get all devices with property=value */
264 	if ((udis = libhal_manager_find_device_string_match(hal_ctx, property,
265 	    value, &num_udis, &error)) == NULL) {
266 		rmm_dbus_error_free(&error);
267 		return (NULL);
268 	}
269 
270 	/* find volumes among these devices */
271 	for (i = 0; i < num_udis; i++) {
272 		rmm_dbus_error_free(&error);
273 		if (libhal_device_query_capability(hal_ctx, udis[i], "volume",
274 		    &error)) {
275 			v = libhal_volume_from_udi(hal_ctx, udis[i]);
276 			if (v != NULL) {
277 				*volumes = g_slist_prepend(*volumes, v);
278 			}
279 		}
280 	}
281 
282 	/* used prepend, preserve original order */
283 	if (*volumes != NULL) {
284 		*volumes = g_slist_reverse(*volumes);
285 
286 		v = (LibHalVolume *)(*volumes)->data;
287 		drive = libhal_drive_from_udi(hal_ctx,
288 		    libhal_volume_get_storage_device_udi(v));
289 		if (drive == NULL) {
290 			rmm_volumes_free (*volumes);
291 			*volumes = NULL;
292 		}
293 	}
294 
295 	libhal_free_string_array(udis);
296 	rmm_dbus_error_free(&error);
297 
298 	return (drive);
299 }
300 
301 static void
302 rmm_print_nicknames_one(LibHalDrive *d, LibHalVolume *v,
303     const char *device, char **drive_nicknames)
304 {
305 	const char	*volume_label = NULL;
306 	const char	*mount_point = NULL;
307 	boolean_t	comma;
308 	int		i;
309 
310 	(void) printf("%-*s ", RMM_PRINT_DEVICE_WIDTH, device);
311 	comma = B_FALSE;
312 
313 	if (drive_nicknames != NULL) {
314 		for (i = 0; drive_nicknames[i] != NULL; i++) {
315 			(void) printf("%s%s", comma ? "," : "",
316 			    drive_nicknames[i]);
317 			comma = B_TRUE;
318 		}
319 	}
320 
321 	if ((v != NULL) &&
322 	    ((volume_label = libhal_volume_get_label(v)) != NULL) &&
323 	    (strlen(volume_label) > 0)) {
324 		(void) printf("%s%s", comma ? "," : "", volume_label);
325 		comma = B_TRUE;
326 	}
327 
328 	if ((v != NULL) &&
329 	    ((mount_point = libhal_volume_get_mount_point(v)) != NULL) &&
330 	    (strlen(mount_point) > 0)) {
331 		(void) printf("%s%s", comma ? "," : "", mount_point);
332 		comma = B_TRUE;
333 	}
334 
335 	(void) printf("\n");
336 }
337 
338 /*
339  * print nicknames for each available volume
340  *
341  * print_mask:
342  *   RMM_PRINT_MOUNTABLE	print only mountable volumes
343  *   RMM_PRINT_EJECTABLE	print volume-less ejectable drives
344  */
345 void
346 rmm_print_volume_nicknames(LibHalContext *hal_ctx, DBusError *error,
347     int print_mask)
348 {
349 	char		**udis;
350 	int		num_udis;
351 	GSList		*volumes = NULL;
352 	LibHalDrive	*d, *d_tmp;
353 	LibHalVolume	*v;
354 	const char	*device;
355 	char		**nicknames;
356 	int		i;
357 	GSList		*j;
358 	int		nprinted;
359 
360 	dbus_error_init(error);
361 
362 	if ((udis = libhal_find_device_by_capability(hal_ctx, "storage",
363 	    &num_udis, error)) == NULL) {
364 		rmm_dbus_error_free(error);
365 		return;
366 	}
367 
368 	for (i = 0; i < num_udis; i++) {
369 		if ((d = libhal_drive_from_udi(hal_ctx, udis[i])) == NULL) {
370 			continue;
371 		}
372 
373 		/* find volumes belonging to this drive */
374 		if ((d_tmp = rmm_hal_volume_findby(hal_ctx,
375 		    "block.storage_device", udis[i], &volumes)) != NULL) {
376 			libhal_drive_free(d_tmp);
377 		}
378 
379 		nicknames = libhal_device_get_property_strlist(hal_ctx,
380 		    udis[i], "storage.solaris.nicknames", NULL);
381 
382 		nprinted = 0;
383 		for (j = volumes; j != NULL; j = g_slist_next(j)) {
384 			v = (LibHalVolume *)(j->data);
385 
386 			if ((device = libhal_volume_get_device_file(v)) ==
387 			    NULL) {
388 				continue;
389 			}
390 			if ((print_mask & RMM_PRINT_MOUNTABLE) &&
391 			    (libhal_volume_get_fsusage(v) !=
392 			    LIBHAL_VOLUME_USAGE_MOUNTABLE_FILESYSTEM)) {
393 				continue;
394 			}
395 
396 			rmm_print_nicknames_one(d, v, device, nicknames);
397 			nprinted++;
398 		}
399 
400 		if ((nprinted == 0) &&
401 		    (print_mask & RMM_PRINT_EJECTABLE) &&
402 		    libhal_drive_requires_eject(d) &&
403 		    ((device = libhal_drive_get_device_file(d)) != NULL)) {
404 			rmm_print_nicknames_one(d, NULL, device, nicknames);
405 		}
406 
407 		libhal_free_string_array(nicknames);
408 		libhal_drive_free(d);
409 		rmm_volumes_free(volumes);
410 		volumes = NULL;
411 	}
412 
413 	libhal_free_string_array(udis);
414 }
415 
416 /*
417  * find volume by nickname
418  * returns the LibHalDrive object and a list of LibHalVolume objects.
419  */
420 LibHalDrive *
421 rmm_hal_volume_findby_nickname(LibHalContext *hal_ctx, const char *name,
422     GSList **volumes)
423 {
424 	DBusError	error;
425 	LibHalDrive	*drive = NULL;
426 	LibHalDrive	*drive_tmp;
427 	char		**udis;
428 	int		num_udis;
429 	char		**nicknames;
430 	int		i, j;
431 
432 	*volumes = NULL;
433 
434 	dbus_error_init(&error);
435 
436 	if ((udis = libhal_find_device_by_capability(hal_ctx, "storage",
437 	    &num_udis, &error)) == NULL) {
438 		rmm_dbus_error_free(&error);
439 		return (NULL);
440 	}
441 
442 	/* find a drive by nickname */
443 	for (i = 0; (i < num_udis) && (drive == NULL); i++) {
444 		if ((nicknames = libhal_device_get_property_strlist(hal_ctx,
445 		    udis[i], "storage.solaris.nicknames", &error)) == NULL) {
446 			rmm_dbus_error_free(&error);
447 			continue;
448 		}
449 		for (j = 0; (nicknames[j] != NULL) && (drive == NULL); j++) {
450 			if (strcmp(nicknames[j], name) == 0) {
451 				drive = libhal_drive_from_udi(hal_ctx, udis[i]);
452 			}
453 		}
454 		libhal_free_string_array(nicknames);
455 	}
456 	libhal_free_string_array(udis);
457 
458 	if (drive != NULL) {
459 		/* found the drive, now find its volumes */
460 		if ((drive_tmp = rmm_hal_volume_findby(hal_ctx,
461 		    "block.storage_device", libhal_drive_get_udi(drive),
462 		    volumes)) != NULL) {
463 			libhal_drive_free(drive_tmp);
464 		}
465 	}
466 
467 	rmm_dbus_error_free(&error);
468 
469 	return (drive);
470 }
471 
472 void
473 rmm_volumes_free(GSList *volumes)
474 {
475 	GSList	*i;
476 
477 	for (i = volumes; i != NULL; i = g_slist_next(i)) {
478 		libhal_volume_free((LibHalVolume *)(i->data));
479 	}
480 	g_slist_free(volumes);
481 }
482 
483 /*
484  * Call HAL's Mount() method on the given device
485  */
486 boolean_t
487 rmm_hal_mount(LibHalContext *hal_ctx, const char *udi,
488     char **opts, int num_opts, char *mountpoint, DBusError *error)
489 {
490 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
491 	DBusMessage	*dmesg, *reply;
492 	char		*fstype;
493 
494 	dprintf("mounting %s...\n", udi);
495 
496 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal", udi,
497 	    "org.freedesktop.Hal.Device.Volume", "Mount"))) {
498 		dprintf(
499 		    "mount failed for %s: cannot create dbus message\n", udi);
500 		return (B_FALSE);
501 	}
502 
503 	fstype = "";
504 	if (mountpoint == NULL) {
505 		mountpoint = "";
506 	}
507 
508 	if (!dbus_message_append_args(dmesg, DBUS_TYPE_STRING, &mountpoint,
509 	    DBUS_TYPE_STRING, &fstype,
510 	    DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &opts, num_opts,
511 	    DBUS_TYPE_INVALID)) {
512 		dprintf("mount failed for %s: cannot append args\n", udi);
513 		dbus_message_unref(dmesg);
514 		return (B_FALSE);
515 	}
516 
517 	dbus_error_init(error);
518 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
519 	    dmesg, RMM_MOUNT_TIMEOUT, error))) {
520 		dprintf("mount failed for %s: %s\n", udi, error->message);
521 		dbus_message_unref(dmesg);
522 		return (B_FALSE);
523 	}
524 
525 	dprintf("mounted %s\n", udi);
526 
527 	dbus_message_unref(dmesg);
528 	dbus_message_unref(reply);
529 
530 	rmm_dbus_error_free(error);
531 
532 	return (B_TRUE);
533 }
534 
535 
536 /*
537  * Call HAL's Unmount() method on the given device
538  */
539 boolean_t
540 rmm_hal_unmount(LibHalContext *hal_ctx, const char *udi, DBusError *error)
541 {
542 	DBusConnection *dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
543 	DBusMessage *dmesg, *reply;
544 	char **opts = NULL;
545 
546 	dprintf("unmounting %s...\n", udi);
547 
548 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal", udi,
549 	    "org.freedesktop.Hal.Device.Volume", "Unmount"))) {
550 		dprintf(
551 		    "unmount failed %s: cannot create dbus message\n", udi);
552 		return (B_FALSE);
553 	}
554 
555 	if (!dbus_message_append_args(dmesg, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
556 	    &opts, 0, DBUS_TYPE_INVALID)) {
557 		dprintf("unmount failed %s: cannot append args\n", udi);
558 		dbus_message_unref(dmesg);
559 		return (B_FALSE);
560 	}
561 
562 	dbus_error_init(error);
563 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
564 	    dmesg, RMM_UNMOUNT_TIMEOUT, error))) {
565 		dprintf("unmount failed for %s: %s\n", udi, error->message);
566 		dbus_message_unref(dmesg);
567 		return (B_FALSE);
568 	}
569 
570 	dprintf("unmounted %s\n", udi);
571 
572 	dbus_message_unref(dmesg);
573 	dbus_message_unref(reply);
574 
575 	rmm_dbus_error_free(error);
576 
577 	return (B_TRUE);
578 }
579 
580 
581 /*
582  * Call HAL's Eject() method on the given device
583  */
584 boolean_t
585 rmm_hal_eject(LibHalContext *hal_ctx, const char *udi, DBusError *error)
586 {
587 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
588 	DBusMessage	*dmesg, *reply;
589 	char		**options = NULL;
590 	uint_t		num_options = 0;
591 
592 	dprintf("ejecting %s...\n", udi);
593 
594 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal", udi,
595 	    "org.freedesktop.Hal.Device.Storage", "Eject"))) {
596 		dprintf("eject %s: cannot create dbus message\n", udi);
597 		return (B_FALSE);
598 	}
599 
600 	if (!dbus_message_append_args(dmesg,
601 	    DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, num_options,
602 	    DBUS_TYPE_INVALID)) {
603 		dprintf("eject %s: cannot append args to dbus message ", udi);
604 		dbus_message_unref(dmesg);
605 		return (B_FALSE);
606 	}
607 
608 	dbus_error_init(error);
609 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
610 	    dmesg, RMM_EJECT_TIMEOUT, error))) {
611 		dprintf("eject %s: %s\n", udi, error->message);
612 		dbus_message_unref(dmesg);
613 		return (B_FALSE);
614 	}
615 
616 	dprintf("ejected %s\n", udi);
617 
618 	dbus_message_unref(dmesg);
619 	dbus_message_unref(reply);
620 
621 	rmm_dbus_error_free(error);
622 
623 	return (B_TRUE);
624 }
625 
626 /*
627  * Call HAL's CloseTray() method on the given device
628  */
629 boolean_t
630 rmm_hal_closetray(LibHalContext *hal_ctx, const char *udi, DBusError *error)
631 {
632 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
633 	DBusMessage	*dmesg, *reply;
634 	char		**options = NULL;
635 	uint_t		num_options = 0;
636 
637 	dprintf("closing tray %s...\n", udi);
638 
639 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal", udi,
640 	    "org.freedesktop.Hal.Device.Storage", "CloseTray"))) {
641 		dprintf(
642 		    "closetray failed for %s: cannot create dbus message\n",
643 		    udi);
644 		return (B_FALSE);
645 	}
646 
647 	if (!dbus_message_append_args(dmesg,
648 	    DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &options, num_options,
649 	    DBUS_TYPE_INVALID)) {
650 		dprintf("closetray %s: cannot append args to dbus message ",
651 		    udi);
652 		dbus_message_unref(dmesg);
653 		return (B_FALSE);
654 	}
655 
656 	dbus_error_init(error);
657 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
658 	    dmesg, RMM_CLOSETRAY_TIMEOUT, error))) {
659 		dprintf("closetray failed for %s: %s\n", udi, error->message);
660 		dbus_message_unref(dmesg);
661 		return (B_FALSE);
662 	}
663 
664 	dprintf("closetray ok %s\n", udi);
665 
666 	dbus_message_unref(dmesg);
667 	dbus_message_unref(reply);
668 
669 	rmm_dbus_error_free(error);
670 
671 	return (B_TRUE);
672 }
673 
674 /*
675  * Call HAL's Rescan() method on the given device
676  */
677 boolean_t
678 rmm_hal_rescan(LibHalContext *hal_ctx, const char *udi, DBusError *error)
679 {
680 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
681 	DBusMessage	*dmesg, *reply;
682 
683 	dprintf("rescanning %s...\n", udi);
684 
685 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal", udi,
686 	    "org.freedesktop.Hal.Device", "Rescan"))) {
687 		dprintf("rescan failed for %s: cannot create dbus message\n",
688 		    udi);
689 		return (B_FALSE);
690 	}
691 
692 	dbus_error_init(error);
693 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
694 	    dmesg, -1, error))) {
695 		dprintf("rescan failed for %s: %s\n", udi, error->message);
696 		dbus_message_unref(dmesg);
697 		return (B_FALSE);
698 	}
699 
700 	dprintf("rescan ok %s\n", udi);
701 
702 	dbus_message_unref(dmesg);
703 	dbus_message_unref(reply);
704 
705 	rmm_dbus_error_free(error);
706 
707 	return (B_TRUE);
708 }
709 
710 boolean_t
711 rmm_hal_claim_branch(LibHalContext *hal_ctx, const char *udi)
712 {
713 	DBusError error;
714 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
715 	DBusMessage *dmesg, *reply;
716 	const char *claimed_by = "rmvolmgr";
717 
718 	dprintf("claiming branch %s...\n", udi);
719 
720 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal",
721 	    "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager",
722 	    "ClaimBranch"))) {
723 		dprintf("cannot create dbus message\n");
724 		return (B_FALSE);
725 	}
726 
727 	if (!dbus_message_append_args(dmesg, DBUS_TYPE_STRING, &udi,
728 	    DBUS_TYPE_STRING, &claimed_by, DBUS_TYPE_INVALID)) {
729 		dprintf("cannot append args to dbus message\n");
730 		dbus_message_unref(dmesg);
731 		return (B_FALSE);
732 	}
733 
734 	dbus_error_init(&error);
735 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
736 	    dmesg, -1, &error))) {
737 		dprintf("cannot send dbus message\n");
738 		dbus_message_unref(dmesg);
739 		rmm_dbus_error_free(&error);
740 		return (B_FALSE);
741 	}
742 
743 	dprintf("claim branch ok %s\n", udi);
744 
745 	dbus_message_unref(dmesg);
746 	dbus_message_unref(reply);
747 
748 	return (B_TRUE);
749 }
750 
751 boolean_t
752 rmm_hal_unclaim_branch(LibHalContext *hal_ctx, const char *udi)
753 {
754 	DBusError error;
755 	DBusConnection	*dbus_conn = libhal_ctx_get_dbus_connection(hal_ctx);
756 	DBusMessage *dmesg, *reply;
757 	const char *claimed_by = "rmvolmgr";
758 
759 	dprintf("unclaiming branch %s...\n", udi);
760 
761 	if (!(dmesg = dbus_message_new_method_call("org.freedesktop.Hal",
762 	    "/org/freedesktop/Hal/Manager", "org.freedesktop.Hal.Manager",
763 	    "UnclaimBranch"))) {
764 		dprintf("cannot create dbus message\n");
765 		return (B_FALSE);
766 	}
767 
768 	if (!dbus_message_append_args(dmesg, DBUS_TYPE_STRING, &udi,
769 	    DBUS_TYPE_STRING, &claimed_by, DBUS_TYPE_INVALID)) {
770 		dprintf("cannot append args to dbus message\n");
771 		dbus_message_unref(dmesg);
772 		return (B_FALSE);
773 	}
774 
775 	dbus_error_init(&error);
776 	if (!(reply = dbus_connection_send_with_reply_and_block(dbus_conn,
777 	    dmesg, -1, &error))) {
778 		dprintf("cannot send dbus message\n");
779 		dbus_message_unref(dmesg);
780 		rmm_dbus_error_free(&error);
781 		return (B_FALSE);
782 	}
783 
784 	dprintf("unclaim branch ok %s\n", udi);
785 
786 	dbus_message_unref(dmesg);
787 	dbus_message_unref(reply);
788 
789 	return (B_TRUE);
790 }
791 
792 static boolean_t
793 rmm_action_one(LibHalContext *hal_ctx, const char *name, action_t action,
794     const char *dev, const char *udi, LibHalVolume *v,
795     char **opts, int num_opts, char *mountpoint)
796 {
797 	char		dev_str[MAXPATHLEN];
798 	char		*mountp;
799 	DBusError	error;
800 	boolean_t	ret = B_FALSE;
801 
802 	if (strcmp(name, dev) == 0) {
803 		(void) snprintf(dev_str, sizeof (dev_str), name);
804 	} else {
805 		(void) snprintf(dev_str, sizeof (dev_str), "%s %s", name, dev);
806 	}
807 
808 	dbus_error_init(&error);
809 
810 	switch (action) {
811 	case EJECT:
812 		ret = rmm_hal_eject(hal_ctx, udi, &error);
813 		break;
814 	case INSERT:
815 	case REMOUNT:
816 		if (libhal_volume_is_mounted(v)) {
817 			goto done;
818 		}
819 		ret = rmm_hal_mount(hal_ctx, udi,
820 		    opts, num_opts, mountpoint, &error);
821 		break;
822 	case UNMOUNT:
823 		if (!libhal_volume_is_mounted(v)) {
824 			goto done;
825 		}
826 		ret = rmm_hal_unmount(hal_ctx, udi, &error);
827 		break;
828 	case CLOSETRAY:
829 		ret = rmm_hal_closetray(hal_ctx, udi, &error);
830 		break;
831 	}
832 
833 	if (!ret) {
834 		(void) fprintf(stderr, gettext("%s of %s failed: %s\n"),
835 		    action_strings[action], dev_str, rmm_strerror(&error, -1));
836 		goto done;
837 	}
838 
839 	switch (action) {
840 	case EJECT:
841 		(void) printf(gettext("%s ejected\n"), dev_str);
842 		break;
843 	case INSERT:
844 	case REMOUNT:
845 		mountp = rmm_get_mnttab_mount_point(dev);
846 		if (mountp != NULL) {
847 			(void) printf(gettext("%s mounted at %s\n"),
848 			    dev_str, mountp);
849 			free(mountp);
850 		}
851 		break;
852 	case UNMOUNT:
853 		(void) printf(gettext("%s unmounted\n"), dev_str);
854 		break;
855 	case CLOSETRAY:
856 		(void) printf(gettext("%s tray closed\n"), dev_str);
857 		break;
858 	}
859 
860 done:
861 	rmm_dbus_error_free(&error);
862 	return (ret);
863 }
864 
865 /*
866  * top level action routine
867  *
868  * If non-null 'aa' is passed, it will be used, otherwise a local copy
869  * will be created.
870  */
871 boolean_t
872 rmm_action(LibHalContext *hal_ctx, const char *name, action_t action,
873     struct action_arg *aap, char **opts, int num_opts, char *mountpoint)
874 {
875 	DBusError	error;
876 	GSList		*volumes, *i;
877 	LibHalDrive	*d;
878 	LibHalVolume	*v;
879 	const char	*udi, *d_udi;
880 	const char	*dev, *d_dev;
881 	struct action_arg aa_local;
882 	boolean_t	ret = B_FALSE;
883 
884 	dprintf("rmm_action %s %s\n", name, action_strings[action]);
885 
886 	if (aap == NULL) {
887 		bzero(&aa_local, sizeof (aa_local));
888 		aap = &aa_local;
889 	}
890 
891 	dbus_error_init(&error);
892 
893 	/* find the drive and its volumes */
894 	d = rmm_hal_volume_find(hal_ctx, name, &error, &volumes);
895 	rmm_dbus_error_free(&error);
896 	if (d == NULL) {
897 		(void) fprintf(stderr, gettext("cannot find '%s'\n"), name);
898 		return (B_FALSE);
899 	}
900 	d_udi = libhal_drive_get_udi(d);
901 	d_dev = libhal_drive_get_device_file(d);
902 	if ((d_udi == NULL) || (d_dev == NULL)) {
903 		goto out;
904 	}
905 
906 	/*
907 	 * For those drives that do not require media eject,
908 	 * EJECT turns into UNMOUNT.
909 	 */
910 	if ((action == EJECT) && !libhal_drive_requires_eject(d)) {
911 		action = UNMOUNT;
912 	}
913 
914 	/* per drive action */
915 	if ((action == EJECT) || (action == CLOSETRAY)) {
916 		ret = rmm_action_one(hal_ctx, name, action, d_dev, d_udi, NULL,
917 		    opts, num_opts, NULL);
918 
919 		if (!ret || (action == CLOSETRAY)) {
920 			goto out;
921 		}
922 	}
923 
924 	/* per volume action */
925 	for (i = volumes; i != NULL; i = g_slist_next(i)) {
926 		v = (LibHalVolume *)i->data;
927 		udi = libhal_volume_get_udi(v);
928 		dev = libhal_volume_get_device_file(v);
929 
930 		if ((udi == NULL) || (dev == NULL)) {
931 			continue;
932 		}
933 		if (aap == &aa_local) {
934 			if (!rmm_volume_aa_from_prop(hal_ctx, udi, v, aap)) {
935 				dprintf("rmm_volume_aa_from_prop failed %s\n",
936 				    udi);
937 				continue;
938 			}
939 		}
940 		aap->aa_action = action;
941 
942 		/* ejected above, just need postprocess */
943 		if (action != EJECT) {
944 			ret = rmm_action_one(hal_ctx, name, action, dev, udi, v,
945 			    opts, num_opts, mountpoint);
946 		}
947 		if (ret) {
948 			(void) vold_postprocess(hal_ctx, udi, aap);
949 		}
950 
951 		libhal_volume_free(v);
952 		if (aap == &aa_local) {
953 			rmm_volume_aa_free(aap);
954 		}
955 	}
956 
957 out:
958 	g_slist_free(volumes);
959 	libhal_drive_free(d);
960 
961 	return (ret);
962 }
963 
964 
965 /*
966  * rescan by name
967  * if name is NULL, rescan all drives
968  */
969 boolean_t
970 rmm_rescan(LibHalContext *hal_ctx, const char *name, boolean_t query)
971 {
972 	DBusError	error;
973 	GSList		*volumes;
974 	LibHalDrive	*drive = NULL;
975 	const char	*drive_udi;
976 	char		**udis;
977 	int		num_udis;
978 	char		*nickname;
979 	char		**nicks = NULL;
980 	boolean_t	do_free_udis = FALSE;
981 	int		i;
982 	boolean_t	ret = B_FALSE;
983 
984 	dprintf("rmm_rescan %s\n", name != NULL ? name : "all");
985 
986 	dbus_error_init(&error);
987 
988 	if (name != NULL) {
989 		if ((drive = rmm_hal_volume_find(hal_ctx, name, &error,
990 		    &volumes)) == NULL) {
991 			rmm_dbus_error_free(&error);
992 			(void) fprintf(stderr,
993 			    gettext("cannot find '%s'\n"), name);
994 			return (B_FALSE);
995 		}
996 		rmm_dbus_error_free(&error);
997 		g_slist_free(volumes);
998 
999 		drive_udi = libhal_drive_get_udi(drive);
1000 		udis = (char **)&drive_udi;
1001 		num_udis = 1;
1002 	} else {
1003 		if ((udis = libhal_find_device_by_capability(hal_ctx,
1004 		    "storage", &num_udis, &error)) == NULL) {
1005 			rmm_dbus_error_free(&error);
1006 			return (B_TRUE);
1007 		}
1008 		rmm_dbus_error_free(&error);
1009 		do_free_udis = TRUE;
1010 	}
1011 
1012 	for (i = 0; i < num_udis; i++) {
1013 		if (name == NULL) {
1014 			nicks = libhal_device_get_property_strlist(hal_ctx,
1015 			    udis[i], "storage.solaris.nicknames", NULL);
1016 			if (nicks != NULL) {
1017 				nickname = nicks[0];
1018 			} else {
1019 				nickname = "";
1020 			}
1021 		}
1022 		if (!(ret = rmm_hal_rescan(hal_ctx, udis[i], &error))) {
1023 			(void) fprintf(stderr,
1024 			    gettext("rescan of %s failed: %s\n"),
1025 			    name ? name : nickname,
1026 			    rmm_strerror(&error, -1));
1027 			libhal_free_string_array(nicks);
1028 			continue;
1029 		}
1030 		if (query) {
1031 			ret = libhal_device_get_property_bool(hal_ctx, udis[i],
1032 			    "storage.removable.media_available", NULL);
1033 			if (ret) {
1034 				printf(gettext("%s is available\n"),
1035 				    name ? name : nickname);
1036 			} else {
1037 				printf(gettext("%s is not available\n"),
1038 				    name ? name : nickname);
1039 			}
1040 		}
1041 		libhal_free_string_array(nicks);
1042 	}
1043 
1044 	if (drive != NULL) {
1045 		libhal_drive_free(drive);
1046 	}
1047 	if (do_free_udis) {
1048 		libhal_free_string_array(udis);
1049 	}
1050 
1051 	return (ret);
1052 }
1053 
1054 
1055 /*
1056  * set action_arg from volume properties
1057  */
1058 boolean_t
1059 rmm_volume_aa_from_prop(LibHalContext *hal_ctx, const char *udi_arg,
1060     LibHalVolume *volume_arg, struct action_arg *aap)
1061 {
1062 	LibHalVolume	*volume = volume_arg;
1063 	const char	*udi = udi_arg;
1064 	const char	*drive_udi;
1065 	char		*volume_label;
1066 	char		*mountpoint;
1067 	int		len;
1068 	int		ret = B_FALSE;
1069 
1070 	/* at least udi or volume must be supplied */
1071 	if ((udi == NULL) && (volume == NULL)) {
1072 		return (B_FALSE);
1073 	}
1074 	if (volume == NULL) {
1075 		if ((volume = libhal_volume_from_udi(hal_ctx, udi)) == NULL) {
1076 			dprintf("cannot get volume %s\n", udi);
1077 			goto out;
1078 		}
1079 	}
1080 	if (udi == NULL) {
1081 		if ((udi = libhal_volume_get_udi(volume)) == NULL) {
1082 			dprintf("cannot get udi\n");
1083 			goto out;
1084 		}
1085 	}
1086 	drive_udi = libhal_volume_get_storage_device_udi(volume);
1087 
1088 	if (!(aap->aa_symdev = libhal_device_get_property_string(hal_ctx,
1089 	    drive_udi, "storage.solaris.legacy.symdev", NULL))) {
1090 		dprintf("property %s not found %s\n",
1091 		    "storage.solaris.legacy.symdev", drive_udi);
1092 		goto out;
1093 	}
1094 	if (!(aap->aa_media = libhal_device_get_property_string(hal_ctx,
1095 	    drive_udi, "storage.solaris.legacy.media_type", NULL))) {
1096 		dprintf("property %s not found %s\n",
1097 		    "storage.solaris.legacy.media_type", drive_udi);
1098 		goto out;
1099 	}
1100 
1101 	/* name is derived from volume label */
1102 	aap->aa_name = NULL;
1103 	if ((volume_label = (char *)libhal_device_get_property_string(hal_ctx,
1104 	    udi, "volume.label", NULL)) != NULL) {
1105 		if ((len = strlen(volume_label)) > 0) {
1106 			aap->aa_name = rmm_vold_convert_volume_label(
1107 			    volume_label, len);
1108 			if (strlen(aap->aa_name) == 0) {
1109 				free(aap->aa_name);
1110 				aap->aa_name = NULL;
1111 			}
1112 		}
1113 		libhal_free_string(volume_label);
1114 	}
1115 	/* if no label, then unnamed_<mediatype> */
1116 	if (aap->aa_name == NULL) {
1117 		aap->aa_name = (char *)calloc(1, sizeof ("unnamed_floppyNNNN"));
1118 		if (aap->aa_name == NULL) {
1119 			goto out;
1120 		}
1121 		(void) snprintf(aap->aa_name, sizeof ("unnamed_floppyNNNN"),
1122 		    "unnamed_%s", aap->aa_media);
1123 	}
1124 
1125 	if (!(aap->aa_path = libhal_device_get_property_string(hal_ctx, udi,
1126 	    "block.device", NULL))) {
1127 		dprintf("property %s not found %s\n", "block.device", udi);
1128 		goto out;
1129 	}
1130 	if (!(aap->aa_rawpath = libhal_device_get_property_string(hal_ctx, udi,
1131 	    "block.solaris.raw_device", NULL))) {
1132 		dprintf("property %s not found %s\n",
1133 		    "block.solaris.raw_device", udi);
1134 		goto out;
1135 	}
1136 	if (!(aap->aa_type = libhal_device_get_property_string(hal_ctx, udi,
1137 	    "volume.fstype", NULL))) {
1138 		dprintf("property %s not found %s\n", "volume.fstype", udi);
1139 		goto out;
1140 	}
1141 	if (!libhal_device_get_property_bool(hal_ctx, udi,
1142 	    "volume.is_partition", NULL)) {
1143 		aap->aa_partname = NULL;
1144 	} else if (!(aap->aa_partname = libhal_device_get_property_string(
1145 	    hal_ctx, udi, "block.solaris.slice", NULL))) {
1146 		dprintf("property %s not found %s\n",
1147 		    "block.solaris.slice", udi);
1148 		goto out;
1149 	}
1150 	if (!(mountpoint = libhal_device_get_property_string(hal_ctx, udi,
1151 	    "volume.mount_point", NULL))) {
1152 		dprintf("property %s not found %s\n",
1153 		    "volume.mount_point", udi);
1154 		goto out;
1155 	}
1156 	/*
1157 	 * aa_mountpoint can be reallocated in rmm_volume_aa_update_mountpoint()
1158 	 * won't have to choose between free() or libhal_free_string() later on
1159 	 */
1160 	aap->aa_mountpoint = strdup(mountpoint);
1161 	libhal_free_string(mountpoint);
1162 	if (aap->aa_mountpoint == NULL) {
1163 		dprintf("mountpoint is NULL %s\n", udi);
1164 		goto out;
1165 	}
1166 
1167 	ret = B_TRUE;
1168 
1169 out:
1170 	if ((volume != NULL) && (volume != volume_arg)) {
1171 		libhal_volume_free(volume);
1172 	}
1173 	if (!ret) {
1174 		rmm_volume_aa_free(aap);
1175 	}
1176 	return (ret);
1177 }
1178 
1179 /* ARGSUSED */
1180 void
1181 rmm_volume_aa_update_mountpoint(LibHalContext *hal_ctx, const char *udi,
1182     struct action_arg *aap)
1183 {
1184 	if (aap->aa_mountpoint != NULL) {
1185 		free(aap->aa_mountpoint);
1186 	}
1187 	aap->aa_mountpoint = rmm_get_mnttab_mount_point(aap->aa_path);
1188 }
1189 
1190 void
1191 rmm_volume_aa_free(struct action_arg *aap)
1192 {
1193 	if (aap->aa_symdev != NULL) {
1194 		libhal_free_string(aap->aa_symdev);
1195 		aap->aa_symdev = NULL;
1196 	}
1197 	if (aap->aa_name != NULL) {
1198 		free(aap->aa_name);
1199 		aap->aa_name = NULL;
1200 	}
1201 	if (aap->aa_path != NULL) {
1202 		libhal_free_string(aap->aa_path);
1203 		aap->aa_path = NULL;
1204 	}
1205 	if (aap->aa_rawpath != NULL) {
1206 		libhal_free_string(aap->aa_rawpath);
1207 		aap->aa_rawpath = NULL;
1208 	}
1209 	if (aap->aa_type != NULL) {
1210 		libhal_free_string(aap->aa_type);
1211 		aap->aa_type = NULL;
1212 	}
1213 	if (aap->aa_media != NULL) {
1214 		libhal_free_string(aap->aa_media);
1215 		aap->aa_media = NULL;
1216 	}
1217 	if (aap->aa_partname != NULL) {
1218 		libhal_free_string(aap->aa_partname);
1219 		aap->aa_partname = NULL;
1220 	}
1221 	if (aap->aa_mountpoint != NULL) {
1222 		free(aap->aa_mountpoint);
1223 		aap->aa_mountpoint = NULL;
1224 	}
1225 }
1226 
1227 /*
1228  * get device's mount point from mnttab
1229  */
1230 char *
1231 rmm_get_mnttab_mount_point(const char *special)
1232 {
1233 	char		*mount_point = NULL;
1234 	FILE		*f;
1235 	struct mnttab	mnt;
1236 	struct mnttab	mpref = { NULL, NULL, NULL, NULL, NULL };
1237 
1238 	if ((f = fopen(MNTTAB, "r")) != NULL) {
1239 		mpref.mnt_special = (char *)special;
1240 		if (getmntany(f, &mnt, &mpref) == 0) {
1241 			mount_point = strdup(mnt.mnt_mountp);
1242 		}
1243 		fclose(f);
1244 	}
1245 
1246 	return (mount_point);
1247 }
1248 
1249 
1250 /*
1251  * get human readable string from error values
1252  */
1253 const char *
1254 rmm_strerror(DBusError *dbus_error, int rmm_error)
1255 {
1256 	const char	*str;
1257 
1258 	if ((dbus_error != NULL) && dbus_error_is_set(dbus_error)) {
1259 		str = dbus_error->message;
1260 	} else {
1261 		switch (rmm_error) {
1262 		case RMM_EOK:
1263 			str = gettext("success");
1264 			break;
1265 		case RMM_EDBUS_CONNECT:
1266 			str = gettext("cannot connect to D-Bus");
1267 			break;
1268 		case RMM_EHAL_CONNECT:
1269 			str = gettext("cannot connect to HAL");
1270 			break;
1271 		default:
1272 			str = gettext("undefined error");
1273 			break;
1274 		}
1275 	}
1276 
1277 	return (str);
1278 }
1279 
1280 void
1281 rmm_dbus_error_free(DBusError *error)
1282 {
1283 	if (error != NULL && dbus_error_is_set(error)) {
1284 		dbus_error_free(error);
1285 	}
1286 }
1287 
1288 static int
1289 rmm_vold_isbadchar(int c)
1290 {
1291 	int	ret_val = 0;
1292 
1293 
1294 	switch (c) {
1295 	case '/':
1296 	case ';':
1297 	case '|':
1298 		ret_val = 1;
1299 		break;
1300 	default:
1301 		if (iscntrl(c) || isspace(c)) {
1302 			ret_val = 1;
1303 		}
1304 	}
1305 
1306 	return (ret_val);
1307 }
1308 
1309 char *
1310 rmm_vold_convert_volume_label(const char *name, size_t len)
1311 {
1312 	char	buf[MAXNAMELEN+1];
1313 	char	*s = buf;
1314 	int	i;
1315 
1316 	if (len > MAXNAMELEN) {
1317 		len = MAXNAMELEN;
1318 	}
1319 
1320 	for (i = 0; i < len; i++) {
1321 		if (name[i] == '\0') {
1322 			break;
1323 		}
1324 		if (isgraph((int)name[i])) {
1325 			if (isupper((int)name[i])) {
1326 				*s++ = tolower((int)name[i]);
1327 			} else if (rmm_vold_isbadchar((int)name[i])) {
1328 				*s++ = '_';
1329 			} else {
1330 				*s++ = name[i];
1331 			}
1332 		}
1333 	}
1334 	*s = '\0';
1335 	s = strdup(buf);
1336 
1337 	return (s);
1338 }
1339 
1340 /*
1341  * swiped from mkdir.c
1342  */
1343 int
1344 makepath(char *dir, mode_t mode)
1345 {
1346 	int		err;
1347 	char		*slash;
1348 
1349 
1350 	if ((mkdir(dir, mode) == 0) || (errno == EEXIST)) {
1351 		return (0);
1352 	}
1353 	if (errno != ENOENT) {
1354 		return (-1);
1355 	}
1356 	if ((slash = strrchr(dir, '/')) == NULL) {
1357 		return (-1);
1358 	}
1359 	*slash = '\0';
1360 	err = makepath(dir, mode);
1361 	*slash++ = '/';
1362 
1363 	if (err || (*slash == '\0')) {
1364 		return (err);
1365 	}
1366 
1367 	return (mkdir(dir, mode));
1368 }
1369 
1370 
1371 void
1372 dprintf(const char *fmt, ...)
1373 {
1374 
1375 	va_list		ap;
1376 	const char	*p;
1377 	char		msg[BUFSIZ];
1378 	char		*errmsg = strerror(errno);
1379 	char		*s;
1380 
1381 	if (rmm_debug == 0) {
1382 		return;
1383 	}
1384 
1385 	(void) memset(msg, 0, BUFSIZ);
1386 
1387 	/* scan for %m and replace with errno msg */
1388 	s = &msg[strlen(msg)];
1389 	p = fmt;
1390 
1391 	while (*p != '\0') {
1392 		if ((*p == '%') && (*(p+1) == 'm')) {
1393 			(void) strcat(s, errmsg);
1394 			p += 2;
1395 			s += strlen(errmsg);
1396 			continue;
1397 		}
1398 		*s++ = *p++;
1399 	}
1400 	*s = '\0';	/* don't forget the null byte */
1401 
1402 	va_start(ap, fmt);
1403 	(void) vfprintf(stderr, msg, ap);
1404 	va_end(ap);
1405 }
1406