xref: /titanic_50/usr/src/cmd/hal/probing/volume/probe-volume.c (revision ba7866cd2cbdf574f47d4e38a1301b90744dd677)
1 /***************************************************************************
2  *
3  * probe-volume.c : probe volumes
4  *
5  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
6  *
7  * Licensed under the Academic Free License version 2.1
8  *
9  **************************************************************************/
10 
11 #ifdef HAVE_CONFIG_H
12 #  include <config.h>
13 #endif
14 
15 #include <errno.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <sys/ioctl.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 #include <time.h>
26 #include <sys/time.h>
27 #include <sys/dkio.h>
28 #include <sys/cdio.h>
29 #include <sys/fdio.h>
30 #include <libnvpair.h>
31 #include <libfstyp.h>
32 #include <sys/vtoc.h>
33 #include <sys/efi_partition.h>
34 #include <sys/fs/hsfs_spec.h>
35 #include <sys/fs/hsfs_isospec.h>
36 #include <priv.h>
37 #include <sys/u8_textprep.h>
38 
39 #include <libhal.h>
40 #include <cdutils.h>
41 #include <fsutils.h>
42 #include <logger.h>
43 
44 static void
45 my_dbus_error_free(DBusError *error)
46 {
47 	if (dbus_error_is_set(error)) {
48 		dbus_error_free(error);
49 	}
50 }
51 
52 /*
53  * Return a copy of a string without trailing spaces. If 'len' is non-zero,
54  * it specifies max length, otherwise the string must be null-terminated.
55  */
56 static char *
57 rtrim_copy(char *src, int len)
58 {
59 	char	*dst, *p;
60 
61 	if (len == 0) {
62 		len = strlen(src);
63 	}
64 	if ((dst = calloc(1, len + 1)) != NULL) {
65 		strncpy(dst, src, len);
66 		p = dst + len - 1;
67 		while ((p >= dst) && (isspace(*p))) {
68 			*p-- = '\0';
69 		}
70 	}
71 	return (dst);
72 }
73 
74 static void
75 set_fstyp_properties (LibHalContext *ctx, const char *udi, const char *fstype, nvlist_t *fsattr)
76 {
77 	char buf[256];
78 	DBusError error;
79 	char *uuid = NULL;
80 	char *label_orig = NULL;
81 	char *label = NULL;
82 	int  err;
83 	LibHalChangeSet *cs;
84 
85 	dbus_error_init (&error);
86 
87 	if ((cs = libhal_device_new_changeset (udi)) == NULL) {
88 		return;
89 	}
90 
91 	libhal_changeset_set_property_string (cs, "volume.fsusage", "filesystem");
92 	libhal_changeset_set_property_string (cs, "volume.fstype", fstype);
93 
94 	/* label */
95 	(void) nvlist_lookup_string(fsattr, "gen_volume_label", &label_orig);
96 	if (label_orig != NULL) {
97 		label = rtrim_copy(label_orig, 0);
98 	}
99 	/* Check if label is utf8 format */
100 	if ((label != NULL) && (label[0] != '\0') &&
101 	    (u8_validate(label, strlen(label), (char **)NULL,
102 	    U8_VALIDATE_ENTIRE, &err) != -1)) {
103 	        libhal_changeset_set_property_string (cs, "volume.label", label);
104 	        libhal_changeset_set_property_string (cs, "info.product", label);
105 	} else {
106 		libhal_changeset_set_property_string (cs, "volume.label", "");
107 		snprintf (buf, sizeof (buf), "Volume (%s)", fstype);
108 		libhal_changeset_set_property_string (cs, "info.product", buf);
109 	}
110 	free(label);
111 
112 	/* uuid */
113 	if (nvlist_lookup_string(fsattr, "gen_uuid", &uuid) == 0) {
114 		libhal_changeset_set_property_string (cs, "volume.uuid", uuid);
115 	} else {
116 		libhal_changeset_set_property_string (cs, "volume.uuid", "");
117 	}
118 
119 	libhal_device_commit_changeset (ctx, cs, &error);
120 	libhal_device_free_changeset (cs);
121 
122 	my_dbus_error_free (&error);
123 }
124 
125 /*
126  * hsfs/iso9660 contents detection: Video DVD, Video CD, etc.
127  */
128 static void
129 hsfs_contents(int fd, off_t probe_offset, LibHalContext *ctx, const char *udi)
130 {
131 	size_t	secsz = ISO_SECTOR_SIZE;
132 	uchar_t	buf[ISO_SECTOR_SIZE];
133 	int	ptbl_lbn, ptbl_size;
134 	int	off, reloff, readoff;
135 	uchar_t	*p;
136 	char	*name;
137 	int	name_len;
138 	int	ipe_len;
139 	DBusError error;
140 
141 	/*
142 	 * find 1st Primary Volume Descriptor
143 	 */
144 	readoff = probe_offset + ISO_VOLDESC_SEC * secsz;
145 	if (pread (fd, buf, secsz, readoff) != secsz) {
146 		return;
147 	}
148 	while (ISO_DESC_TYPE (buf) != ISO_VD_PVD) {
149 		if (ISO_DESC_TYPE (buf) == ISO_VD_EOV) {
150 			return;
151 		}
152 		readoff += secsz;
153 		if (pread (fd, buf, secsz, readoff) != secsz) {
154 			return;
155 		}
156 	}
157 
158 	/*
159 	 * PVD contains size and offset of the LSB/MSB path table
160 	 */
161 	ptbl_size = ISO_PTBL_SIZE (buf);
162 #if defined(_LITTLE_ENDIAN)
163         ptbl_lbn = ISO_PTBL_MAN_LS (buf);
164 #else
165         ptbl_lbn = ISO_PTBL_MAN_MS (buf);
166 #endif
167 
168 	/*
169 	 * Look through path table entries
170 	 */
171 	readoff = probe_offset + ptbl_lbn * secsz;
172 	if (pread (fd, buf, secsz, readoff) != secsz) {
173 		return;
174 	}
175 	dbus_error_init (&error);
176 
177 	for (off = reloff = 0;
178 	    off < ptbl_size;
179 	    off += ipe_len, reloff += ipe_len) {
180 
181 		/* load sectors on demand */
182 		if (reloff >= secsz) {
183 			readoff += secsz;
184 			if (pread (fd, buf, secsz, readoff) != secsz) {
185 				break;
186 			}
187 			reloff -= secsz;
188 		}
189 
190 		p = buf + reloff;
191 		name_len = IPE_NAME_LEN(p);
192 		ipe_len = IPE_FPESIZE + name_len + (name_len % 2);
193 
194 		/* only interested in root directories */
195 		if (IPE_PARENT_NO (p) != 1) {
196 			continue;
197 		}
198 		if ((name_len < 2) || (name_len > IDE_MAX_NAME_LEN)) {
199 			continue;
200 		}
201 
202 		name = (char *)IPE_NAME (p);
203 		if (strncasecmp (name, "VIDEO_TS", min (8, name_len)) == 0) {
204 			libhal_device_set_property_bool (ctx, udi,
205 			    "volume.disc.is_videodvd", TRUE, &error);
206 		} else if (strncasecmp (name, "VCD", min (3, name_len)) == 0) {
207 			libhal_device_set_property_bool (ctx, udi,
208 			    "volume.disc.is_vcd", TRUE, &error);
209 		} else if (strncasecmp (name, "SVCD", min (4, name_len)) == 0) {
210 			libhal_device_set_property_bool (ctx, udi,
211 			    "volume.disc.is_svcd", TRUE, &error);
212 		}
213 	}
214 
215 	my_dbus_error_free (&error);
216 }
217 
218 static dbus_bool_t
219 probe_disc (int fd, LibHalContext *ctx, const char *udi, dbus_bool_t *has_data,
220     dbus_bool_t *has_audio)
221 {
222 	DBusError error;
223 	disc_info_t di;
224 	int profile;
225 	dbus_bool_t is_blank, is_appendable, is_rewritable;
226 	char *disc_type = "cd_rom";
227 	uint64_t capacity = 0;
228 	int i;
229 	LibHalChangeSet *cs;
230 
231 	dbus_error_init (&error);
232 
233 	if (get_disc_info (fd, &di)) {
234 		is_blank = (di.disc_status == 0);
235 		is_appendable = (di.disc_status == 1);
236 		is_rewritable = (di.erasable != 0);
237 	} else {
238 		is_blank = is_appendable = is_rewritable = FALSE;
239 	}
240 
241 	if (get_current_profile (fd, &profile)) {
242 		switch (profile) {
243 		case 0x08: /* CD-ROM */
244 			disc_type = "cd_rom";
245 			break;
246 		case 0x09: /* CD-R */
247 			disc_type = "cd_r";
248 			break;
249 		case 0x0A: /* CD-RW */
250 			disc_type = "cd_rw";
251 			is_rewritable = TRUE;
252 			break;
253 		case 0x10: /* DVD-ROM */
254 			disc_type = "dvd_rom";
255 			break;
256 		case 0x11: /* DVD-R Sequential */
257 			disc_type = "dvd_r";
258 			break;
259 		case 0x12: /* DVD-RAM */
260 			disc_type = "dvd_ram";
261 			is_rewritable = TRUE;
262 			break;
263 		case 0x13: /* DVD-RW Restricted Overwrite */
264 			disc_type = "dvd_rw";
265 			is_rewritable = TRUE;
266 			break;
267 		case 0x14: /* DVD-RW Sequential */
268 			disc_type = "dvd_rw";
269 			is_rewritable = TRUE;
270 			break;
271 		case 0x1A: /* DVD+RW */
272 			disc_type = "dvd_plus_rw";
273 			is_rewritable = TRUE;
274 			break;
275 		case 0x1B: /* DVD+R */
276 			disc_type = "dvd_plus_r";
277 			break;
278 		case 0x2B: /* DVD+R Double Layer */
279                         disc_type = "dvd_plus_r_dl";
280 			break;
281 		case 0x40: /* BD-ROM */
282                         disc_type = "bd_rom";
283 			break;
284 		case 0x41: /* BD-R Sequential */
285                         disc_type = "bd_r";
286 			break;
287 		case 0x42: /* BD-R Random */
288                         disc_type = "bd_r";
289 			break;
290 		case 0x43: /* BD-RE */
291                         disc_type = "bd_re";
292 			is_rewritable = TRUE;
293 			break;
294 		case 0x50: /* HD DVD-ROM */
295                         disc_type = "hddvd_rom";
296 			break;
297 		case 0x51: /* HD DVD-R */
298                         disc_type = "hddvd_r";
299 			break;
300 		case 0x52: /* HD DVD-Rewritable */
301                         disc_type = "hddvd_rw";
302 			is_rewritable = TRUE;
303 			break;
304 		}
305 
306 		(void) get_disc_capacity_for_profile(fd, profile, &capacity);
307 	}
308 
309 	*has_audio = *has_data = FALSE;
310 	if (!is_blank) {
311 		uchar_t	smalltoc[12];
312 		size_t	toc_size;
313 		uchar_t	*toc, *p;
314 
315 		/*
316 		 * XXX for some reason CDROMREADTOCENTRY fails on video DVDs,
317 		 * but extracting the toc directly works okay. And the toc
318 		 * data buffer length passed to read_toc() should be the same
319 		 * as the real buffer size.
320 		 */
321 		if (!read_toc(fd, 0, 1, 12, smalltoc)) {
322                 	HAL_DEBUG(("read_toc failed"));
323 			*has_data = B_TRUE; /* probe for fs anyway */
324         	} else {
325         		toc_size = smalltoc[0] * 256 + smalltoc[1] + 2;
326         		toc = (uchar_t *)calloc(1, toc_size);
327         		if (toc == NULL || !read_toc(fd, 0, 1, toc_size, toc)) {
328                 		HAL_DEBUG (("read_toc again failed"));
329         		} else {
330         			for (p = &toc[4]; p < (toc + toc_size); p += 8) {
331 					/* skip leadout */
332                 			if (p[2] == 0xAA) {
333 						continue;
334 					}
335 					if (p[1] & 4) {
336 						*has_data = B_TRUE;
337 					} else {
338 						*has_audio = B_TRUE;
339 					}
340         			}
341 			}
342 			free(toc);
343 		}
344 	}
345 
346 	if ((cs = libhal_device_new_changeset (udi)) == NULL) {
347 		return (FALSE);
348 	}
349 	libhal_changeset_set_property_string (cs, "volume.disc.type", disc_type);
350 	libhal_changeset_set_property_bool (cs, "volume.disc.is_blank", is_blank);
351 	libhal_changeset_set_property_bool (cs, "volume.disc.has_audio", *has_audio);
352 	libhal_changeset_set_property_bool (cs, "volume.disc.has_data", *has_data);
353 	libhal_changeset_set_property_bool (cs, "volume.disc.is_appendable", is_appendable);
354 	libhal_changeset_set_property_bool (cs, "volume.disc.is_rewritable", is_rewritable);
355 	libhal_changeset_set_property_uint64 (cs, "volume.disc.capacity", capacity);
356 
357 	libhal_changeset_set_property_bool (cs, "volume.disc.is_videodvd", FALSE);
358 	libhal_changeset_set_property_bool (cs, "volume.disc.is_vcd", FALSE);
359 	libhal_changeset_set_property_bool (cs, "volume.disc.is_svcd", FALSE);
360 
361 	libhal_device_commit_changeset (ctx, cs, &error);
362 	libhal_device_free_changeset (cs);
363 
364 out:
365 	my_dbus_error_free (&error);
366 
367 	return (TRUE);
368 }
369 
370 static void
371 drop_privileges ()
372 {
373 	priv_set_t *pPrivSet = NULL;
374 	priv_set_t *lPrivSet = NULL;
375 
376 	/*
377 	 * Start with the 'basic' privilege set and then remove any
378 	 * of the 'basic' privileges that will not be needed.
379 	 */
380 	if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) {
381 		return;
382 	}
383 
384 	/* Clear privileges we will not need from the 'basic' set */
385 	(void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY);
386 	(void) priv_delset(pPrivSet, PRIV_PROC_INFO);
387 	(void) priv_delset(pPrivSet, PRIV_PROC_SESSION);
388 	(void) priv_delset(pPrivSet, PRIV_PROC_EXEC);
389 	(void) priv_delset(pPrivSet, PRIV_PROC_FORK);
390 
391 	/* for uscsi */
392 	(void) priv_addset(pPrivSet, PRIV_SYS_DEVICES);
393 
394 
395 	/* to open logindevperm'd devices */
396 	(void) priv_addset(pPrivSet, PRIV_FILE_DAC_READ);
397 
398 	/* Set the permitted privilege set. */
399 	if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) {
400 		return;
401 	}
402 
403 	/* Clear the limit set. */
404 	if ((lPrivSet = priv_allocset()) == NULL) {
405 		return;
406 	}
407 
408 	priv_emptyset(lPrivSet);
409 
410 	if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) {
411 		return;
412 	}
413 
414 	priv_freeset(lPrivSet);
415 }
416 
417 int
418 main (int argc, char *argv[])
419 {
420 	int fd, rfd;
421 	int ret;
422 	char *udi;
423 	char *device_file, *raw_device_file;
424 	char *devpath, *rdevpath;
425 	boolean_t is_dos;
426 	int dos_num;
427 	LibHalContext *ctx = NULL;
428 	DBusError error;
429 	DBusConnection *conn;
430 	char *parent_udi;
431 	char *storage_device;
432 	char *is_disc_str;
433 	int fdc;
434 	dbus_bool_t is_disc = FALSE;
435 	dbus_bool_t is_floppy = FALSE;
436 	unsigned int block_size;
437 	dbus_uint64_t vol_size;
438 	dbus_bool_t has_data = TRUE;	/* probe for fs by default */
439 	dbus_bool_t has_audio = FALSE;
440 	char *partition_scheme = NULL;
441 	dbus_uint64_t partition_start = 0;
442 	int partition_number = 0;
443 	struct extvtoc vtoc;
444 	dk_gpt_t *gpt;
445 	struct dk_minfo mi;
446 	int i, dos_cnt;
447 	fstyp_handle_t fstyp_handle;
448 	off_t probe_offset = 0;
449 	int num_volumes;
450 	char **volumes;
451 	dbus_uint64_t v_start;
452 	const char *fstype;
453 	nvlist_t *fsattr;
454 
455 	fd = rfd = -1;
456 
457 	ret = 1;
458 
459 	if ((udi = getenv ("UDI")) == NULL) {
460 		goto out;
461 	}
462 	if ((device_file = getenv ("HAL_PROP_BLOCK_DEVICE")) == NULL) {
463 		goto out;
464 	}
465 	if ((raw_device_file = getenv ("HAL_PROP_BLOCK_SOLARIS_RAW_DEVICE")) == NULL) {
466 		goto out;
467 	}
468 	if (!dos_to_dev(raw_device_file, &rdevpath, &dos_num)) {
469 		rdevpath = raw_device_file;
470 	}
471 	if (!(is_dos = dos_to_dev(device_file, &devpath, &dos_num))) {
472 		devpath = device_file;
473 	}
474 	if ((parent_udi = getenv ("HAL_PROP_INFO_PARENT")) == NULL) {
475 		goto out;
476 	}
477 	if ((storage_device = getenv ("HAL_PROP_BLOCK_STORAGE_DEVICE")) == NULL) {
478 		goto out;
479 	}
480 
481 	is_disc_str = getenv ("HAL_PROP_VOLUME_IS_DISC");
482 	if (is_disc_str != NULL && strcmp (is_disc_str, "true") == 0) {
483 		is_disc = TRUE;
484 	} else {
485 		is_disc = FALSE;
486 	}
487 
488 	drop_privileges ();
489 
490 	setup_logger ();
491 
492 	dbus_error_init (&error);
493 	if ((ctx = libhal_ctx_init_direct (&error)) == NULL)
494 		goto out;
495 
496 	HAL_DEBUG (("Doing probe-volume for %s\n", device_file));
497 
498 	fd = open (devpath, O_RDONLY | O_NONBLOCK);
499 	if (fd < 0) {
500 		goto out;
501 	}
502 	rfd = open (rdevpath, O_RDONLY | O_NONBLOCK);
503 	if (rfd < 0) {
504 		goto out;
505 	}
506 
507 	/* if it's a floppy with no media, bail out */
508 	if (ioctl(rfd, FDGETCHANGE, &fdc) == 0) {
509 		is_floppy = TRUE;
510 		if (fdc & FDGC_CURRENT) {
511 			goto out;
512 		}
513 	}
514 
515 	/* block size and total size */
516 	if (ioctl(rfd, DKIOCGMEDIAINFO, &mi) != -1) {
517 		block_size = mi.dki_lbsize;
518 		vol_size = mi.dki_capacity * block_size;
519 	} else if (errno == ENXIO) {
520 		/* driver supports ioctl, but media is not available */
521 		goto out;
522 	} else {
523 		/* driver does not support ioctl, e.g. lofi */
524 		block_size = 512;
525 		vol_size = 0;
526 	}
527 	libhal_device_set_property_int (ctx, udi, "volume.block_size", block_size, &error);
528 	my_dbus_error_free (&error);
529 	libhal_device_set_property_uint64 (ctx, udi, "volume.size", vol_size, &error);
530 	my_dbus_error_free (&error);
531 
532 	if (is_disc) {
533 		if (!probe_disc (rfd, ctx, udi, &has_data, &has_audio)) {
534 			HAL_DEBUG (("probe_disc failed, skipping fstyp"));
535 			goto out;
536 		}
537 		/* with audio present, create volume even if fs probing fails */
538 		if (has_audio) {
539 			ret = 0;
540 		}
541 	}
542 
543 	if (!has_data) {
544 		goto skip_fs;
545 	}
546 
547 	/* don't support partitioned floppy */
548 	if (is_floppy) {
549 		goto skip_part;
550 	}
551 
552 	/*
553 	 * first get partitioning info
554 	 */
555 	if (is_dos) {
556 		/* for a dos drive find partition offset */
557 		if (!find_dos_drive(fd, dos_num, block_size, &probe_offset)) {
558 			goto out;
559 		}
560 		partition_scheme = "mbr";
561 		partition_start = (dbus_uint64_t)probe_offset;
562 		partition_number = dos_num;
563 	} else {
564 		if ((partition_number = read_extvtoc(rfd, &vtoc)) >= 0) {
565 			if (!vtoc_one_slice_entire_disk(&vtoc)) {
566 				partition_scheme = "smi";
567 				if (partition_number < vtoc.v_nparts) {
568 					if (vtoc.v_part[partition_number].p_size == 0) {
569 						HAL_DEBUG (("zero size partition"));
570 					}
571 					partition_start = vtoc.v_part[partition_number].p_start * block_size;
572 				}
573 			}
574 		} else if ((partition_number = efi_alloc_and_read(rfd, &gpt)) >= 0) {
575 			partition_scheme = "gpt";
576 			if (partition_number < gpt->efi_nparts) {
577 				if (gpt->efi_parts[partition_number].p_size == 0) {
578 					HAL_DEBUG (("zero size partition"));
579 				}
580 				partition_start = gpt->efi_parts[partition_number].p_start * block_size;
581 			}
582 			efi_free(gpt);
583 		}
584 		probe_offset = 0;
585 	}
586 
587 	if (partition_scheme != NULL) {
588 		libhal_device_set_property_string (ctx, udi, "volume.partition.scheme", partition_scheme, &error);
589 		my_dbus_error_free (&error);
590 		libhal_device_set_property_int (ctx, udi, "volume.partition.number", partition_number, &error);
591 		my_dbus_error_free (&error);
592 		libhal_device_set_property_uint64 (ctx, udi, "volume.partition.start", partition_start, &error);
593 		my_dbus_error_free (&error);
594 		libhal_device_set_property_bool (ctx, udi, "volume.is_partition", TRUE, &error);
595 		my_dbus_error_free (&error);
596 	} else {
597 		libhal_device_set_property_bool (ctx, udi, "volume.is_partition", FALSE, &error);
598 		my_dbus_error_free (&error);
599 	}
600 
601 	/*
602 	 * ignore duplicate partitions
603 	 */
604 	if ((volumes = libhal_manager_find_device_string_match (
605 	    ctx, "block.storage_device", storage_device, &num_volumes, &error)) != NULL) {
606 		my_dbus_error_free (&error);
607 		for (i = 0; i < num_volumes; i++) {
608 			if (strcmp (udi, volumes[i]) == 0) {
609 				continue; /* skip self */
610 			}
611 			v_start = libhal_device_get_property_uint64 (ctx, volumes[i], "volume.partition.start", &error);
612 			if (dbus_error_is_set(&error)) {
613 				dbus_error_free(&error);
614 				continue;
615 			}
616 			if (v_start == partition_start) {
617 				HAL_DEBUG (("duplicate partition"));
618 				goto out;
619 			}
620 		}
621 		libhal_free_string_array (volumes);
622 	}
623 
624 skip_part:
625 
626 	/*
627 	 * now determine fs type
628 	 *
629 	 * XXX We could get better performance from block device,
630 	 * but for now we use raw device because:
631 	 *
632 	 * - fstyp_udfs has a bug that it only works on raw
633 	 *
634 	 * - sd has a bug that causes extremely slow reads
635 	 *   and incorrect probing of hybrid audio/data media
636 	 */
637 	if (fstyp_init(rfd, probe_offset, NULL, &fstyp_handle) != 0) {
638 		HAL_DEBUG (("fstyp_init failed"));
639 		goto out;
640 	}
641 	if ((fstyp_ident(fstyp_handle, NULL, &fstype) != 0) ||
642 	    (fstyp_get_attr(fstyp_handle, &fsattr) != 0)) {
643 		HAL_DEBUG (("fstyp ident or get_attr failed"));
644 		fstyp_fini(fstyp_handle);
645 		goto out;
646 	}
647 	set_fstyp_properties (ctx, udi, fstype, fsattr);
648 
649 	if (strcmp (fstype, "hsfs") == 0) {
650 		hsfs_contents (fd, probe_offset, ctx, udi);
651 	}
652 
653 	fstyp_fini(fstyp_handle);
654 
655 skip_fs:
656 
657 	ret = 0;
658 
659 out:
660 	if (fd >= 0)
661 		close (fd);
662 	if (rfd >= 0)
663 		close (rfd);
664 
665 	if (ctx != NULL) {
666 		my_dbus_error_free (&error);
667 		libhal_ctx_shutdown (ctx, &error);
668 		libhal_ctx_free (ctx);
669 	}
670 
671 	return ret;
672 
673 }
674