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