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