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