2 * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
5 * This code is derived from software contributed to The NetBSD Foundation
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/types.h>
33 #include <sys/param.h>
34 #include <sys/ioctl.h>
36 #include <machine/inttypes.h>
37 #include <dev/disk/dm/netbsd-dm.h>
45 #include <libprop/proplib.h>
51 prop_dictionary_t dict;
58 uint32_t cmd_version[3];
61 struct dm_cmd dm_cmds[] = {
62 { DM_DEVICE_REMOVE, "remove", {4, 0, 0} },
63 { DM_DEVICE_REMOVE_ALL, "remove_all", {4, 0, 0} },
64 { DM_DEVICE_CREATE, "create", {4, 0, 0} },
65 { DM_DEVICE_RELOAD, "reload", {4, 0, 0} },
66 { DM_DEVICE_RESUME, "resume", {4, 0, 0} },
67 { DM_DEVICE_SUSPEND, "suspend", {4, 0, 0} },
68 { DM_DEVICE_CLEAR, "clear", {4, 0, 0} },
69 { DM_DEVICE_LIST_VERSIONS, "targets", {4, 1, 0} },
70 { DM_DEVICE_STATUS, "status", {4, 0, 0} },
71 { DM_DEVICE_TABLE, "table", {4, 0, 0} },
72 { DM_DEVICE_INFO, "info", {4, 0, 0} },
73 { DM_DEVICE_DEPS, "deps", {4, 0, 0} },
74 { DM_DEVICE_VERSION, "version", {4, 0, 0} },
75 { DM_DEVICE_TARGET_MSG, "message", {4, 2, 0} },
76 { DM_DEVICE_RENAME, "rename", {4, 0, 0} },
77 { DM_DEVICE_LIST, "names", {4, 0, 0} },
78 { 0, NULL, {0, 0, 0} }
85 static void _stderr_log(int level, const char *file,
86 int line, const char *fmt, ...)
105 fprintf(stderr, "libdm %s:%d: ", file, line);
106 fprintf(stderr, "%s", prefix);
109 vfprintf(stderr, fmt, ap);
112 fprintf(stderr, "\n");
117 static dm_error_func_t dm_log = _stderr_log;
120 dm_task_create(int task_type)
123 struct dm_cmd *cmd = NULL;
124 const char *task_cmd = NULL;
126 uint32_t flags = DM_EXISTS_FLAG;
129 for (i = 0; dm_cmds[i].dm_cmd != NULL; i++) {
130 if (dm_cmds[i].task_type == task_type) {
132 task_cmd = dm_cmds[i].dm_cmd;
137 if (task_cmd == NULL)
140 if (task_type == DM_DEVICE_TABLE)
141 flags |= DM_STATUS_TABLE_FLAG;
143 if (task_type == DM_DEVICE_SUSPEND)
144 flags |= DM_SUSPEND_FLAG;
146 if ((dmt = malloc(sizeof(*dmt))) == NULL)
149 memset(dmt, 0, sizeof(*dmt));
151 dmt->task_type = task_type;
154 if ((dmt->dict = prop_dictionary_create()) == NULL)
157 if ((pa = prop_array_create_with_capacity(3)) == NULL)
160 if (!prop_array_add_uint32(pa, cmd->cmd_version[0])) {
161 prop_object_release(pa);
165 if (!prop_array_add_uint32(pa, cmd->cmd_version[1])) {
166 prop_object_release(pa);
170 if (!prop_array_add_uint32(pa, cmd->cmd_version[2])) {
171 prop_object_release(pa);
175 if (!prop_dictionary_set(dmt->dict, DM_IOCTL_VERSION, pa)) {
176 prop_object_release(pa);
180 prop_object_release(pa);
182 if (!prop_dictionary_set_cstring(dmt->dict, DM_IOCTL_COMMAND,
186 if (!prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags))
189 if ((pa = prop_array_create_with_capacity(5)) == NULL)
192 if (!prop_dictionary_set(dmt->dict, DM_IOCTL_CMD_DATA, pa)) {
193 prop_object_release(pa);
197 prop_object_release(pa);
203 if (dmt->dict != NULL)
204 prop_object_release(dmt->dict);
213 dm_task_destroy(struct dm_task *dmt)
216 if (dmt->data_buffer)
217 free(dmt->data_buffer);
220 prop_object_release(dmt->dict);
229 dm_task_run(struct dm_task *dmt)
231 struct dm_task *dmt_internal = NULL;
232 prop_dictionary_t ret_pd = NULL;
238 if ((fd = open("/dev/mapper/control", O_RDWR)) < -1)
241 pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA);
242 if ((dmt->task_type == DM_DEVICE_CREATE) && (pa != NULL) &&
243 (prop_array_count(pa) > 0)) {
245 * Magic to separate a combined DM_DEVICE_CREATE+RELOAD int
246 * a DM_DEVICE_CREATE and a RELOAD with target table.
249 if ((dmt_internal = dm_task_create(DM_DEVICE_CREATE)) == NULL)
251 if (!dm_task_set_name(dmt_internal, dm_task_get_name(dmt)))
253 if (!dm_task_set_uuid(dmt_internal, dm_task_get_uuid(dmt)))
255 if (!dm_task_run(dmt_internal))
257 dm_task_destroy(dmt_internal);
260 if (!prop_dictionary_set_cstring_nocopy(dmt->dict,
261 DM_IOCTL_COMMAND, "reload"))
263 dmt->task_type = DM_DEVICE_RELOAD;
264 if ((error = prop_dictionary_sendrecv_ioctl(dmt->dict, fd,
265 NETBSD_DM_IOCTL, &ret_pd)) != 0) {
266 dm_log(_LOG_ERR, __FILE__, __LINE__, "ioctl failed: %d",
271 if (!prop_dictionary_set_cstring_nocopy(dmt->dict,
272 DM_IOCTL_COMMAND, "resume"))
274 dmt->task_type = DM_DEVICE_RESUME;
275 /* Remove superfluous stuff */
276 prop_dictionary_remove(dmt->dict, DM_IOCTL_CMD_DATA);
281 if ((error = prop_dictionary_sendrecv_ioctl(dmt->dict, fd,
282 NETBSD_DM_IOCTL, &ret_pd)) != 0) {
283 if (((error == ENOENT) &&
284 ((dmt->task_type == DM_DEVICE_INFO) ||
285 (dmt->task_type == DM_DEVICE_STATUS)))) {
289 dm_log(_LOG_ERR, __FILE__, __LINE__, "ioctl failed: %d",
299 prop_object_retain(ret_pd);
301 prop_object_release(dmt->dict);
308 prop_dictionary_remove(dmt->dict, DM_IOCTL_CMD_DATA);
310 if (!prop_dictionary_set_cstring_nocopy(dmt->dict, DM_IOCTL_COMMAND,
312 dm_log(_LOG_ERR, __FILE__, __LINE__, "couldn't unroll changes "
317 if ((error = prop_dictionary_sendrecv_ioctl(dmt->dict, fd,
318 NETBSD_DM_IOCTL, &ret_pd)) != 0) {
319 dm_log(_LOG_ERR, __FILE__, __LINE__, "ioctl failed: %d",
323 dmt->task_type = DM_DEVICE_REMOVE;
331 dm_task_destroy(dmt_internal);
337 dm_task_set_name(struct dm_task *dmt, const char *name)
339 return prop_dictionary_set_cstring(dmt->dict, DM_IOCTL_NAME,
340 __DECONST(char *, name));
345 dm_task_get_name(struct dm_task *dmt)
347 const char *name = NULL;
349 prop_dictionary_get_cstring_nocopy(dmt->dict, DM_IOCTL_NAME, &name);
355 dm_task_set_newname(struct dm_task *dmt, const char *newname)
357 return prop_dictionary_set_cstring(dmt->dict, DM_DEV_NEWNAME,
358 __DECONST(char *, newname));
362 dm_task_set_major(struct dm_task *dmt __unused, int major __unused)
368 dm_task_set_minor(struct dm_task *dmt, int minor)
370 return prop_dictionary_set_int32(dmt->dict, DM_IOCTL_MINOR, minor);
374 dm_task_get_minor(struct dm_task *dmt)
378 minor = prop_dictionary_get_int32(dmt->dict, DM_IOCTL_MINOR, &minor);
384 dm_task_set_uuid(struct dm_task *dmt, const char *uuid)
386 return prop_dictionary_set_cstring(dmt->dict, DM_IOCTL_UUID,
387 __DECONST(char *,uuid));
391 dm_task_get_uuid(struct dm_task *dmt)
393 const char *uuid = NULL;
395 prop_dictionary_get_cstring_nocopy(dmt->dict, DM_IOCTL_UUID, &uuid);
401 dm_task_add_target(struct dm_task *dmt, uint64_t start, size_t size,
402 const char *target, const char *params)
404 prop_dictionary_t target_dict = NULL;
405 prop_array_t pa = NULL;
407 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
410 if ((target_dict = prop_dictionary_create()) == NULL)
413 if (!prop_dictionary_set_uint64(target_dict, DM_TABLE_START, start))
416 if (!prop_dictionary_set_uint64(target_dict, DM_TABLE_LENGTH, size))
419 if (!prop_dictionary_set_cstring(target_dict, DM_TABLE_TYPE, target))
422 if (!prop_dictionary_set_cstring(target_dict, DM_TABLE_PARAMS, params))
425 if (!prop_array_add(pa, target_dict))
428 prop_object_release(target_dict);
434 prop_object_release(target_dict);
439 dm_task_set_sector(struct dm_task *dmt, uint64_t sector)
441 return prop_dictionary_set_uint64(dmt->dict, DM_MESSAGE_SECTOR,
446 dm_task_set_message(struct dm_task *dmt, const char *msg)
448 return prop_dictionary_set_cstring(dmt->dict, DM_MESSAGE_STR, msg);
452 dm_task_set_ro(struct dm_task *dmt)
456 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS, &flags);
457 flags |= DM_READONLY_FLAG;
459 return prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags);
463 dm_task_no_open_count(struct dm_task *dmt __unused)
466 * nothing else needed, since we don't have performance problems when
467 * getting the open count.
473 dm_task_query_inactive_table(struct dm_task *dmt)
477 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS, &flags);
478 flags |= DM_QUERY_INACTIVE_TABLE_FLAG;
480 return prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags);
484 dm_task_set_read_ahead(struct dm_task *dmt __unused,
485 uint32_t read_ahead __unused)
487 /* We don't support readahead */
492 dm_task_get_read_ahead(struct dm_task *dmt __unused, uint32_t *read_ahead)
500 dm_task_secure_data(struct dm_task *dmt)
502 /* XXX: needs kernel support */
505 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS, &flags);
506 flags |= DM_SECURE_DATA_FLAG;
508 return prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags);
512 dm_task_get_info(struct dm_task *dmt, struct dm_info *dmi)
516 memset(dmi, 0, sizeof(struct dm_info));
518 /* Hack due to the way Linux dm works */
519 if (dmt->was_enoent) {
525 if (!prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS,
529 prop_dictionary_get_int32(dmt->dict, DM_IOCTL_OPEN, &dmi->open_count);
531 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_TARGET_COUNT,
534 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_EVENT, &dmi->event_nr);
536 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_MINOR, &dmi->minor);
538 dmi->major = dm_get_major();
540 dmi->read_only = (flags & DM_READONLY_FLAG);
541 dmi->exists = (flags & DM_EXISTS_FLAG);
542 dmi->suspended = (flags & DM_SUSPEND_FLAG);
543 dmi->live_table = (flags & DM_ACTIVE_PRESENT_FLAG);
544 dmi->inactive_table = (flags & DM_INACTIVE_PRESENT_FLAG);
550 dm_task_get_driver_version(struct dm_task *dmt, char *ver, size_t ver_sz)
553 uint32_t maj = 0, min = 0, patch = 0;
555 if ((pa_ver = prop_dictionary_get(dmt->dict, DM_IOCTL_VERSION)) == NULL)
558 if (!prop_array_get_uint32(pa_ver, 0, &maj))
561 if (!prop_array_get_uint32(pa_ver, 1, &min))
564 if (!prop_array_get_uint32(pa_ver, 2, &patch))
567 snprintf(ver, ver_sz, "%u.%u.%u", maj, min, patch);
573 dm_task_get_deps(struct dm_task *dmt)
575 prop_object_iterator_t iter;
578 struct dm_deps *deps;
583 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
586 count = prop_array_count(pa);
588 if (dmt->data_buffer != NULL)
589 free(dmt->data_buffer);
591 if ((dmt->data_buffer = malloc(sizeof(struct dm_deps) +
592 (count * sizeof(uint64_t)))) == NULL)
595 if ((iter = prop_array_iterator(pa)) == NULL)
598 deps = (struct dm_deps *)dmt->data_buffer;
599 memset(deps, 0, sizeof(struct dm_deps) + (count * sizeof(uint64_t)));
601 while ((po = prop_object_iterator_next(iter)) != NULL)
602 deps->deps[i++] = prop_number_unsigned_integer_value(po);
604 deps->count = (uint32_t)count;
606 prop_object_iterator_release(iter);
612 dm_task_get_versions(struct dm_task *dmt)
614 prop_object_iterator_t iter;
615 prop_dictionary_t target_dict;
616 prop_array_t pa, pa_ver;
617 struct dm_versions *vers;
622 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
625 count = prop_array_count(pa);
627 if (dmt->data_buffer != NULL)
628 free(dmt->data_buffer);
630 if ((dmt->data_buffer = malloc(sizeof(struct dm_versions) * count))
634 if ((iter = prop_array_iterator(pa)) == NULL)
637 vers = (struct dm_versions *)dmt->data_buffer;
638 memset(vers, 0, sizeof(struct dm_versions) * count);
640 while ((target_dict = prop_object_iterator_next(iter)) != NULL) {
641 vers[i].next = sizeof(struct dm_versions);
642 prop_dictionary_get_cstring_nocopy(target_dict,
643 DM_TARGETS_NAME, &vers[i].name);
645 pa_ver = prop_dictionary_get(target_dict, DM_TARGETS_VERSION);
646 for (j = 0; j < 3; j++)
647 prop_array_get_uint32(pa_ver, j, &vers[i].version[j]);
652 /* Finish the array */
655 prop_object_iterator_release(iter);
657 return (struct dm_versions *)dmt->data_buffer;
661 dm_task_get_names(struct dm_task *dmt)
663 prop_object_iterator_t iter;
664 prop_dictionary_t devs_dict;
666 struct dm_names *names;
671 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
674 count = prop_array_count(pa);
676 if (dmt->data_buffer != NULL)
677 free(dmt->data_buffer);
679 if ((dmt->data_buffer = malloc(sizeof(struct dm_names) * count))
683 if ((iter = prop_array_iterator(pa)) == NULL)
686 names = (struct dm_names *)dmt->data_buffer;
687 memset(names, 0, sizeof(struct dm_names) * count);
689 while ((devs_dict = prop_object_iterator_next(iter)) != NULL) {
690 names[i].next = sizeof(struct dm_names);
692 prop_dictionary_get_cstring_nocopy(devs_dict,
693 DM_DEV_NAME, &names[i].name);
695 prop_dictionary_get_uint64(devs_dict, DM_DEV_DEV,
701 /* Finish the array */
704 prop_object_iterator_release(iter);
706 return (struct dm_names *)dmt->data_buffer;
710 dm_task_update_nodes(void)
713 /* nothing else needed */
718 dm_get_next_target(struct dm_task *dmt, void *cur, uint64_t *startp,
719 uint64_t *lengthp, char **target_type, char **params)
721 prop_object_iterator_t iter;
722 prop_dictionary_t target_dict;
727 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
730 count = prop_array_count(pa);
733 if ((iter = prop_array_iterator(pa)) == NULL)
736 iter = (prop_object_iterator_t)cur;
739 /* Get the next target dict */
740 if ((target_dict = prop_object_iterator_next(iter)) == NULL) {
741 /* If there are no more target dicts, release the iterator */
745 if (!prop_dictionary_get_cstring_nocopy(target_dict, DM_TABLE_TYPE,
746 (const char **)target_type))
750 * Ugly __DECONST and (const char **) casts due to the linux prototype
753 *params = __DECONST(char *, "");
754 prop_dictionary_get_cstring_nocopy(target_dict, DM_TABLE_PARAMS,
755 (const char **)params);
757 if (!prop_dictionary_get_uint64(target_dict, DM_TABLE_START, startp))
760 if (!prop_dictionary_get_uint64(target_dict, DM_TABLE_LENGTH, &ulength))
763 *lengthp = (size_t)ulength;
765 /* If we are at the last element, make sure we return NULL */
766 if (target_dict == prop_array_get(pa, count-1))
774 prop_object_iterator_release(iter);
784 if (stat("/dev/mapper/control", &sb) < 0)
787 return (uint32_t)major(sb.st_dev);
791 dm_is_dm_major(uint32_t major)
793 return (major == dm_get_major());
799 return "/dev/mapper";
803 dm_udev_set_sync_support(int sync_udev __unused)
809 dm_task_set_cookie(struct dm_task *dmt __unused, uint32_t *cookie __unused,
810 uint16_t udev_flags __unused)
816 dm_udev_wait(uint32_t cookie __unused)
828 dm_log_init(dm_error_func_t fn)
836 dm_log_init_verbose(int verbose __unused)
841 /* XXX: unused in kernel */
843 dm_task_set_uid(struct dm_task *dmt, uid_t uid)
845 return prop_dictionary_set_uint32(dmt->dict, DM_DEV_UID,
850 dm_task_set_gid(struct dm_task *dmt, gid_t gid)
852 return prop_dictionary_set_uint32(dmt->dict, DM_DEV_GID,
857 dm_task_set_mode(struct dm_task *dmt, mode_t mode)
859 return prop_dictionary_set_uint32(dmt->dict, DM_DEV_MODE,
864 dm_task_no_flush(struct dm_task *dmt __unused)
868 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS, &flags);
869 flags |= DM_NOFLUSH_FLAG;
871 return prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags);
875 dm_task_skip_lockfs(struct dm_task *dmt __unused)
879 prop_dictionary_get_uint32(dmt->dict, DM_IOCTL_FLAGS, &flags);
880 flags |= DM_SKIP_LOCKFS_FLAG;
882 return prop_dictionary_set_uint32(dmt->dict, DM_IOCTL_FLAGS, flags);
885 int dm_task_set_geometry(struct dm_task *dmt __unused,
886 const char *cylinders __unused, const char *heads __unused,
887 const char *sectors __unused, const char *start __unused)
892 /*****************************************************************************/
893 /********************** DragonFly-specific extensions ************************/
894 /*****************************************************************************/
896 dm_get_next_version(struct dm_task *dmt, void *cur, const char **target_type,
897 uint32_t *target_ver)
899 prop_object_iterator_t iter;
900 prop_dictionary_t target_dict;
901 prop_array_t pa, pa_ver;
905 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
908 count = prop_array_count(pa);
911 if ((iter = prop_array_iterator(pa)) == NULL)
914 iter = (prop_object_iterator_t)cur;
917 /* Get the next target dict */
918 if ((target_dict = prop_object_iterator_next(iter)) == NULL) {
919 /* If there are no more target dicts, release the iterator */
923 if (!prop_dictionary_get_cstring_nocopy(target_dict, DM_TARGETS_NAME,
927 if ((pa_ver = prop_dictionary_get(target_dict, DM_TARGETS_VERSION))
931 for (j = 0; j < 3; j++) {
932 if (!prop_array_get_uint32(pa_ver, j, &target_ver[j]))
936 /* If we are at the last element, make sure we return NULL */
937 if (target_dict == prop_array_get(pa, count-1))
945 prop_object_iterator_release(iter);
951 dm_get_next_dep(struct dm_task *dmt, void *cur, uint64_t *dep)
953 prop_object_iterator_t iter;
960 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
963 count = prop_array_count(pa);
966 if ((iter = prop_array_iterator(pa)) == NULL)
969 iter = (prop_object_iterator_t)cur;
972 /* Get the next target dict */
973 if ((po = prop_object_iterator_next(iter)) == NULL) {
974 /* If there are no more target dicts, release the iterator */
978 *dep = prop_number_unsigned_integer_value(po);
980 /* If we are at the last element, make sure we return NULL */
981 if (po == prop_array_get(pa, count-1))
989 prop_object_iterator_release(iter);
995 dm_get_next_name(struct dm_task *dmt, void *cur, const char **name,
998 prop_object_iterator_t iter;
999 prop_dictionary_t devs_dict;
1003 if ((pa = prop_dictionary_get(dmt->dict, DM_IOCTL_CMD_DATA)) == NULL)
1006 count = prop_array_count(pa);
1010 if ((iter = prop_array_iterator(pa)) == NULL)
1013 iter = (prop_object_iterator_t)cur;
1016 /* Get the next dev dict */
1017 if ((devs_dict = prop_object_iterator_next(iter)) == NULL) {
1018 /* If there are no more dev dicts, release the iterator */
1022 if (!prop_dictionary_get_cstring_nocopy(devs_dict, DM_DEV_NAME, name))
1025 if (!prop_dictionary_get_uint64(devs_dict, DM_DEV_DEV, dev))
1028 /* If we are at the last element, make sure we return NULL */
1029 if (devs_dict == prop_array_get(pa, count-1))
1032 return (void *)iter;
1037 prop_object_iterator_release(iter);