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