static struct external_profile *create_profile(struct gatt_app *app,
						GDBusProxy *proxy,
						const char *path)
{
	struct external_profile *profile;
	DBusMessageIter iter, array;

	if (!path || !g_str_has_prefix(path, "/"))
		return NULL;

	profile = new0(struct external_profile, 1);

	profile->app = app;
	profile->proxy = g_dbus_proxy_ref(proxy);
	profile->profiles = queue_new();

	if (!g_dbus_proxy_get_property(proxy, "UUIDs", &iter)) {
		DBG("UUIDs property not found");
		goto fail;
	}

	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
		const char *uuid;

		dbus_message_iter_get_basic(&array, &uuid);

		if (profile_add(profile, uuid) < 0)
			goto fail;

		dbus_message_iter_next(&array);
	}

	if (queue_isempty(profile->profiles))
		goto fail;

	queue_foreach(profile->profiles, add_profile, app->database->adapter);
	queue_push_tail(app->profiles, profile);

	return profile;

fail:
	profile_free(profile);
	return NULL;
}


static bool database_add_app(struct gatt_app *app)
{
	const struct queue_entry *entry;

	entry = queue_get_entries(app->services);
	while (entry) {
		if (!database_add_service(entry->data)) {
			error("Failed to add service");
			return false;
		}

		entry = entry->next;
	}

	return true;
}


static void read_reply_cb(DBusMessage *message, void *user_data)
{
	struct pending_op *op = user_data;
	DBusError err;
	DBusMessageIter iter, array;
	uint8_t ecode = 0;
	uint8_t *value = NULL;
	int len = 0;

	if (!op->owner_queue) {
		DBG("Pending read was canceled when object got removed");
		return;
	}

	dbus_error_init(&err);

	if (dbus_set_error_from_message(&err, message) == TRUE) {
		DBG("Failed to read value: %s: %s", err.name, err.message);
		ecode = dbus_error_to_att_ecode(err.name,
					BT_ATT_ERROR_READ_NOT_PERMITTED);
		dbus_error_free(&err);
		goto done;
	}

	dbus_message_iter_init(message, &iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
		/*
		 * Return not supported for this, as the external app basically
		 * doesn't properly support reading from this characteristic.
		 */
		ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
		error("Invalid return value received for \"ReadValue\"");
		goto done;
	}

	dbus_message_iter_recurse(&iter, &array);
	dbus_message_iter_get_fixed_array(&array, &value, &len);

	if (len < 0) {
		ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
		value = NULL;
		len = 0;
		goto done;
	}

	/* Truncate the value if it's too large */
	len = MIN(BT_ATT_MAX_VALUE_LEN, len);
	value = len ? value : NULL;

done:
	gatt_db_attribute_read_result(op->attrib, op->id, ecode, value, len);
}


static void profile_free(void *data)
{
	struct external_profile *profile = data;

	queue_destroy(profile->profiles, profile_remove);

	profile_release(profile);

	g_dbus_proxy_unref(profile->proxy);

	free(profile);
}


static void send_notification_to_device(void *data, void *user_data)
{
	struct device_state *device_state = data;
	struct notify *notify = user_data;
	struct ccc_state *ccc;
	struct btd_device *device;
	struct bt_gatt_server *server;

	if (notify->conf == service_changed_conf) {
		if (device_state->cli_feat[0] &
				BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING) {
			device_state->change_aware = false;
			notify->user_data = device_state;
		}
	}

	ccc = find_ccc_state(device_state, notify->ccc_handle);
	if (!ccc || !(ccc->value & 0x0003))
		return;

	device = btd_adapter_find_device(notify->database->adapter,
						&device_state->bdaddr,
						device_state->bdaddr_type);
	if (!device) {
		/* If ATT has not disconnect yet don't remove the state as it
		 * will eventually be removed when att_disconnected is called.
		 */
		if (device_state->disc_id)
			return;
		goto remove;
	}

	server = btd_device_get_gatt_server(device);
	if (!server) {
		if (!device_is_bonded(device, device_state->bdaddr_type))
			goto remove;
		state_set_pending(device_state, notify);
		return;
	}

	/*
	 * TODO: If the device is not connected but bonded, send the
	 * notification/indication when it becomes connected.
	 */
	if (!(ccc->value & 0x0002)) {
		DBG("GATT server sending notification");
		bt_gatt_server_send_notification(server,
					notify->handle, notify->value,
					notify->len, device_state->cli_feat[0] &
					BT_GATT_CHRC_CLI_FEAT_NFY_MULTI);
		return;
	}

	DBG("GATT server sending indication");
	bt_gatt_server_send_indication(server, notify->handle, notify->value,
						notify->len, notify->conf,
						notify->user_data, NULL);

	return;

remove:
	/* Remove device state if device no longer exists or is not paired */
	if (queue_remove(notify->database->device_states, device_state)) {
		queue_foreach(device_state->ccc_states, clear_ccc_state,
						notify->database);
		device_state_free(device_state);
	}
}


void btd_gatt_database_att_disconnected(struct btd_gatt_database *database,
						struct btd_device *device)
{
	struct bt_gatt_server *server = btd_device_get_gatt_server(device);
	struct bt_att *att = bt_gatt_server_get_att(server);
	struct device_state *state;
	const bdaddr_t *addr;
	uint8_t type;

	DBG("");

	addr = device_get_address(device);
	type = btd_device_get_bdaddr_type(device);

	state = find_device_state(database, addr, type);
	if (!state)
		return;

	if (state->disc_id)
		bt_att_unregister_disconnect(att, state->disc_id);

	att_disconnected(0, state);
}


static void service_changed_conf(void *user_data)
{
	struct device_state *state = user_data;

	DBG("");

	if (!state)
		return;

	state->change_aware = true;
}


find_device_state(struct btd_gatt_database *database, const bdaddr_t *bdaddr,
							uint8_t bdaddr_type)
{
	struct device_info dev_info;

	memset(&dev_info, 0, sizeof(dev_info));

	bacpy(&dev_info.bdaddr, bdaddr);
	dev_info.bdaddr_type = bdaddr_type;

	return queue_find(database->device_states, dev_state_match, &dev_info);
}


static bool incr_attr_count(struct external_service *service, uint16_t incr)
{
	if (service->attr_cnt > UINT16_MAX - incr)
		return false;

	service->attr_cnt += incr;

	return true;
}


static struct pending_op *send_read(struct bt_att *att,
					struct gatt_db_attribute *attrib,
					GDBusProxy *proxy,
					struct queue *owner_queue,
					unsigned int id,
					uint16_t offset)
{
	struct pending_op *op;

	op = pending_read_new(att, owner_queue, attrib, id, offset);

	if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup_cb,
				read_reply_cb, op, pending_op_free) == TRUE)
		return op;

	pending_op_free(op);

	return NULL;
}


static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type)
{
	GIOChannel *io = NULL;
	GError *gerr = NULL;
	int fd;

	fd = bt_att_get_fd(att);
	if (fd < 0)
		return false;

	io = g_io_channel_unix_new(fd);
	if (!io)
		return false;

	bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst,
						BT_IO_OPT_DEST_TYPE, dst_type,
						BT_IO_OPT_INVALID);
	if (gerr) {
		error("gatt: bt_io_get: %s", gerr->message);
		g_error_free(gerr);
		g_io_channel_unref(io);
		return false;
	}

	g_io_channel_unref(io);
	return true;
}


static void cancel_pending_read(void *data)
{
	struct pending_op *op = data;

	gatt_db_attribute_read_result(op->attrib, op->id,
					BT_ATT_ERROR_REQUEST_NOT_SUPPORTED,
					NULL, 0);
	op->owner_queue = NULL;
}


static bool parse_includes(GDBusProxy *proxy, struct external_service *service)
{
	DBusMessageIter iter;
	DBusMessageIter array;
	char *obj;
	char *includes;
	int type;

	/* Includes property is optional */
	if (!g_dbus_proxy_get_property(proxy, "Includes", &iter))
		return true;

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
		return false;

	dbus_message_iter_recurse(&iter, &array);

	while ((type = dbus_message_iter_get_arg_type(&array))
					!= DBUS_TYPE_INVALID) {
		if (type != DBUS_TYPE_OBJECT_PATH)
			return false;

		dbus_message_iter_get_basic(&array, &obj);

		includes = g_strdup(obj);
		if (!includes)
			return false;

		if (!queue_push_tail(service->includes, includes)) {
			error("Failed to add Includes path in queue\n");
			return false;
		}

		incr_attr_count(service, 1);

		dbus_message_iter_next(&array);
	}

	return true;
}


static void client_disconnect_cb(DBusConnection *conn, void *user_data)
{
	struct gatt_app *app = user_data;
	struct btd_gatt_database *database = app->database;

	DBG("Client disconnected");

	if (queue_remove(database->apps, app))
		app_free(app);
}


static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
					unsigned int id, uint16_t offset,
					const uint8_t *value, size_t len,
					uint8_t opcode, struct bt_att *att,
					void *user_data)
{
	struct btd_gatt_database *database = user_data;
	struct ccc_state *ccc;
	struct ccc_cb_data *ccc_cb;
	uint16_t handle, val;
	uint8_t ecode = 0;

	handle = gatt_db_attribute_get_handle(attrib);

	DBG("CCC write called for handle: 0x%04x", handle);

	if (!value || len > 2) {
		ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
		goto done;
	}

	if (offset > 2) {
		ecode = BT_ATT_ERROR_INVALID_OFFSET;
		goto done;
	}

	ccc = get_ccc_state(database, att, handle);
	if (!ccc) {
		ecode = BT_ATT_ERROR_UNLIKELY;
		goto done;
	}

	ccc_cb = queue_find(database->ccc_callbacks, ccc_cb_match_handle,
			UINT_TO_PTR(gatt_db_attribute_get_handle(attrib)));
	if (!ccc_cb) {
		ecode = BT_ATT_ERROR_UNLIKELY;
		goto done;
	}

	if (len == 1)
		val = *value;
	else
		val = get_le16(value);

	/* If value is identical, then just succeed */
	if (val == ccc->value)
		goto done;

	if (ccc_cb->callback) {
		struct pending_op *op;

		op = pending_ccc_new(att, attrib, val,
					bt_att_get_link_type(att));
		if (!op) {
			ecode = BT_ATT_ERROR_UNLIKELY;
			goto done;
		}

		ecode = ccc_cb->callback(op, ccc_cb->user_data);
		if (ecode)
			pending_op_free(op);
	}

	if (!ecode)
		ccc->value = val;

done:
	gatt_db_attribute_write_result(attrib, id, ecode);
}


static int mode_select_pages(SCSIDiskReq *r, uint8_t *p, int len, bool change)
{
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    while (len > 0) {
        int page, subpage, page_len;

        /* Parse both possible formats for the mode page headers.  */
        page = p[0] & 0x3f;
        if (p[0] & 0x40) {
            if (len < 4) {
                goto invalid_param_len;
            }
            subpage = p[1];
            page_len = lduw_be_p(&p[2]);
            p += 4;
            len -= 4;
        } else {
            if (len < 2) {
                goto invalid_param_len;
            }
            subpage = 0;
            page_len = p[1];
            p += 2;
            len -= 2;
        }

        if (subpage) {
            goto invalid_param;
        }
        if (page_len > len) {
            goto invalid_param_len;
        }

        if (!change) {
            if (scsi_disk_check_mode_select(s, page, p, page_len) < 0) {
                goto invalid_param;
            }
        } else {
            scsi_disk_apply_mode_select(s, page, p);
        }

        p += page_len;
        len -= page_len;
    }
    return 0;

invalid_param:
    scsi_check_condition(r, SENSE_CODE(INVALID_PARAM));
    return -1;

invalid_param_len:
    scsi_check_condition(r, SENSE_CODE(INVALID_PARAM_LEN));
    return -1;
}


static void scsi_block_sgio_complete(void *opaque, int ret)
{
    SCSIBlockReq *req = (SCSIBlockReq *)opaque;
    SCSIDiskReq *r = &req->req;
    SCSIDevice *s = r->req.dev;
    sg_io_hdr_t *io_hdr = &req->io_header;

    if (ret == 0) {
        if (io_hdr->host_status != SCSI_HOST_OK) {
            scsi_req_complete_failed(&r->req, io_hdr->host_status);
            scsi_req_unref(&r->req);
            return;
        }

        if (io_hdr->driver_status & SG_ERR_DRIVER_TIMEOUT) {
            ret = BUSY;
        } else {
            ret = io_hdr->status;
        }

        if (ret > 0) {
            aio_context_acquire(blk_get_aio_context(s->conf.blk));
            if (scsi_handle_rw_error(r, ret, true)) {
                aio_context_release(blk_get_aio_context(s->conf.blk));
                scsi_req_unref(&r->req);
                return;
            }
            aio_context_release(blk_get_aio_context(s->conf.blk));

            /* Ignore error.  */
            ret = 0;
        }
    }

    req->cb(req->cb_opaque, ret);
}


static void scsi_block_class_initfn(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass);
    SCSIDiskClass *sdc = SCSI_DISK_BASE_CLASS(klass);

    sc->realize      = scsi_block_realize;
    sc->alloc_req    = scsi_block_new_request;
    sc->parse_cdb    = scsi_block_parse_cdb;
    sdc->dma_readv   = scsi_block_dma_readv;
    sdc->dma_writev  = scsi_block_dma_writev;
    sdc->update_sense = scsi_block_update_sense;
    sdc->need_fua_emulation = scsi_block_no_fua;
    dc->desc = "SCSI block device passthrough";
    device_class_set_props(dc, scsi_block_properties);
    dc->vmsd  = &vmstate_scsi_disk_state;
}


static inline bool media_is_cd(SCSIDiskState *s)
{
    uint64_t nb_sectors;
    if (s->qdev.type != TYPE_ROM) {
        return false;
    }
    if (!blk_is_available(s->qdev.conf.blk)) {
        return false;
    }
    blk_get_geometry(s->qdev.conf.blk, &nb_sectors);
    return nb_sectors <= CD_MAX_SECTORS;
}


static void scsi_unmap_complete(void *opaque, int ret)
{
    UnmapCBData *data = opaque;
    SCSIDiskReq *r = data->r;
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    assert(r->req.aiocb != NULL);
    r->req.aiocb = NULL;

    aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
    if (scsi_disk_req_check_error(r, ret, true)) {
        scsi_req_unref(&r->req);
        g_free(data);
    } else {
        block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
        scsi_unmap_complete_noio(data, ret);
    }
    aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
}


static void scsi_dma_complete(void *opaque, int ret)
{
    SCSIDiskReq *r = (SCSIDiskReq *)opaque;
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    assert(r->req.aiocb != NULL);
    r->req.aiocb = NULL;

    aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
    if (ret < 0) {
        block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
    } else {
        block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
    }
    scsi_dma_complete_noio(r, ret);
    aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
}


static void scsi_do_read_cb(void *opaque, int ret)
{
    SCSIDiskReq *r = (SCSIDiskReq *)opaque;
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    assert (r->req.aiocb != NULL);
    r->req.aiocb = NULL;

    aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
    if (ret < 0) {
        block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
    } else {
        block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
    }
    scsi_do_read(opaque, ret);
    aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
}


static void scsi_init_iovec(SCSIDiskReq *r, size_t size)
{
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    if (!r->iov.iov_base) {
        r->buflen = size;
        r->iov.iov_base = blk_blockalign(s->qdev.conf.blk, r->buflen);
    }
    r->iov.iov_len = MIN(r->sector_count * BDRV_SECTOR_SIZE, r->buflen);
    qemu_iovec_init_external(&r->qiov, &r->iov, 1);
}


static void scsi_write_complete_noio(SCSIDiskReq *r, int ret)
{
    uint32_t n;

    assert (r->req.aiocb == NULL);
    if (scsi_disk_req_check_error(r, ret, false)) {
        goto done;
    }

    n = r->qiov.size / BDRV_SECTOR_SIZE;
    r->sector += n;
    r->sector_count -= n;
    if (r->sector_count == 0) {
        scsi_write_do_fua(r);
        return;
    } else {
        scsi_init_iovec(r, SCSI_DMA_BUF_SIZE);
        trace_scsi_disk_write_complete_noio(r->req.tag, r->qiov.size);
        scsi_req_data(&r->req, r->qiov.size);
    }

done:
    scsi_req_unref(&r->req);
}


static void scsi_read_complete(void *opaque, int ret)
{
    SCSIDiskReq *r = (SCSIDiskReq *)opaque;
    SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev);

    assert(r->req.aiocb != NULL);
    r->req.aiocb = NULL;

    aio_context_acquire(blk_get_aio_context(s->qdev.conf.blk));
    if (ret < 0) {
        block_acct_failed(blk_get_stats(s->qdev.conf.blk), &r->acct);
    } else {
        block_acct_done(blk_get_stats(s->qdev.conf.blk), &r->acct);
        trace_scsi_disk_read_complete(r->req.tag, r->qiov.size);
    }
    scsi_read_complete_noio(r, ret);
    aio_context_release(blk_get_aio_context(s->qdev.conf.blk));
}


ex_put(exarg_T *eap)
{
    // ":0put" works like ":1put!".
    if (eap->line2 == 0)
    {
	eap->line2 = 1;
	eap->forceit = TRUE;
    }
    curwin->w_cursor.lnum = eap->line2;
    check_cursor_col();
    do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1L,
						       PUT_LINE|PUT_CURSLINE);
}


int getnameinfo(const struct sockaddr *sa,
		socklen_t addrlen,
		char *host,
		socklen_t hostlen,
		char *serv,
		socklen_t servlen,
		unsigned flags)
{
	int serrno = errno;
	bool ok = 0;
	struct hostent *hoste = NULL;
	char domain[256];

	if (flags & ~(NI_NUMERICHOST|NI_NUMERICSERV|NI_NOFQDN|NI_NAMEREQD|NI_DGRAM))
		return EAI_BADFLAGS;

	if (sa == NULL || addrlen < sizeof(sa_family_t))
		return EAI_FAMILY;

	if ((flags & NI_NAMEREQD) && host == NULL && serv == NULL)
		return EAI_NONAME;

	if (sa->sa_family == AF_LOCAL) /* valid */;
#ifdef __UCLIBC_HAS_IPV4__
	else if (sa->sa_family == AF_INET) {
		if (addrlen < sizeof(struct sockaddr_in))
			return EAI_FAMILY;
	}
#endif
#ifdef __UCLIBC_HAS_IPV6__
	else if (sa->sa_family == AF_INET6) {
		if (addrlen < sizeof(struct sockaddr_in6))
			return EAI_FAMILY;
	}
#endif
	else
		return EAI_FAMILY;

	if (host != NULL && hostlen > 0)
		switch (sa->sa_family) {
		case AF_INET:
#ifdef __UCLIBC_HAS_IPV6__
		case AF_INET6:
#endif
			if (!(flags & NI_NUMERICHOST)) {
				if (0) /* nothing */;
#ifdef __UCLIBC_HAS_IPV6__
				else if (sa->sa_family == AF_INET6)
					hoste = gethostbyaddr((const void *)
						&(((const struct sockaddr_in6 *) sa)->sin6_addr),
						sizeof(struct in6_addr), AF_INET6);
#endif
#ifdef __UCLIBC_HAS_IPV4__
				else
					hoste = gethostbyaddr((const void *)
						&(((const struct sockaddr_in *)sa)->sin_addr),
						sizeof(struct in_addr), AF_INET);
#endif

				if (hoste) {
					char *c;
					if ((flags & NI_NOFQDN)
					 && (getdomainname(domain, sizeof(domain)) == 0)
					 && (c = strstr(hoste->h_name, domain)) != NULL
					 && (c != hoste->h_name) && (*(--c) == '.')
					) {
						strncpy(host, hoste->h_name,
							MIN(hostlen, (size_t) (c - hoste->h_name)));
						host[MIN(hostlen - 1, (size_t) (c - hoste->h_name))] = '\0';
					} else {
						strncpy(host, hoste->h_name, hostlen);
					}
					ok = 1;
				}
			}

			if (!ok) {
				const char *c = NULL;

				if (flags & NI_NAMEREQD) {
					errno = serrno;
					return EAI_NONAME;
				}
				if (0) /* nothing */;
#ifdef __UCLIBC_HAS_IPV6__
				else if (sa->sa_family == AF_INET6) {
					const struct sockaddr_in6 *sin6p;

					sin6p = (const struct sockaddr_in6 *) sa;
					c = inet_ntop(AF_INET6,
						(const void *) &sin6p->sin6_addr,
						host, hostlen);
#if 0
					/* Does scope id need to be supported? */
					uint32_t scopeid;
					scopeid = sin6p->sin6_scope_id;
					if (scopeid != 0) {
						/* Buffer is >= IFNAMSIZ+1.  */
						char scopebuf[IFNAMSIZ + 1];
						char *scopeptr;
						int ni_numericscope = 0;
						size_t real_hostlen = strnlen(host, hostlen);
						size_t scopelen = 0;

						scopebuf[0] = SCOPE_DELIMITER;
						scopebuf[1] = '\0';
						scopeptr = &scopebuf[1];

						if (IN6_IS_ADDR_LINKLOCAL(&sin6p->sin6_addr)
						    || IN6_IS_ADDR_MC_LINKLOCAL(&sin6p->sin6_addr)) {
							if (if_indextoname(scopeid, scopeptr) == NULL)
								++ni_numericscope;
							else
								scopelen = strlen(scopebuf);
						} else {
							++ni_numericscope;
						}

						if (ni_numericscope)
							scopelen = 1 + snprintf(scopeptr,
								(scopebuf
								+ sizeof scopebuf
								- scopeptr),
								"%u", scopeid);

						if (real_hostlen + scopelen + 1 > hostlen)
							return EAI_SYSTEM;
						memcpy(host + real_hostlen, scopebuf, scopelen + 1);
					}
#endif
				}
#endif /* __UCLIBC_HAS_IPV6__ */
#if defined __UCLIBC_HAS_IPV4__
				else {
					c = inet_ntop(AF_INET, (const void *)
						&(((const struct sockaddr_in *) sa)->sin_addr),
						host, hostlen);
				}
#endif
				if (c == NULL) {
					errno = serrno;
					return EAI_SYSTEM;
				}
				ok = 1;
			}
			break;

		case AF_LOCAL:
			if (!(flags & NI_NUMERICHOST)) {
				struct utsname utsname;

				if (!uname(&utsname)) {
					strncpy(host, utsname.nodename, hostlen);
					break;
				};
			};

			if (flags & NI_NAMEREQD) {
				errno = serrno;
				return EAI_NONAME;
			}

			strncpy(host, "localhost", hostlen);
			break;
/* Already checked above
		default:
			return EAI_FAMILY;
*/
	}

	if (serv && (servlen > 0)) {
		if (sa->sa_family == AF_LOCAL) {
			strncpy(serv, ((const struct sockaddr_un *) sa)->sun_path, servlen);
		} else { /* AF_INET || AF_INET6 */
			if (!(flags & NI_NUMERICSERV)) {
				struct servent *s;
				s = getservbyport(((const struct sockaddr_in *) sa)->sin_port,
				      ((flags & NI_DGRAM) ? "udp" : "tcp"));
				if (s) {
					strncpy(serv, s->s_name, servlen);
					goto DONE;
				}
			}
			snprintf(serv, servlen, "%d",
				ntohs(((const struct sockaddr_in *) sa)->sin_port));
		}
	}
DONE:
	if (host && (hostlen > 0))
		host[hostlen-1] = 0;
	if (serv && (servlen > 0))
		serv[servlen-1] = 0;
	errno = serrno;
	return 0;
}


_archive_write_disk_filter_bytes(struct archive *_a, int n)
{
	struct archive_write_disk *a = (struct archive_write_disk *)_a;
	(void)n; /* UNUSED */
	if (n == -1 || n == 0)
		return (a->total_bytes_written);
	return (-1);
}


archive_write_disk_vtable(void)
{
	static struct archive_vtable av;
	static int inited = 0;

	if (!inited) {
		av.archive_close = _archive_write_disk_close;
		av.archive_filter_bytes = _archive_write_disk_filter_bytes;
		av.archive_free = _archive_write_disk_free;
		av.archive_write_header = _archive_write_disk_header;
		av.archive_write_finish_entry
		    = _archive_write_disk_finish_entry;
		av.archive_write_data = _archive_write_disk_data;
		av.archive_write_data_block = _archive_write_disk_data_block;
		inited = 1;
	}
	return (&av);
}


restore_entry(struct archive_write_disk *a)
{
	int ret = ARCHIVE_OK, en;

	if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) {
		/*
		 * TODO: Fix this.  Apparently, there are platforms
		 * that still allow root to hose the entire filesystem
		 * by unlinking a dir.  The S_ISDIR() test above
		 * prevents us from using unlink() here if the new
		 * object is a dir, but that doesn't mean the old
		 * object isn't a dir.
		 */
		if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
			(void)clear_nochange_fflags(a);
		if (unlink(a->name) == 0) {
			/* We removed it, reset cached stat. */
			a->pst = NULL;
		} else if (errno == ENOENT) {
			/* File didn't exist, that's just as good. */
		} else if (rmdir(a->name) == 0) {
			/* It was a dir, but now it's gone. */
			a->pst = NULL;
		} else {
			/* We tried, but couldn't get rid of it. */
			archive_set_error(&a->archive, errno,
			    "Could not unlink");
			return(ARCHIVE_FAILED);
		}
	}

	/* Try creating it first; if this fails, we'll try to recover. */
	en = create_filesystem_object(a);

	if ((en == ENOTDIR || en == ENOENT)
	    && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) {
		/* If the parent dir doesn't exist, try creating it. */
		create_parent_dir(a, a->name);
		/* Now try to create the object again. */
		en = create_filesystem_object(a);
	}

	if ((en == ENOENT) && (archive_entry_hardlink(a->entry) != NULL)) {
		archive_set_error(&a->archive, en,
		    "Hard-link target '%s' does not exist.",
		    archive_entry_hardlink(a->entry));
		return (ARCHIVE_FAILED);
	}

	if ((en == EISDIR || en == EEXIST)
	    && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
		/* If we're not overwriting, we're done. */
		if (S_ISDIR(a->mode)) {
			/* Don't overwrite any settings on existing directories. */
			a->todo = 0;
		}
		archive_entry_unset_size(a->entry);
		return (ARCHIVE_OK);
	}

	/*
	 * Some platforms return EISDIR if you call
	 * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some
	 * return EEXIST.  POSIX is ambiguous, requiring EISDIR
	 * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT)
	 * on an existing item.
	 */
	if (en == EISDIR) {
		/* A dir is in the way of a non-dir, rmdir it. */
		if (rmdir(a->name) != 0) {
			archive_set_error(&a->archive, errno,
			    "Can't remove already-existing dir");
			return (ARCHIVE_FAILED);
		}
		a->pst = NULL;
		/* Try again. */
		en = create_filesystem_object(a);
	} else if (en == EEXIST) {
		/*
		 * We know something is in the way, but we don't know what;
		 * we need to find out before we go any further.
		 */
		int r = 0;
		/*
		 * The SECURE_SYMLINKS logic has already removed a
		 * symlink to a dir if the client wants that.  So
		 * follow the symlink if we're creating a dir.
		 */
		if (S_ISDIR(a->mode))
			r = la_stat(a->name, &a->st);
		/*
		 * If it's not a dir (or it's a broken symlink),
		 * then don't follow it.
		 */
		if (r != 0 || !S_ISDIR(a->mode))
			r = lstat(a->name, &a->st);
		if (r != 0) {
			archive_set_error(&a->archive, errno,
			    "Can't stat existing object");
			return (ARCHIVE_FAILED);
		}

		/*
		 * NO_OVERWRITE_NEWER doesn't apply to directories.
		 */
		if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER)
		    &&  !S_ISDIR(a->st.st_mode)) {
			if (!older(&(a->st), a->entry)) {
				archive_entry_unset_size(a->entry);
				return (ARCHIVE_OK);
			}
		}

		/* If it's our archive, we're done. */
		if (a->skip_file_set &&
		    a->st.st_dev == (dev_t)a->skip_file_dev &&
		    a->st.st_ino == (ino_t)a->skip_file_ino) {
			archive_set_error(&a->archive, 0,
			    "Refusing to overwrite archive");
			return (ARCHIVE_FAILED);
		}

		if (!S_ISDIR(a->st.st_mode)) {
			if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
				(void)clear_nochange_fflags(a);

			if ((a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) &&
			    S_ISREG(a->st.st_mode)) {
				/* Use a temporary file to extract */
				if ((a->fd = la_mktemp(a)) == -1) {
					archive_set_error(&a->archive, errno,
					    "Can't create temporary file");
					return ARCHIVE_FAILED;
				}
				a->pst = NULL;
				en = 0;
			} else {
				/* A non-dir is in the way, unlink it. */
				if (unlink(a->name) != 0) {
					archive_set_error(&a->archive, errno,
					    "Can't unlink already-existing "
					    "object");
					return (ARCHIVE_FAILED);
				}
				a->pst = NULL;
				/* Try again. */
				en = create_filesystem_object(a);
			}
		} else if (!S_ISDIR(a->mode)) {
			/* A dir is in the way of a non-dir, rmdir it. */
			if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
				(void)clear_nochange_fflags(a);
			if (rmdir(a->name) != 0) {
				archive_set_error(&a->archive, errno,
				    "Can't replace existing directory with non-directory");
				return (ARCHIVE_FAILED);
			}
			/* Try again. */
			en = create_filesystem_object(a);
		} else {
			/*
			 * There's a dir in the way of a dir.  Don't
			 * waste time with rmdir()/mkdir(), just fix
			 * up the permissions on the existing dir.
			 * Note that we don't change perms on existing
			 * dirs unless _EXTRACT_PERM is specified.
			 */
			if ((a->mode != a->st.st_mode)
			    && (a->todo & TODO_MODE_FORCE))
				a->deferred |= (a->todo & TODO_MODE);
			/* Ownership doesn't need deferred fixup. */
			en = 0; /* Forget the EEXIST. */
		}
	}

	if (en) {
		/* Everything failed; give up here. */
		if ((&a->archive)->error == NULL)
			archive_set_error(&a->archive, en, "Can't create '%s'",
			    a->name);
		return (ARCHIVE_FAILED);
	}

	a->pst = NULL; /* Cached stat data no longer valid. */
	return (ret);
}


older(struct stat *st, struct archive_entry *entry)
{
	/* First, test the seconds and return if we have a definite answer. */
	/* Definitely older. */
	if (to_int64_time(st->st_mtime) < to_int64_time(archive_entry_mtime(entry)))
		return (1);
	/* Definitely younger. */
	if (to_int64_time(st->st_mtime) > to_int64_time(archive_entry_mtime(entry)))
		return (0);
	/* If this platform supports fractional seconds, try those. */
#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
	/* Definitely older. */
	if (st->st_mtimespec.tv_nsec < archive_entry_mtime_nsec(entry))
		return (1);
#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
	/* Definitely older. */
	if (st->st_mtim.tv_nsec < archive_entry_mtime_nsec(entry))
		return (1);
#elif HAVE_STRUCT_STAT_ST_MTIME_N
	/* older. */
	if (st->st_mtime_n < archive_entry_mtime_nsec(entry))
		return (1);
#elif HAVE_STRUCT_STAT_ST_UMTIME
	/* older. */
	if (st->st_umtime * 1000 < archive_entry_mtime_nsec(entry))
		return (1);
#elif HAVE_STRUCT_STAT_ST_MTIME_USEC
	/* older. */
	if (st->st_mtime_usec * 1000 < archive_entry_mtime_nsec(entry))
		return (1);
#else
	/* This system doesn't have high-res timestamps. */
#endif
	/* Same age or newer, so not older. */
	return (0);
}


hfs_reset_compressor(struct archive_write_disk *a)
{
	int ret;

	if (a->stream_valid)
		ret = deflateReset(&a->stream);
	else
		ret = deflateInit(&a->stream, a->decmpfs_compression_level);

	if (ret != Z_OK) {
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Failed to initialize compressor");
		return (ARCHIVE_FATAL);
	} else
		a->stream_valid = 1;

	return (ARCHIVE_OK);
}


translate_acl(struct archive_read_disk *a,
    struct archive_entry *entry, acl_t acl, int default_entry_acl_type)
{
	acl_tag_t	 acl_tag;
	acl_entry_t	 acl_entry;
	acl_permset_t	 acl_permset;
	int		 i, entry_acl_type;
	int		 r, s, ae_id, ae_tag, ae_perm;
	void		*q;
	const char	*ae_name;

	s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
	if (s == -1) {
		archive_set_error(&a->archive, errno,
		    "Failed to get first ACL entry");
		return (ARCHIVE_WARN);
	}

	while (s == 1) {
		ae_id = -1;
		ae_name = NULL;
		ae_perm = 0;

		if (acl_get_tag_type(acl_entry, &acl_tag) != 0) {
			archive_set_error(&a->archive, errno,
			    "Failed to get ACL tag type");
			return (ARCHIVE_WARN);
		}
		switch (acl_tag) {
		case ACL_USER:
			q = acl_get_qualifier(acl_entry);
			if (q != NULL) {
				ae_id = (int)*(uid_t *)q;
				acl_free(q);
				ae_name = archive_read_disk_uname(&a->archive,
				    ae_id);
			}
			ae_tag = ARCHIVE_ENTRY_ACL_USER;
			break;
		case ACL_GROUP:
			q = acl_get_qualifier(acl_entry);
			if (q != NULL) {
				ae_id = (int)*(gid_t *)q;
				acl_free(q);
				ae_name = archive_read_disk_gname(&a->archive,
				    ae_id);
			}
			ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
			break;
		case ACL_MASK:
			ae_tag = ARCHIVE_ENTRY_ACL_MASK;
			break;
		case ACL_USER_OBJ:
			ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
			break;
		case ACL_GROUP_OBJ:
			ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
			break;
		case ACL_OTHER:
			ae_tag = ARCHIVE_ENTRY_ACL_OTHER;
			break;
		default:
			/* Skip types that libarchive can't support. */
			s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
			continue;
		}

		// XXX acl_type maps to allow/deny/audit/YYYY bits
		entry_acl_type = default_entry_acl_type;

		if (acl_get_permset(acl_entry, &acl_permset) != 0) {
			archive_set_error(&a->archive, errno,
			    "Failed to get ACL permission set");
			return (ARCHIVE_WARN);
		}

		for (i = 0; i < acl_posix_perm_map_size; ++i) {
			r = acl_get_perm(acl_permset,
			    acl_posix_perm_map[i].p_perm);
			if (r == -1) {
				archive_set_error(&a->archive, errno,
				    "Failed to check permission in an ACL "
				    "permission set");
				return (ARCHIVE_WARN);
			} else if (r)
				ae_perm |= acl_posix_perm_map[i].a_perm;
		}

		archive_entry_acl_add_entry(entry, entry_acl_type,
					    ae_perm, ae_tag,
					    ae_id, ae_name);

		s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
		if (s == -1) {
			archive_set_error(&a->archive, errno,
			    "Failed to get next ACL entry");
			return (ARCHIVE_WARN);
		}
	}
	return (ARCHIVE_OK);
}


translate_acl(struct archive_read_disk *a,
    struct archive_entry *entry, acl_t acl, int default_entry_acl_type)
{
#if ARCHIVE_ACL_FREEBSD_NFS4
	int brand;
	acl_flagset_t	 acl_flagset;
	acl_entry_type_t acl_type;
#endif
	acl_tag_t	 acl_tag;
	acl_entry_t	 acl_entry;
	acl_permset_t	 acl_permset;
	int		 i, entry_acl_type, perm_map_size;
	const acl_perm_map_t	*perm_map;
	int		 r, s, ae_id, ae_tag, ae_perm;
	void		*q;
	const char	*ae_name;

#if ARCHIVE_ACL_FREEBSD_NFS4
	// FreeBSD "brands" ACLs as POSIX.1e or NFSv4
	// Make sure the "brand" on this ACL is consistent
	// with the default_entry_acl_type bits provided.
	if (acl_get_brand_np(acl, &brand) != 0) {
		archive_set_error(&a->archive, errno,
		    "Failed to read ACL brand");
		return (ARCHIVE_WARN);
	}
	switch (brand) {
	case ACL_BRAND_POSIX:
		switch (default_entry_acl_type) {
		case ARCHIVE_ENTRY_ACL_TYPE_ACCESS:
		case ARCHIVE_ENTRY_ACL_TYPE_DEFAULT:
			break;
		default:
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Invalid ACL entry type for POSIX.1e ACL");
			return (ARCHIVE_WARN);
		}
		break;
	case ACL_BRAND_NFS4:
		if (default_entry_acl_type & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) {
			archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
			    "Invalid ACL entry type for NFSv4 ACL");
			return (ARCHIVE_WARN);
		}
		break;
	default:
		archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
		    "Unknown ACL brand");
		return (ARCHIVE_WARN);
	}
#endif

	s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
	if (s == -1) {
		archive_set_error(&a->archive, errno,
		    "Failed to get first ACL entry");
		return (ARCHIVE_WARN);
	}

	while (s == 1) {
		ae_id = -1;
		ae_name = NULL;
		ae_perm = 0;

		if (acl_get_tag_type(acl_entry, &acl_tag) != 0) {
			archive_set_error(&a->archive, errno,
			    "Failed to get ACL tag type");
			return (ARCHIVE_WARN);
		}
		switch (acl_tag) {
		case ACL_USER:
			q = acl_get_qualifier(acl_entry);
			if (q != NULL) {
				ae_id = (int)*(uid_t *)q;
				acl_free(q);
				ae_name = archive_read_disk_uname(&a->archive,
				    ae_id);
			}
			ae_tag = ARCHIVE_ENTRY_ACL_USER;
			break;
		case ACL_GROUP:
			q = acl_get_qualifier(acl_entry);
			if (q != NULL) {
				ae_id = (int)*(gid_t *)q;
				acl_free(q);
				ae_name = archive_read_disk_gname(&a->archive,
				    ae_id);
			}
			ae_tag = ARCHIVE_ENTRY_ACL_GROUP;
			break;
		case ACL_MASK:
			ae_tag = ARCHIVE_ENTRY_ACL_MASK;
			break;
		case ACL_USER_OBJ:
			ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ;
			break;
		case ACL_GROUP_OBJ:
			ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ;
			break;
		case ACL_OTHER:
			ae_tag = ARCHIVE_ENTRY_ACL_OTHER;
			break;
#if ARCHIVE_ACL_FREEBSD_NFS4
		case ACL_EVERYONE:
			ae_tag = ARCHIVE_ENTRY_ACL_EVERYONE;
			break;
#endif
		default:
			/* Skip types that libarchive can't support. */
			s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
			continue;
		}

		// XXX acl_type maps to allow/deny/audit/YYYY bits
		entry_acl_type = default_entry_acl_type;

#if ARCHIVE_ACL_FREEBSD_NFS4
		if (default_entry_acl_type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) {
			/*
			 * acl_get_entry_type_np() fails with non-NFSv4 ACLs
			 */
			if (acl_get_entry_type_np(acl_entry, &acl_type) != 0) {
				archive_set_error(&a->archive, errno, "Failed "
				    "to get ACL type from a NFSv4 ACL entry");
				return (ARCHIVE_WARN);
			}
			switch (acl_type) {
			case ACL_ENTRY_TYPE_ALLOW:
				entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALLOW;
				break;
			case ACL_ENTRY_TYPE_DENY:
				entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_DENY;
				break;
			case ACL_ENTRY_TYPE_AUDIT:
				entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_AUDIT;
				break;
			case ACL_ENTRY_TYPE_ALARM:
				entry_acl_type = ARCHIVE_ENTRY_ACL_TYPE_ALARM;
				break;
			default:
				archive_set_error(&a->archive, errno,
				    "Invalid NFSv4 ACL entry type");
				return (ARCHIVE_WARN);
			}

			/*
			 * Libarchive stores "flag" (NFSv4 inheritance bits)
			 * in the ae_perm bitmap.
			 *
			 * acl_get_flagset_np() fails with non-NFSv4 ACLs
			 */
			if (acl_get_flagset_np(acl_entry, &acl_flagset) != 0) {
				archive_set_error(&a->archive, errno,
				    "Failed to get flagset from a NFSv4 "
				    "ACL entry");
				return (ARCHIVE_WARN);
			}
			for (i = 0; i < acl_nfs4_flag_map_size; ++i) {
				r = acl_get_flag_np(acl_flagset,
				    acl_nfs4_flag_map[i].p_perm);
				if (r == -1) {
					archive_set_error(&a->archive, errno,
					    "Failed to check flag in a NFSv4 "
					    "ACL flagset");
					return (ARCHIVE_WARN);
				} else if (r)
					ae_perm |= acl_nfs4_flag_map[i].a_perm;
			}
		}
#endif

		if (acl_get_permset(acl_entry, &acl_permset) != 0) {
			archive_set_error(&a->archive, errno,
			    "Failed to get ACL permission set");
			return (ARCHIVE_WARN);
		}

#if ARCHIVE_ACL_FREEBSD_NFS4
		if (default_entry_acl_type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) {
			perm_map_size = acl_nfs4_perm_map_size;
			perm_map = acl_nfs4_perm_map;
		} else {
#endif
			perm_map_size = acl_posix_perm_map_size;
			perm_map = acl_posix_perm_map;
#if ARCHIVE_ACL_FREEBSD_NFS4
		}
#endif

		for (i = 0; i < perm_map_size; ++i) {
			r = acl_get_perm_np(acl_permset, perm_map[i].p_perm);
			if (r == -1) {
				archive_set_error(&a->archive, errno,
				    "Failed to check permission in an ACL "
				    "permission set");
				return (ARCHIVE_WARN);
			} else if (r)
				ae_perm |= perm_map[i].a_perm;
		}

		archive_entry_acl_add_entry(entry, entry_acl_type,
					    ae_perm, ae_tag,
					    ae_id, ae_name);

		s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
		if (s == -1) {
			archive_set_error(&a->archive, errno,
			    "Failed to get next ACL entry");
			return (ARCHIVE_WARN);
		}
	}
	return (ARCHIVE_OK);
}


qemuProcessEndJob(virQEMUDriverPtr driver,
                  virDomainObjPtr vm)
{
    qemuDomainObjEndAsyncJob(driver, vm);
}


qemuProcessHandleAgentEOF(qemuAgentPtr agent,
                          virDomainObjPtr vm)
{
    qemuDomainObjPrivatePtr priv;

    VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name);

    virObjectLock(vm);

    priv = vm->privateData;

    if (!priv->agent) {
        VIR_DEBUG("Agent freed already");
        goto unlock;
    }

    if (priv->beingDestroyed) {
        VIR_DEBUG("Domain is being destroyed, agent EOF is expected");
        goto unlock;
    }

    qemuAgentClose(agent);
    priv->agent = NULL;
    priv->agentError = false;

    virObjectUnlock(vm);
    return;

 unlock:
    virObjectUnlock(vm);
    return;
}


qemuProcessHandleTrayChange(qemuMonitorPtr mon G_GNUC_UNUSED,
                            virDomainObjPtr vm,
                            const char *devAlias,
                            const char *devid,
                            int reason,
                            void *opaque)
{
    virQEMUDriverPtr driver = opaque;
    virObjectEventPtr event = NULL;
    virDomainDiskDefPtr disk;
    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);

    virObjectLock(vm);
    disk = qemuProcessFindDomainDiskByAliasOrQOM(vm, devAlias, devid);

    if (disk) {
        event = virDomainEventTrayChangeNewFromObj(vm, disk->info.alias, reason);
        /* Update disk tray status */
        if (reason == VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN)
            disk->tray_status = VIR_DOMAIN_DISK_TRAY_OPEN;
        else if (reason == VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE)
            disk->tray_status = VIR_DOMAIN_DISK_TRAY_CLOSED;

        if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) {
            VIR_WARN("Unable to save status on vm %s after tray moved event",
                     vm->def->name);
        }

        virDomainObjBroadcast(vm);
    }

    virObjectUnlock(vm);
    virObjectEventStateQueue(driver->domainEventState, event);
    return 0;
}


qemuProcessSetupIOThread(virDomainObjPtr vm,
                         virDomainIOThreadIDDefPtr iothread)
{
    return qemuProcessSetupPid(vm, iothread->thread_id,
                               VIR_CGROUP_THREAD_IOTHREAD,
                               iothread->iothread_id,
                               iothread->cpumask,
                               vm->def->cputune.iothread_period,
                               vm->def->cputune.iothread_quota,
                               &iothread->sched);
}


qemuProcessQMPStop(qemuProcessQMPPtr proc)
{
    if (proc->mon) {
        virObjectUnlock(proc->mon);
        qemuMonitorClose(proc->mon);
        proc->mon = NULL;
    }

    if (proc->cmd) {
        virCommandAbort(proc->cmd);
        virCommandFree(proc->cmd);
        proc->cmd = NULL;
    }

    if (proc->monpath)
        unlink(proc->monpath);

    virDomainObjEndAPI(&proc->vm);

    if (proc->pid != 0) {
        VIR_DEBUG("Killing QMP caps process %lld", (long long)proc->pid);
        if (virProcessKill(proc->pid, SIGKILL) < 0 && errno != ESRCH)
            VIR_ERROR(_("Failed to kill process %lld: %s"),
                      (long long)proc->pid,
                      g_strerror(errno));

        proc->pid = 0;
    }

    if (proc->pidfile)
        unlink(proc->pidfile);

    if (proc->uniqDir)
        rmdir(proc->uniqDir);
}


qemuProcessStartCPUs(virQEMUDriverPtr driver, virDomainObjPtr vm,
                     virDomainRunningReason reason,
                     qemuDomainAsyncJob asyncJob)
{
    int ret = -1;
    qemuDomainObjPrivatePtr priv = vm->privateData;
    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);

    /* Bring up netdevs before starting CPUs */
    if (qemuInterfaceStartDevices(vm->def) < 0)
       return -1;

    VIR_DEBUG("Using lock state '%s'", NULLSTR(priv->lockState));
    if (virDomainLockProcessResume(driver->lockManager, cfg->uri,
                                   vm, priv->lockState) < 0) {
        /* Don't free priv->lockState on error, because we need
         * to make sure we have state still present if the user
         * tries to resume again
         */
        return -1;
    }
    VIR_FREE(priv->lockState);

    priv->runningReason = reason;

    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
        goto release;

    ret = qemuMonitorStartCPUs(priv->mon);
    if (qemuDomainObjExitMonitor(driver, vm) < 0)
        ret = -1;

    if (ret < 0)
        goto release;

    /* The RESUME event handler will change the domain state with the reason
     * saved in priv->runningReason and it will also emit corresponding domain
     * lifecycle event.
     */

    return ret;

 release:
    priv->runningReason = VIR_DOMAIN_RUNNING_UNKNOWN;
    if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0)
        VIR_WARN("Unable to release lease on %s", vm->def->name);
    VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState));
    return ret;
}


qemuProcessStart(virConnectPtr conn,
                 virQEMUDriverPtr driver,
                 virDomainObjPtr vm,
                 virCPUDefPtr updatedCPU,
                 qemuDomainAsyncJob asyncJob,
                 const char *migrateFrom,
                 int migrateFd,
                 const char *migratePath,
                 virDomainMomentObjPtr snapshot,
                 virNetDevVPortProfileOp vmop,
                 unsigned int flags)
{
    qemuDomainObjPrivatePtr priv = vm->privateData;
    qemuProcessIncomingDefPtr incoming = NULL;
    unsigned int stopFlags;
    bool relabel = false;
    bool relabelSavedState = false;
    int ret = -1;
    int rv;

    VIR_DEBUG("conn=%p driver=%p vm=%p name=%s id=%d asyncJob=%s "
              "migrateFrom=%s migrateFd=%d migratePath=%s "
              "snapshot=%p vmop=%d flags=0x%x",
              conn, driver, vm, vm->def->name, vm->def->id,
              qemuDomainAsyncJobTypeToString(asyncJob),
              NULLSTR(migrateFrom), migrateFd, NULLSTR(migratePath),
              snapshot, vmop, flags);

    virCheckFlagsGoto(VIR_QEMU_PROCESS_START_COLD |
                      VIR_QEMU_PROCESS_START_PAUSED |
                      VIR_QEMU_PROCESS_START_AUTODESTROY |
                      VIR_QEMU_PROCESS_START_GEN_VMID, cleanup);

    if (!migrateFrom && !snapshot)
        flags |= VIR_QEMU_PROCESS_START_NEW;

    if (qemuProcessInit(driver, vm, updatedCPU,
                        asyncJob, !!migrateFrom, flags) < 0)
        goto cleanup;

    if (migrateFrom) {
        incoming = qemuProcessIncomingDefNew(priv->qemuCaps, NULL, migrateFrom,
                                             migrateFd, migratePath);
        if (!incoming)
            goto stop;
    }

    if (qemuProcessPrepareDomain(driver, vm, flags) < 0)
        goto stop;

    if (qemuProcessPrepareHost(driver, vm, flags) < 0)
        goto stop;

    if (migratePath) {
        if (qemuSecuritySetSavedStateLabel(driver->securityManager,
                                           vm->def, migratePath) < 0)
            goto cleanup;
        relabelSavedState = true;
    }

    if ((rv = qemuProcessLaunch(conn, driver, vm, asyncJob, incoming,
                                snapshot, vmop, flags)) < 0) {
        if (rv == -2)
            relabel = true;
        goto stop;
    }
    relabel = true;

    if (incoming) {
        if (incoming->deferredURI &&
            qemuMigrationDstRun(driver, vm, incoming->deferredURI, asyncJob) < 0)
            goto stop;
    } else {
        /* Refresh state of devices from QEMU. During migration this happens
         * in qemuMigrationDstFinish to ensure that state information is fully
         * transferred. */
        if (qemuProcessRefreshState(driver, vm, asyncJob) < 0)
            goto stop;
    }

    if (qemuProcessFinishStartup(driver, vm, asyncJob,
                                 !(flags & VIR_QEMU_PROCESS_START_PAUSED),
                                 incoming ?
                                 VIR_DOMAIN_PAUSED_MIGRATION :
                                 VIR_DOMAIN_PAUSED_USER) < 0)
        goto stop;

    if (!incoming) {
        /* Keep watching qemu log for errors during incoming migration, otherwise
         * unset reporting errors from qemu log. */
        qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL);
    }

    ret = 0;

 cleanup:
    if (relabelSavedState &&
        qemuSecurityRestoreSavedStateLabel(driver->securityManager,
                                           vm->def, migratePath) < 0)
        VIR_WARN("failed to restore save state label on %s", migratePath);
    qemuProcessIncomingDefFree(incoming);
    return ret;

 stop:
    stopFlags = 0;
    if (!relabel)
        stopFlags |= VIR_QEMU_PROCESS_STOP_NO_RELABEL;
    if (migrateFrom)
        stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED;
    if (priv->mon)
        qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL);
    qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, asyncJob, stopFlags);
    goto cleanup;
}


qemuProcessFiltersInstantiate(virDomainDefPtr def)
{
    size_t i;

    for (i = 0; i < def->nnets; i++) {
        virDomainNetDefPtr net = def->nets[i];
        if ((net->filter) && (net->ifname)) {
            if (virDomainConfNWFilterInstantiate(def->name, def->uuid, net,
                                                 true) < 0) {
                VIR_WARN("filter '%s' instantiation for '%s' failed '%s'",
                         net->filter, net->ifname, virGetLastErrorMessage());
                virResetLastError();
            }
        }
    }
}


qemuProcessUpdateAndVerifyCPU(virQEMUDriverPtr driver,
                              virDomainObjPtr vm,
                              qemuDomainAsyncJob asyncJob)
{
    virCPUDataPtr cpu = NULL;
    virCPUDataPtr disabled = NULL;
    int ret = -1;

    if (qemuProcessFetchGuestCPU(driver, vm, asyncJob, &cpu, &disabled) < 0)
        goto cleanup;

    if (qemuProcessVerifyCPU(vm, cpu) < 0)
        goto cleanup;

    if (qemuProcessUpdateLiveGuestCPU(vm, cpu, disabled) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virCPUDataFree(cpu);
    virCPUDataFree(disabled);
    return ret;
}


qemuProcessFetchGuestCPU(virQEMUDriverPtr driver,
                         virDomainObjPtr vm,
                         qemuDomainAsyncJob asyncJob,
                         virCPUDataPtr *enabled,
                         virCPUDataPtr *disabled)
{
    qemuDomainObjPrivatePtr priv = vm->privateData;
    g_autoptr(virCPUData) dataEnabled = NULL;
    g_autoptr(virCPUData) dataDisabled = NULL;
    bool generic;
    int rc;

    *enabled = NULL;
    *disabled = NULL;

    generic = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_CPU_UNAVAILABLE_FEATURES);

    if (!generic && !ARCH_IS_X86(vm->def->os.arch))
        return 0;

    if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0)
        return -1;

    if (generic) {
        rc = qemuMonitorGetGuestCPU(priv->mon,
                                    vm->def->os.arch,
                                    qemuProcessTranslateCPUFeatures, priv->qemuCaps,
                                    &dataEnabled, &dataDisabled);
    } else {
        rc = qemuMonitorGetGuestCPUx86(priv->mon, &dataEnabled, &dataDisabled);
    }

    if (qemuDomainObjExitMonitor(driver, vm) < 0)
        return -1;

    if (rc == -1)
        return -1;

    *enabled = g_steal_pointer(&dataEnabled);
    *disabled = g_steal_pointer(&dataDisabled);
    return 0;
}


qemuProcessHandleMemoryFailure(qemuMonitorPtr mon G_GNUC_UNUSED,
                               virDomainObjPtr vm,
                               qemuMonitorEventMemoryFailurePtr mfp,
                               void *opaque)
{
    virQEMUDriverPtr driver = opaque;
    virObjectEventPtr event = NULL;
    virDomainMemoryFailureRecipientType recipient;
    virDomainMemoryFailureActionType action;
    unsigned int flags = 0;

    switch (mfp->recipient) {
    case QEMU_MONITOR_MEMORY_FAILURE_RECIPIENT_HYPERVISOR:
        recipient = VIR_DOMAIN_EVENT_MEMORY_FAILURE_RECIPIENT_HYPERVISOR;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_RECIPIENT_GUEST:
        recipient = VIR_DOMAIN_EVENT_MEMORY_FAILURE_RECIPIENT_GUEST;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_RECIPIENT_LAST:
    default:
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("requested unknown memory failure recipient"));
        return -1;
    }

    switch (mfp->action) {
    case QEMU_MONITOR_MEMORY_FAILURE_ACTION_IGNORE:
        action = VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_IGNORE;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_ACTION_INJECT:
        action = VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_INJECT;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_ACTION_FATAL:
        action = VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_FATAL;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_ACTION_RESET:
        action = VIR_DOMAIN_EVENT_MEMORY_FAILURE_ACTION_RESET;
        break;
    case QEMU_MONITOR_MEMORY_FAILURE_ACTION_LAST:
    default:
        virReportError(VIR_ERR_INVALID_ARG, "%s",
                       _("requested unknown memory failure action"));
        return -1;
    }

    if (mfp->action_required)
        flags |= VIR_DOMAIN_MEMORY_FAILURE_ACTION_REQUIRED;
    if (mfp->recursive)
        flags |= VIR_DOMAIN_MEMORY_FAILURE_RECURSIVE;

    event = virDomainEventMemoryFailureNewFromObj(vm, recipient, action, flags);
    virObjectEventStateQueue(driver->domainEventState, event);

    return 0;
}


qemuProcessQMPStart(qemuProcessQMPPtr proc)
{
    VIR_DEBUG("proc=%p, emulator=%s", proc, proc->binary);

    if (qemuProcessQMPInit(proc) < 0)
        return -1;

    if (qemuProcessQMPLaunch(proc) < 0)
        return -1;

    if (qemuProcessQMPConnectMonitor(proc) < 0)
        return -1;

    return 0;
}


qemuProcessDropUnknownCPUFeatures(const char *name,
                                  virCPUFeaturePolicy policy,
                                  void *opaque)
{
    const char **features = opaque;

    if (policy != VIR_CPU_FEATURE_DISABLE &&
        policy != VIR_CPU_FEATURE_FORBID)
        return true;

    if (g_strv_contains(features, name))
        return true;

    /* Features unknown to QEMU are implicitly disabled, we can just drop them
     * from the definition. */
    return false;
}


qemuProcessReconnectHelper(virDomainObjPtr obj,
                           void *opaque)
{
    virThread thread;
    struct qemuProcessReconnectData *src = opaque;
    struct qemuProcessReconnectData *data;
    g_autofree char *name = NULL;

    /* If the VM was inactive, we don't need to reconnect */
    if (!obj->pid)
        return 0;

    data = g_new0(struct qemuProcessReconnectData, 1);

    memcpy(data, src, sizeof(*data));
    data->obj = obj;
    data->identity = virIdentityGetCurrent();

    virNWFilterReadLockFilterUpdates();

    /* this lock and reference will be eventually transferred to the thread
     * that handles the reconnect */
    virObjectLock(obj);
    virObjectRef(obj);

    name = g_strdup_printf("init-%s", obj->def->name);

    if (virThreadCreateFull(&thread, false, qemuProcessReconnect,
                            name, false, data) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Could not create thread. QEMU initialization "
                         "might be incomplete"));
        /* We can't spawn a thread and thus connect to monitor. Kill qemu.
         * It's safe to call qemuProcessStop without a job here since there
         * is no thread that could be doing anything else with the same domain
         * object.
         */
        qemuProcessStop(src->driver, obj, VIR_DOMAIN_SHUTOFF_FAILED,
                        QEMU_ASYNC_JOB_NONE, 0);
        qemuDomainRemoveInactiveJobLocked(src->driver, obj);

        virDomainObjEndAPI(&obj);
        virNWFilterUnlockFilterUpdates();
        g_clear_object(&data->identity);
        VIR_FREE(data);
        return -1;
    }

    return 0;
}


qemuProcessKill(virDomainObjPtr vm, unsigned int flags)
{
    VIR_DEBUG("vm=%p name=%s pid=%lld flags=0x%x",
              vm, vm->def->name,
              (long long)vm->pid, flags);

    if (!(flags & VIR_QEMU_PROCESS_KILL_NOCHECK)) {
        if (!virDomainObjIsActive(vm)) {
            VIR_DEBUG("VM '%s' not active", vm->def->name);
            return 0;
        }
    }

    if (flags & VIR_QEMU_PROCESS_KILL_NOWAIT) {
        virProcessKill(vm->pid,
                       (flags & VIR_QEMU_PROCESS_KILL_FORCE) ?
                       SIGKILL : SIGTERM);
        return 0;
    }

    /* Request an extra delay of two seconds per current nhostdevs
     * to be safe against stalls by the kernel freeing up the resources */
    return virProcessKillPainfullyDelay(vm->pid,
                                        !!(flags & VIR_QEMU_PROCESS_KILL_FORCE),
                                        vm->def->nhostdevs * 2);
}


qemuProcessBeginJob(virQEMUDriverPtr driver,
                    virDomainObjPtr vm,
                    virDomainJobOperation operation,
                    unsigned long apiFlags)
{
    if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_START,
                                   operation, apiFlags) < 0)
        return -1;

    qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE);
    return 0;
}


qemuProcessTranslateCPUFeatures(const char *name,
                                void *opaque)
{
    virQEMUCapsPtr qemuCaps = opaque;

    return virQEMUCapsCPUFeatureFromQEMU(qemuCaps, name);
}


qemuProcessHandleStop(qemuMonitorPtr mon G_GNUC_UNUSED,
                      virDomainObjPtr vm,
                      void *opaque)
{
    virQEMUDriverPtr driver = opaque;
    virObjectEventPtr event = NULL;
    virDomainPausedReason reason;
    virDomainEventSuspendedDetailType detail;
    g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
    qemuDomainObjPrivatePtr priv = vm->privateData;

    virObjectLock(vm);

    reason = priv->pausedReason;
    priv->pausedReason = VIR_DOMAIN_PAUSED_UNKNOWN;

    /* In case of fake reboot qemu paused state is transient so don't
     * reveal it in domain state nor sent events */
    if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING &&
        !priv->pausedShutdown) {
        if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) {
            if (priv->job.current->status == QEMU_DOMAIN_JOB_STATUS_POSTCOPY)
                reason = VIR_DOMAIN_PAUSED_POSTCOPY;
            else
                reason = VIR_DOMAIN_PAUSED_MIGRATION;
        }

        detail = qemuDomainPausedReasonToSuspendedEvent(reason);
        VIR_DEBUG("Transitioned guest %s to paused state, "
                  "reason %s, event detail %d",
                  vm->def->name, virDomainPausedReasonTypeToString(reason),
                  detail);

        if (priv->job.current)
            ignore_value(virTimeMillisNow(&priv->job.current->stopped));

        if (priv->signalStop)
            virDomainObjBroadcast(vm);

        virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, reason);
        event = virDomainEventLifecycleNewFromObj(vm,
                                                  VIR_DOMAIN_EVENT_SUSPENDED,
                                                  detail);

        VIR_FREE(priv->lockState);
        if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0)
            VIR_WARN("Unable to release lease on %s", vm->def->name);
        VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState));

        if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0) {
            VIR_WARN("Unable to save status on vm %s after state change",
                     vm->def->name);
        }
    }

    virObjectUnlock(vm);
    virObjectEventStateQueue(driver->domainEventState, event);

    return 0;
}


static pid_t path_to_tid(const char *filename)
{
	char *path = mnt_resolve_path(filename, NULL);
	char *p, *end = NULL;
	pid_t tid = 0;

	if (!path)
		goto done;
	p = strrchr(path, '/');
	if (!p)
		goto done;
	*p = '\0';
	p = strrchr(path, '/');
	if (!p)
		goto done;
	p++;

	errno = 0;
	tid = strtol(p, &end, 10);
	if (errno || p == end || (end && *end)) {
		tid = 0;
		goto done;
	}
	DBG(TAB, ul_debug("TID for %s is %d", filename, tid));
done:
	free(path);
	return tid;
}


const char *mnt_fs_get_comment(struct libmnt_fs *fs)
{
	if (!fs)
		return NULL;
	return fs->comment;
}


struct libmnt_fs *mnt_copy_mtab_fs(const struct libmnt_fs *fs)
{
	struct libmnt_fs *n = mnt_new_fs();

	assert(fs);
	if (!n)
		return NULL;

	if (strdup_between_structs(n, fs, source))
		goto err;
	if (strdup_between_structs(n, fs, target))
		goto err;
	if (strdup_between_structs(n, fs, fstype))
		goto err;

	if (fs->vfs_optstr) {
		char *p = NULL;
		mnt_optstr_get_options(fs->vfs_optstr, &p,
				mnt_get_builtin_optmap(MNT_LINUX_MAP),
				MNT_NOMTAB);
		n->vfs_optstr = p;
	}

	if (fs->user_optstr) {
		char *p = NULL;
		mnt_optstr_get_options(fs->user_optstr, &p,
				mnt_get_builtin_optmap(MNT_USERSPACE_MAP),
				MNT_NOMTAB);
		n->user_optstr = p;
	}

	if (strdup_between_structs(n, fs, fs_optstr))
		goto err;

	/* we cannot copy original optstr, the new optstr has to be without
	 * non-mtab options -- so, let's generate a new string */
	n->optstr = mnt_fs_strdup_options(n);

	n->freq       = fs->freq;
	n->passno     = fs->passno;
	n->flags      = fs->flags;

	return n;
err:
	mnt_free_fs(n);
	return NULL;

}


static int get_column_id(int num)
{
	assert(num >= 0);
	assert((size_t) num < ncolumns);
	assert((size_t) columns[num] < ARRAY_SIZE(infos));
	return columns[num];
}


static int mnt_parse_mountinfo_line(struct libmnt_fs *fs, const char *s)
{
	int rc = 0;
	unsigned int maj, min;
	char *p;

	fs->flags |= MNT_FS_KERNEL;

	/* (1) id */
	s = next_s32(s, &fs->id, &rc);
	if (!s || !*s || rc) {
		DBG(TAB, ul_debug("tab parse error: [id]"));
		goto fail;
	}

	s = skip_separator(s);

	/* (2) parent */
	s = next_s32(s, &fs->parent, &rc);
	if (!s || !*s || rc) {
		DBG(TAB, ul_debug("tab parse error: [parent]"));
		goto fail;
	}

	s = skip_separator(s);

	/* (3) maj:min */
	if (sscanf(s, "%u:%u", &maj, &min) != 2) {
		DBG(TAB, ul_debug("tab parse error: [maj:min]"));
		goto fail;
	}
	fs->devno = makedev(maj, min);
	s = skip_nonspearator(s);
	s = skip_separator(s);

	/* (4) mountroot */
	fs->root = unmangle(s, &s);
	if (!fs->root) {
		DBG(TAB, ul_debug("tab parse error: [mountroot]"));
		goto fail;
	}

	s = skip_separator(s);

	/* (5) target */
	fs->target = unmangle(s, &s);
	if (!fs->target) {
		DBG(TAB, ul_debug("tab parse error: [target]"));
		goto fail;
	}

	s = skip_separator(s);

	/* (6) vfs options (fs-independent) */
	fs->vfs_optstr = unmangle(s, &s);
	if (!fs->vfs_optstr) {
		DBG(TAB, ul_debug("tab parse error: [VFS options]"));
		goto fail;
	}

	/* (7) optional fields, terminated by " - " */
	p = strstr(s, " - ");
	if (!p) {
		DBG(TAB, ul_debug("mountinfo parse error: separator not found"));
		return -EINVAL;
	}
	if (p > s + 1)
		fs->opt_fields = strndup(s + 1, p - s - 1);

	s = skip_separator(p + 3);

	/* (8) FS type */
	p = unmangle(s, &s);
	if (!p || (rc = __mnt_fs_set_fstype_ptr(fs, p))) {
		DBG(TAB, ul_debug("tab parse error: [fstype]"));
		free(p);
		goto fail;
	}

	/* (9) source -- maybe empty string */
	if (!s || !*s) {
		DBG(TAB, ul_debug("tab parse error: [source]"));
		goto fail;
	} else if (*s == ' ' && *(s+1) == ' ') {
		if ((rc = mnt_fs_set_source(fs, ""))) {
			DBG(TAB, ul_debug("tab parse error: [empty source]"));
			goto fail;
		}
	} else {
		s = skip_separator(s);
		p = unmangle(s, &s);
		if (!p || (rc = __mnt_fs_set_source_ptr(fs, p))) {
			DBG(TAB, ul_debug("tab parse error: [regular source]"));
			free(p);
			goto fail;
		}
	}

	s = skip_separator(s);

	/* (10) fs options (fs specific) */
	fs->fs_optstr = unmangle(s, &s);
	if (!fs->fs_optstr) {
		DBG(TAB, ul_debug("tab parse error: [FS options]"));
		goto fail;
	}

	/* merge VFS and FS options to one string */
	fs->optstr = mnt_fs_strdup_options(fs);
	if (!fs->optstr) {
		rc = -ENOMEM;
		DBG(TAB, ul_debug("tab parse error: [merge VFS and FS options]"));
		goto fail;
	}

	return 0;
fail:
	if (rc == 0)
		rc = -EINVAL;
	DBG(TAB, ul_debug("tab parse error on: '%s' [rc=%d]", s, rc));
	return rc;
}


static const char *get_column_name(int num)
{
	return get_column_info(num)->name;
}


off_t mnt_fs_get_usedsize(struct libmnt_fs *fs)
{
	return fs ? fs->usedsize : 0;
}


pid_t mnt_fs_get_tid(struct libmnt_fs *fs)
{
	return fs ? fs->tid : 0;
}


int mnt_fs_match_target(struct libmnt_fs *fs, const char *target,
			struct libmnt_cache *cache)
{
	int rc = 0;

	if (!fs || !target || !fs->target)
		return 0;

	/* 1) native paths */
	rc = mnt_fs_streq_target(fs, target);

	if (!rc && cache) {
		/* 2) - canonicalized and non-canonicalized */
		char *cn = mnt_resolve_target(target, cache);
		rc = (cn && mnt_fs_streq_target(fs, cn));

		/* 3) - canonicalized and canonicalized */
		if (!rc && cn && !mnt_fs_is_kernel(fs) && !mnt_fs_is_swaparea(fs)) {
			char *tcn = mnt_resolve_target(fs->target, cache);
			rc = (tcn && strcmp(cn, tcn) == 0);
		}
	}

	return rc;
}


char *mnt_fs_strdup_options(struct libmnt_fs *fs)
{
	char *res;

	if (!fs)
		return NULL;

	errno = 0;
	if (fs->optstr)
		return strdup(fs->optstr);

	res = merge_optstr(fs->vfs_optstr, fs->fs_optstr);
	if (!res && errno)
		return NULL;
	if (fs->user_optstr &&
	    mnt_optstr_append_option(&res, fs->user_optstr, NULL)) {
		free(res);
		res = NULL;
	}
	return res;
}


static void set_match(int id, const char *match)
{
	assert((size_t) id < ARRAY_SIZE(infos));
	infos[id].match = match;
}


const char *mnt_fs_get_options(struct libmnt_fs *fs)
{
	return fs ? fs->optstr : NULL;
}


int __mnt_fs_set_fstype_ptr(struct libmnt_fs *fs, char *fstype)
{
	assert(fs);

	if (fstype != fs->fstype)
		free(fs->fstype);

	fs->fstype = fstype;
	fs->flags &= ~MNT_FS_PSEUDO;
	fs->flags &= ~MNT_FS_NET;
	fs->flags &= ~MNT_FS_SWAP;

	/* save info about pseudo filesystems */
	if (fs->fstype) {
		if (mnt_fstype_is_pseudofs(fs->fstype))
			fs->flags |= MNT_FS_PSEUDO;
		else if (mnt_fstype_is_netfs(fs->fstype))
			fs->flags |= MNT_FS_NET;
		else if (!strcmp(fs->fstype, "swap"))
			fs->flags |= MNT_FS_SWAP;
	}
	return 0;
}


static int create_treenode(struct libscols_table *table, struct libmnt_table *tb,
			   struct libmnt_fs *fs, struct libscols_line *parent_line)
{
	struct libmnt_fs *chld = NULL;
	struct libmnt_iter *itr = NULL;
	struct libscols_line *line;
	int rc = -1, first = 0;

	if (!fs) {
		/* first call, get root FS */
		if (mnt_table_get_root_fs(tb, &fs))
			goto leave;
		parent_line = NULL;
		first = 1;

	} else if ((flags & FL_SUBMOUNTS) && has_line(table, fs))
		return 0;

	itr = mnt_new_iter(MNT_ITER_FORWARD);
	if (!itr)
		goto leave;

	if ((flags & FL_SUBMOUNTS) || match_func(fs, NULL)) {
		line = add_line(table, fs, parent_line);
		if (!line)
			goto leave;
	} else
		line = parent_line;

	/*
	 * add all children to the output table
	 */
	while (mnt_table_next_child_fs(tb, itr, fs, &chld) == 0) {
		if (create_treenode(table, tb, chld, line))
			goto leave;
	}
	rc = 0;

	/* make sure all entries are in the tree */
	if (first && (size_t) mnt_table_get_nents(tb) >
		     (size_t) scols_table_get_nlines(table)) {
		mnt_reset_iter(itr, MNT_ITER_FORWARD);
		fs = NULL;

		while (mnt_table_next_fs(tb, itr, &fs) == 0) {
			if (!has_line(table, fs) && match_func(fs, NULL))
				create_treenode(table, tb, fs, NULL);
		}
	}
leave:
	mnt_free_iter(itr);
	return rc;
}


static int is_tabdiff_column(int id)
{
	assert((size_t) id < ARRAY_SIZE(infos));

	switch(id) {
	case COL_ACTION:
	case COL_OLD_TARGET:
	case COL_OLD_OPTIONS:
		return 1;
	default:
		break;
	}
	return 0;
}


int mnt_fs_append_options(struct libmnt_fs *fs, const char *optstr)
{
	char *v = NULL, *f = NULL, *u = NULL;
	int rc;

	if (!fs)
		return -EINVAL;
	if (!optstr)
		return 0;

	rc = mnt_split_optstr(optstr, &u, &v, &f, 0, 0);
	if (rc)
		return rc;

	if (!rc && v)
		rc = mnt_optstr_append_option(&fs->vfs_optstr, v, NULL);
	if (!rc && f)
		rc = mnt_optstr_append_option(&fs->fs_optstr, f, NULL);
	if (!rc && u)
		rc = mnt_optstr_append_option(&fs->user_optstr, u, NULL);
	if (!rc)
		rc = mnt_optstr_append_option(&fs->optstr, optstr, NULL);

	free(v);
	free(f);
	free(u);

	return rc;
}


static int guess_table_format(const char *line)
{
	unsigned int a, b;

	DBG(TAB, ul_debug("trying to guess table type"));

	if (sscanf(line, "%u %u", &a, &b) == 2)
		return MNT_FMT_MOUNTINFO;

	if (strncmp(line, "Filename\t", 9) == 0)
		return MNT_FMT_SWAPS;

	return MNT_FMT_FSTAB;		/* fstab, mtab or /proc/mounts */
}


static int exec_helper(struct libmnt_context *cxt)
{
	char *namespace = NULL;
	struct libmnt_ns *ns_tgt = mnt_context_get_target_ns(cxt);
	int rc;
	pid_t pid;

	assert(cxt);
	assert(cxt->fs);
	assert(cxt->helper);
	assert((cxt->flags & MNT_FL_MOUNTFLAGS_MERGED));
	assert(cxt->helper_exec_status == 1);

	if (mnt_context_is_fake(cxt)) {
		DBG(CXT, ul_debugobj(cxt, "fake mode: does not execute helper"));
		cxt->helper_exec_status = rc = 0;
		return rc;
	}

	if (ns_tgt->fd != -1
	    && asprintf(&namespace, "/proc/%i/fd/%i",
			getpid(), ns_tgt->fd) == -1) {
		return -ENOMEM;
	}

	DBG_FLUSH;

	pid = fork();
	switch (pid) {
	case 0:
	{
		const char *args[12], *type;
		int i = 0;

		if (drop_permissions() != 0)
			_exit(EXIT_FAILURE);

		if (!mnt_context_switch_origin_ns(cxt))
			_exit(EXIT_FAILURE);

		type = mnt_fs_get_fstype(cxt->fs);

		args[i++] = cxt->helper;			/* 1 */
		args[i++] = mnt_fs_get_target(cxt->fs);		/* 2 */

		if (mnt_context_is_nomtab(cxt))
			args[i++] = "-n";			/* 3 */
		if (mnt_context_is_lazy(cxt))
			args[i++] = "-l";			/* 4 */
		if (mnt_context_is_force(cxt))
			args[i++] = "-f";			/* 5 */
		if (mnt_context_is_verbose(cxt))
			args[i++] = "-v";			/* 6 */
		if (mnt_context_is_rdonly_umount(cxt))
			args[i++] = "-r";			/* 7 */
		if (type
		    && strchr(type, '.')
		    && !endswith(cxt->helper, type)) {
			args[i++] = "-t";			/* 8 */
			args[i++] = type;			/* 9 */
		}
		if (namespace) {
			args[i++] = "-N";			/* 10 */
			args[i++] = namespace;			/* 11 */
		}

		args[i] = NULL;					/* 12 */
		for (i = 0; args[i]; i++)
			DBG(CXT, ul_debugobj(cxt, "argv[%d] = \"%s\"",
							i, args[i]));
		DBG_FLUSH;
		execv(cxt->helper, (char * const *) args);
		_exit(EXIT_FAILURE);
	}
	default:
	{
		int st;

		if (waitpid(pid, &st, 0) == (pid_t) -1) {
			cxt->helper_status = -1;
			rc = -errno;
		} else {
			cxt->helper_status = WIFEXITED(st) ? WEXITSTATUS(st) : -1;
			cxt->helper_exec_status = rc = 0;
		}
		DBG(CXT, ul_debugobj(cxt, "%s executed [status=%d, rc=%d%s]",
				cxt->helper,
				cxt->helper_status, rc,
				rc ? " waitpid failed" : ""));
		break;
	}

	case -1:
		cxt->helper_exec_status = rc = -errno;
		DBG(CXT, ul_debugobj(cxt, "fork() failed"));
		break;
	}

	free(namespace);
	return rc;
}


static int test_remove(struct libmnt_test *ts, int argc, char *argv[])
{
	const char *name;
	char *optstr;
	int rc;

	if (argc < 3)
		return -EINVAL;
	optstr = xstrdup(argv[1]);
	name = argv[2];

	rc = mnt_optstr_remove_option(&optstr, name);
	if (!rc)
		printf("result: >%s<\n", optstr);
	free(optstr);
	return rc;
}


static int test_set(struct libmnt_test *ts, int argc, char *argv[])
{
	const char *value = NULL, *name;
	char *optstr;
	int rc;

	if (argc < 3)
		return -EINVAL;
	optstr = xstrdup(argv[1]);
	name = argv[2];

	if (argc == 4)
		value = argv[3];

	rc = mnt_optstr_set_option(&optstr, name, value);
	if (!rc)
		printf("result: >%s<\n", optstr);
	free(optstr);
	return rc;
}


int mnt_optstr_get_option(const char *optstr, const char *name,
			  char **value, size_t *valsz)
{
	struct libmnt_optloc ol = MNT_INIT_OPTLOC;
	int rc;

	if (!optstr || !name)
		return -EINVAL;

	rc = mnt_optstr_locate_option((char *) optstr, name, &ol);
	if (!rc) {
		if (value)
			*value = ol.value;
		if (valsz)
			*valsz = ol.valsz;
	}
	return rc;
}


static int set_uint_value(char **optstr, unsigned int num,
			char *begin, char *end, char **next)
{
	char buf[40];
	snprintf(buf, sizeof(buf), "%u", num);

	mnt_optstr_remove_option_at(optstr, begin, end);
	return insert_value(optstr, begin, buf, next);
}


int mnt_optstr_fix_secontext(char **optstr,
			     char *value,
			     size_t valsz,
			     char **next)
{
	int rc = 0;
	char *p, *val, *begin, *end, *raw = NULL;
	size_t sz;

	if (!optstr || !*optstr || !value || !valsz)
		return -EINVAL;

	DBG(CXT, ul_debug("fixing SELinux context"));

	begin = value;
	end = value + valsz;

	/* the selinux contexts are quoted */
	if (*value == '"') {
		if (valsz <= 2 || *(value + valsz - 1) != '"')
			return -EINVAL;		/* improperly quoted option string */
		value++;
		valsz -= 2;
	}

	p = strndup(value, valsz);
	if (!p)
		return -ENOMEM;

	/* translate the context */
	rc = selinux_trans_to_raw_context(p, &raw);

	DBG(CXT, ul_debug("SELinux context '%s' translated to '%s'",
			p, rc == -1 ? "FAILED" : (char *) raw));

	free(p);
	if (rc == -1 ||	!raw)
		return -EINVAL;

	/* create a quoted string from the raw context */
	sz = strlen((char *) raw);
	if (!sz)
		return -EINVAL;

	p = val = malloc(valsz + 3);
	if (!val)
		return -ENOMEM;

	*p++ = '"';
	memcpy(p, raw, sz);
	p += sz;
	*p++ = '"';
	*p = '\0';

	freecon(raw);

	/* set new context */
	mnt_optstr_remove_option_at(optstr, begin, end);
	rc = insert_value(optstr, begin, val, next);
	free(val);

	return rc;
}


static void free_todo_entries(TodoEntry **todos) {
        for (TodoEntry *x = *todos; x && x->dir; x++) {
                closedir(x->dir);
                free(x->dirname);
        }

        freep(todos);
}


int hw_atl_utils_initfw(struct aq_hw_s *self, const struct aq_fw_ops **fw_ops)
{
	int err = 0;

	hw_atl_utils_hw_chip_features_init(self,
					   &self->chip_features);

	self->fw_ver_actual = hw_atl_utils_get_fw_version(self);

	if (hw_atl_utils_ver_match(HW_ATL_FW_VER_1X, self->fw_ver_actual)) {
		*fw_ops = &aq_fw_1x_ops;
	} else if (hw_atl_utils_ver_match(HW_ATL_FW_VER_2X, self->fw_ver_actual)) {
		*fw_ops = &aq_fw_2x_ops;
	} else if (hw_atl_utils_ver_match(HW_ATL_FW_VER_3X, self->fw_ver_actual)) {
		*fw_ops = &aq_fw_2x_ops;
	} else if (hw_atl_utils_ver_match(HW_ATL_FW_VER_4X, self->fw_ver_actual)) {
		*fw_ops = &aq_fw_2x_ops;
	} else {
		aq_pr_err("Bad FW version detected: %x\n",
			  self->fw_ver_actual);
		return -EOPNOTSUPP;
	}
	self->aq_fw_ops = *fw_ops;
	err = self->aq_fw_ops->init(self);

	return err;
}


int hw_atl_utils_fw_rpc_wait(struct aq_hw_s *self,
			     struct hw_atl_utils_fw_rpc **rpc)
{
	struct aq_hw_atl_utils_fw_rpc_tid_s sw;
	struct aq_hw_atl_utils_fw_rpc_tid_s fw;
	int err = 0;

	do {
		sw.val = aq_hw_read_reg(self, HW_ATL_RPC_CONTROL_ADR);

		self->rpc_tid = sw.tid;

		err = readx_poll_timeout_atomic(hw_atl_utils_rpc_state_get,
						self, fw.val,
						sw.tid == fw.tid,
						1000U, 100000U);
		if (err < 0)
			goto err_exit;

		err = aq_hw_err_from_flags(self);
		if (err < 0)
			goto err_exit;

		if (fw.len == 0xFFFFU) {
			if (sw.len > sizeof(self->rpc)) {
				printk(KERN_INFO "Invalid sw len: %x\n", sw.len);
				err = -EINVAL;
				goto err_exit;
			}
			err = hw_atl_utils_fw_rpc_call(self, sw.len);
			if (err < 0)
				goto err_exit;
		}
	} while (sw.tid != fw.tid || 0xFFFFU == fw.len);

	if (rpc) {
		if (fw.len) {
			if (fw.len > sizeof(self->rpc)) {
				printk(KERN_INFO "Invalid fw len: %x\n", fw.len);
				err = -EINVAL;
				goto err_exit;
			}
			err =
			hw_atl_utils_fw_downld_dwords(self,
						      self->rpc_addr,
						      (u32 *)(void *)
						      &self->rpc,
						      (fw.len + sizeof(u32) -
						       sizeof(u8)) /
						      sizeof(u32));
			if (err < 0)
				goto err_exit;
		}

		*rpc = &self->rpc;
	}

err_exit:
	return err;
}


static u32 hw_atl_utils_mif_addr_get(struct aq_hw_s *self)
{
	return aq_hw_read_reg(self, HW_ATL_MIF_ADDR);
}


n_start_visual_mode(int c)
{
#ifdef FEAT_CONCEAL
    int cursor_line_was_concealed = curwin->w_p_cole > 0
						&& conceal_cursor_line(curwin);
#endif

    VIsual_mode = c;
    VIsual_active = TRUE;
    VIsual_reselect = TRUE;

    // Corner case: the 0 position in a tab may change when going into
    // virtualedit.  Recalculate curwin->w_cursor to avoid bad highlighting.
    if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB)
    {
	validate_virtcol();
	coladvance(curwin->w_virtcol);
    }
    VIsual = curwin->w_cursor;

#ifdef FEAT_FOLDING
    foldAdjustVisual();
#endif

    trigger_modechanged();
    setmouse();
#ifdef FEAT_CONCEAL
    // Check if redraw is needed after changing the state.
    conceal_check_cursor_line(cursor_line_was_concealed);
#endif

    if (p_smd && msg_silent == 0)
	redraw_cmdline = TRUE;	// show visual mode later
#ifdef FEAT_CLIPBOARD
    // Make sure the clipboard gets updated.  Needed because start and
    // end may still be the same, and the selection needs to be owned
    clip_star.vmode = NUL;
#endif

    // Only need to redraw this line, unless still need to redraw an old
    // Visual area (when 'lazyredraw' is set).
    if (curwin->w_redr_type < INVERTED)
    {
	curwin->w_old_cursor_lnum = curwin->w_cursor.lnum;
	curwin->w_old_visual_lnum = curwin->w_cursor.lnum;
    }
}


find_file_in_path(
    char_u	*ptr,		// file name
    int		len,		// length of file name
    int		options,
    int		first,		// use count'th matching file name
    char_u	*rel_fname)	// file name searching relative to
{
    return find_file_in_path_option(ptr, len, options, first,
	    *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path,
	    FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
}


free_findfile(void)
{
    vim_free(ff_file_to_find);
    vim_findfile_cleanup(fdip_search_ctx);
    vim_free(ff_expand_buffer);
}


ff_clear(ff_search_ctx_T *search_ctx)
{
    ff_stack_T   *sptr;

    // clear up stack
    while ((sptr = ff_pop(search_ctx)) != NULL)
	ff_free_stack_element(sptr);

    vim_free(search_ctx->ffsc_file_to_search);
    vim_free(search_ctx->ffsc_start_dir);
    vim_free(search_ctx->ffsc_fix_path);
#ifdef FEAT_PATH_EXTRA
    vim_free(search_ctx->ffsc_wc_path);
#endif

#ifdef FEAT_PATH_EXTRA
    if (search_ctx->ffsc_stopdirs_v != NULL)
    {
	int  i = 0;

	while (search_ctx->ffsc_stopdirs_v[i] != NULL)
	{
	    vim_free(search_ctx->ffsc_stopdirs_v[i]);
	    i++;
	}
	vim_free(search_ctx->ffsc_stopdirs_v);
    }
    search_ctx->ffsc_stopdirs_v = NULL;
#endif

    // reset everything
    search_ctx->ffsc_file_to_search = NULL;
    search_ctx->ffsc_start_dir = NULL;
    search_ctx->ffsc_fix_path = NULL;
#ifdef FEAT_PATH_EXTRA
    search_ctx->ffsc_wc_path = NULL;
    search_ctx->ffsc_level = 0;
#endif
}


find_directory_in_path(
    char_u	*ptr,		// file name
    int		len,		// length of file name
    int		options,
    char_u	*rel_fname)	// file name searching relative to
{
    return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath,
				       FINDFILE_DIR, rel_fname, (char_u *)"");
}


vim_findfile_free_visited_list(ff_visited_list_hdr_T **list_headp)
{
    ff_visited_list_hdr_T *vp;

    while (*list_headp != NULL)
    {
	vp = (*list_headp)->ffvl_next;
	ff_free_visited_list((*list_headp)->ffvl_visited_list);

	vim_free((*list_headp)->ffvl_filename);
	vim_free(*list_headp);
	*list_headp = vp;
    }
    *list_headp = NULL;
}


expand_path_option(char_u *curdir, garray_T *gap)
{
    char_u	*path_option = *curbuf->b_p_path == NUL
						  ? p_path : curbuf->b_p_path;
    char_u	*buf;
    char_u	*p;
    int		len;

    if ((buf = alloc(MAXPATHL)) == NULL)
	return;

    while (*path_option != NUL)
    {
	copy_option_part(&path_option, buf, MAXPATHL, " ,");

	if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1])))
	{
	    // Relative to current buffer:
	    // "/path/file" + "." -> "/path/"
	    // "/path/file"  + "./subdir" -> "/path/subdir"
	    if (curbuf->b_ffname == NULL)
		continue;
	    p = gettail(curbuf->b_ffname);
	    len = (int)(p - curbuf->b_ffname);
	    if (len + (int)STRLEN(buf) >= MAXPATHL)
		continue;
	    if (buf[1] == NUL)
		buf[len] = NUL;
	    else
		STRMOVE(buf + len, buf + 2);
	    mch_memmove(buf, curbuf->b_ffname, len);
	    simplify_filename(buf);
	}
	else if (buf[0] == NUL)
	    // relative to current directory
	    STRCPY(buf, curdir);
	else if (path_with_url(buf))
	    // URL can't be used here
	    continue;
	else if (!mch_isFullName(buf))
	{
	    // Expand relative path to their full path equivalent
	    len = (int)STRLEN(curdir);
	    if (len + (int)STRLEN(buf) + 3 > MAXPATHL)
		continue;
	    STRMOVE(buf + len + 1, buf);
	    STRCPY(buf, curdir);
	    buf[len] = PATHSEP;
	    simplify_filename(buf);
	}

	if (ga_grow(gap, 1) == FAIL)
	    break;

# if defined(MSWIN)
	// Avoid the path ending in a backslash, it fails when a comma is
	// appended.
	len = (int)STRLEN(buf);
	if (buf[len - 1] == '\\')
	    buf[len - 1] = '/';
# endif

	p = vim_strsave(buf);
	if (p == NULL)
	    break;
	((char_u **)gap->ga_data)[gap->ga_len++] = p;
    }

    vim_free(buf);
}


grab_file_name(long count, linenr_T *file_lnum)
{
    int options = FNAME_MESS|FNAME_EXP|FNAME_REL|FNAME_UNESC;

    if (VIsual_active)
    {
	int	len;
	char_u	*ptr;

	if (get_visual_text(NULL, &ptr, &len) == FAIL)
	    return NULL;
	// Only recognize ":123" here
	if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1]))
	{
	    char_u *p = ptr + len + 1;

	    *file_lnum = getdigits(&p);
	}
	return find_file_name_in_path(ptr, len, options,
						     count, curbuf->b_ffname);
    }
    return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
}


ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
{
    // check for NULL pointer, not to return an error to the user, but
    // to prevent a crash
    if (stack_ptr != NULL)
    {
	stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr;
	search_ctx->ffsc_stack_ptr = stack_ptr;
    }
}


vim_findfirst(char_u *path, char_u *filename, int level)
{
    ff_fn_search_context =
	vim_findfile_init(path, filename, NULL, level, TRUE, FALSE,
		ff_fn_search_context, rel_fname);
    if (NULL == ff_fn_search_context)
	return NULL;
    else
	return vim_findnext()
}


m_local_df(union DateData *x)
{
    if (simple_dat_p(x))
	return 0;
    else {
	get_c_df(x);
	return local_df(x);
    }
}


valid_civil_sub(int argc, VALUE *argv, VALUE klass, int need_jd)
{
    VALUE nth, y;
    int m, d, ry, rm, rd;
    double sg;

    y = argv[0];
    m = NUM2INT(argv[1]);
    d = NUM2INT(argv[2]);
    sg = NUM2DBL(argv[3]);

    valid_sg(sg);

    if (!need_jd && (guess_style(y, sg) < 0)) {
	if (!valid_gregorian_p(y, m, d,
			       &nth, &ry,
			       &rm, &rd))
	    return Qnil;
	return INT2FIX(0); /* dummy */
    }
    else {
	int rjd, ns;
	VALUE rjd2;

	if (!valid_civil_p(y, m, d, sg,
			   &nth, &ry,
			   &rm, &rd, &rjd,
			   &ns))
	    return Qnil;
	if (!need_jd)
	    return INT2FIX(0); /* dummy */
	encode_jd(nth, rjd, &rjd2);
	return rjd2;
    }
}


c_commercial_to_jd(int y, int w, int d, double sg, int *rjd, int *ns)
{
    int rjd2, ns2;

    c_find_fdoy(y, sg, &rjd2, &ns2);
    rjd2 += 3;
    *rjd =
	(rjd2 - MOD((rjd2 - 1) + 1, 7)) +
	7 * (w - 1) +
	(d - 1);
    *ns = (*rjd < sg) ? 0 : 1;
}


canon(VALUE x)
{
    if (RB_TYPE_P(x, T_RATIONAL)) {
	VALUE den = rb_rational_den(x);
	if (FIXNUM_P(den) && FIX2LONG(den) == 1)
	    return rb_rational_num(x);
    }
    return x;
}


m_canonicalize_jd(VALUE obj, union DateData *x)
{
    if (simple_dat_p(x)) {
	get_s_jd(x);
	canonicalize_s_jd(obj, x);
    }
    else {
	get_c_jd(x);
	canonicalize_c_jd(obj, x);
    }
}


decode_day(VALUE d, VALUE *jd, VALUE *df, VALUE *sf)
{
    VALUE f;

    *jd = div_day(d, &f);
    *df = div_df(f, &f);
    *sf = sec_to_ns(f);
}


valid_nth_kday_sub(int argc, VALUE *argv, VALUE klass, int need_jd)
{
    VALUE nth, y;
    int m, n, k, ry, rm, rn, rk;
    double sg;

    y = argv[0];
    m = NUM2INT(argv[1]);
    n = NUM2INT(argv[2]);
    k = NUM2INT(argv[3]);
    sg = NUM2DBL(argv[4]);

    {
	int rjd, ns;
	VALUE rjd2;

	if (!valid_nth_kday_p(y, m, n, k, sg,
			      &nth, &ry,
			      &rm, &rn, &rk, &rjd,
			      &ns))
	    return Qnil;
	if (!need_jd)
	    return INT2FIX(0); /* dummy */
	encode_jd(nth, rjd, &rjd2);
	return rjd2;
    }
}


datetime_s_httpdate(int argc, VALUE *argv, VALUE klass)
{
    VALUE str, sg, opt;

    rb_scan_args(argc, argv, "02:", &str, &sg, &opt);
    if (!NIL_P(opt)) argc--;

    switch (argc) {
      case 0:
	str = rb_str_new2("Mon, 01 Jan -4712 00:00:00 GMT");
      case 1:
	sg = INT2FIX(DEFAULT_SG);
    }

    {
        int argc2 = 1;
        VALUE argv2[2];
        argv2[0] = str;
        argv2[1] = opt;
        if (!NIL_P(opt)) argc2++;
	VALUE hash = date_s__httpdate(argc2, argv2, klass);
	return dt_new_by_frags(klass, hash, sg);
    }
}


rt__valid_commercial_p(VALUE y, VALUE w, VALUE d, VALUE sg)
{
    VALUE nth, rjd2;
    int ry, rw, rd, rjd, ns;

    if (!valid_commercial_p(y, NUM2INT(w), NUM2INT(d), NUM2DBL(sg),
			    &nth, &ry,
			    &rw, &rd, &rjd,
			    &ns))
	return Qnil;
    encode_jd(nth, rjd, &rjd2);
    return rjd2;
}


sec_to_ms(VALUE s)
{
    if (safe_mul_p(s, SECOND_IN_MILLISECONDS))
	return LONG2FIX(FIX2LONG(s) * SECOND_IN_MILLISECONDS);
    return f_mul(s, INT2FIX(SECOND_IN_MILLISECONDS));
}


d_lite_next_month(int argc, VALUE *argv, VALUE self)
{
    VALUE n;

    rb_scan_args(argc, argv, "01", &n);
    if (argc < 1)
	n = INT2FIX(1);
    return d_lite_rshift(self, n);
}


date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vm, vn, vk, vsg;
    VALUE argv2[5];

    rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg);

    argv2[0] = vy;
    argv2[1] = vm;
    argv2[2] = vn;
    argv2[3] = vk;
    if (argc < 5)
	argv2[4] = DBL2NUM(GREGORIAN);
    else
	argv2[4] = vsg;

    return valid_nth_kday_sub(5, argv2, klass, 1);
}


f_negative_p(VALUE x)
{
    if (FIXNUM_P(x))
	return f_boolcast(FIX2LONG(x) < 0);
    return rb_funcall(x, '<', 1, INT2FIX(0));
}


local_jd(union DateData *x)
{
    assert(complex_dat_p(x));
    assert(have_jd_p(x));
    assert(have_df_p(x));
    return jd_utc_to_local(x->c.jd, x->c.df, x->c.of);
}


d_lite_zero(VALUE x)
{
    return INT2FIX(0);
}


min_trunc(VALUE min, VALUE *fr)
{
    VALUE rmin;

    if (wholenum_p(min)) {
	rmin = to_integer(min);
	*fr = INT2FIX(0);
    }
    else {
	rmin = f_idiv(min, INT2FIX(1));
	*fr = f_mod(min, INT2FIX(1));
	*fr = f_quo(*fr, INT2FIX(1440));
    }
    return rmin;
}


test_civil(int from, int to, double sg)
{
    int j;

    fprintf(stderr, "test_civil: %d...%d (%d) - %.0f\n",
	    from, to, to - from, sg);
    for (j = from; j <= to; j++) {
	int y, m, d, rj, ns;

	c_jd_to_civil(j, sg, &y, &m, &d);
	c_civil_to_jd(y, m, d, sg, &rj, &ns);
	if (j != rj) {
	    fprintf(stderr, "%d != %d\n", j, rj);
	    return 0;
	}
    }
    return 1;
}


d_new_by_frags(VALUE klass, VALUE hash, VALUE sg)
{
    VALUE jd;

    if (!c_valid_start_p(NUM2DBL(sg))) {
	sg = INT2FIX(DEFAULT_SG);
	rb_warning("invalid start is ignored");
    }

    if (NIL_P(hash))
	rb_raise(eDateError, "invalid date");

    if (NIL_P(ref_hash("jd")) &&
	NIL_P(ref_hash("yday")) &&
	!NIL_P(ref_hash("year")) &&
	!NIL_P(ref_hash("mon")) &&
	!NIL_P(ref_hash("mday")))
	jd = rt__valid_civil_p(ref_hash("year"),
			       ref_hash("mon"),
			       ref_hash("mday"), sg);
    else {
	hash = rt_rewrite_frags(hash);
	hash = rt_complete_frags(klass, hash);
	jd = rt__valid_date_frags_p(hash, sg);
    }

    if (NIL_P(jd))
	rb_raise(eDateError, "invalid date");
    {
	VALUE nth;
	int rjd;

	decode_jd(jd, &nth, &rjd);
	return d_simple_new_internal(klass,
				     nth, rjd,
				     NUM2DBL(sg),
				     0, 0, 0,
				     HAVE_JD);
    }
}


get_s_civil(union DateData *x)
{
    assert(simple_dat_p(x));
    if (!have_civil_p(x)) {
	int y, m, d;

	assert(have_jd_p(x));
	c_jd_to_civil(x->s.jd, s_virtual_sg(x), &y, &m, &d);
	x->s.year = y;
#ifndef USE_PACK
	x->s.mon = m;
	x->s.mday = d;
#else
	x->s.pc = PACK2(m, d);
#endif
	x->s.flags |= HAVE_CIVIL;
    }
}


date_s_jisx0301(int argc, VALUE *argv, VALUE klass)
{
    VALUE str, sg, opt;

    rb_scan_args(argc, argv, "02:", &str, &sg, &opt);
    if (!NIL_P(opt)) argc--;

    switch (argc) {
      case 0:
	str = rb_str_new2("-4712-01-01");
      case 1:
	sg = INT2FIX(DEFAULT_SG);
    }

    {
        int argc2 = 1;
        VALUE argv2[2];
        argv2[0] = str;
        if (!NIL_P(opt)) argv2[argc2++] = opt;
	VALUE hash = date_s__jisx0301(argc2, argv2, klass);
	return d_new_by_frags(klass, hash, sg);
    }
}


d_lite_hour(VALUE self)
{
    get_d1(self);
    return INT2FIX(m_hour(dat));
}


f_zero_p(VALUE x)
{
    switch (TYPE(x)) {
      case T_FIXNUM:
	return f_boolcast(FIX2LONG(x) == 0);
      case T_BIGNUM:
	return Qfalse;
      case T_RATIONAL:
	{
	    VALUE num = rb_rational_num(x);
	    return f_boolcast(FIXNUM_P(num) && FIX2LONG(num) == 0);
	}
    }
    return rb_funcall(x, id_eqeq_p, 1, INT2FIX(0));
}


sec_to_ns(VALUE s)
{
    if (safe_mul_p(s, SECOND_IN_NANOSECONDS))
	return LONG2FIX(FIX2LONG(s) * SECOND_IN_NANOSECONDS);
    return f_mul(s, INT2FIX(SECOND_IN_NANOSECONDS));
}


m_sg(union DateData *x)
{
    if (simple_dat_p(x))
	return x->s.sg;
    else {
	get_c_jd(x);
	return x->c.sg;
    }
}


date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass)
{
    VALUE vy, vw, vd, vsg;
    VALUE argv2[4];

    rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg);

    argv2[0] = vy;
    argv2[1] = vw;
    argv2[2] = vd;
    if (argc < 4)
	argv2[3] = DBL2NUM(GREGORIAN);
    else
	argv2[3] = vsg;

    return valid_commercial_sub(4, argv2, klass, 1);
}


cmp_gen(VALUE self, VALUE other)
{
    get_d1(self);

    if (k_numeric_p(other))
	return INT2FIX(f_cmp(m_ajd(dat), other));
    else if (k_date_p(other))
	return INT2FIX(f_cmp(m_ajd(dat), f_ajd(other)));
    return rb_num_coerce_cmp(self, other, id_cmp);
}


d_lite_inspect_raw(VALUE self)
{
    get_d1(self);
    return mk_inspect_raw(dat, rb_obj_class(self));
}


ordinal_to_jd(VALUE y, int d, double sg,
	      VALUE *nth, int *ry,
	      int *rjd,
	      int *ns)
{
    double style = guess_style(y, sg);

    if (style == 0) {
	int jd;

	c_ordinal_to_jd(FIX2INT(y), d, sg, &jd, ns);
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	c_ordinal_to_jd(*ry, d, style, rjd, ns);
    }
}


tmx_m_secs(union DateData *x)
{
    VALUE s;
    int df;

    s = day_to_sec(f_sub(m_real_jd(x),
			 UNIX_EPOCH_IN_CJD));
    if (simple_dat_p(x))
	return s;
    df = m_df(x);
    if (df)
	s = f_add(s, INT2FIX(df));
    return s;
}


valid_ordinal_sub(int argc, VALUE *argv, VALUE klass, int need_jd)
{
    VALUE nth, y;
    int d, ry, rd;
    double sg;

    y = argv[0];
    d = NUM2INT(argv[1]);
    sg = NUM2DBL(argv[2]);

    valid_sg(sg);

    {
	int rjd, ns;
	VALUE rjd2;

	if (!valid_ordinal_p(y, d, sg,
			     &nth, &ry,
			     &rd, &rjd,
			     &ns))
	    return Qnil;
	if (!need_jd)
	    return INT2FIX(0); /* dummy */
	encode_jd(nth, rjd, &rjd2);
	return rjd2;
    }
}


valid_commercial_p(VALUE y, int w, int d, double sg,
		   VALUE *nth, int *ry,
		   int *rw, int *rd, int *rjd,
		   int *ns)
{
    double style = guess_style(y, sg);
    int r;

    if (style == 0) {
	int jd;

	r = c_valid_commercial_p(FIX2INT(y), w, d, sg, rw, rd, &jd, ns);
	if (!r)
	    return 0;
	decode_jd(INT2FIX(jd), nth, rjd);
	if (f_zero_p(*nth))
	    *ry = FIX2INT(y);
	else {
	    VALUE nth2;
	    decode_year(y, *ns ? -1 : +1, &nth2, ry);
	}
    }
    else {
	decode_year(y, style, nth, ry);
	r = c_valid_commercial_p(*ry, w, d, style, rw, rd, rjd, ns);
    }
    return r;
}


tmx_m_msecs(union DateData *x)
{
    VALUE s, sf;

    s = sec_to_ms(tmx_m_secs(x));
    if (simple_dat_p(x))
	return s;
    sf = m_sf(x);
    if (f_nonzero_p(sf))
	s = f_add(s, f_div(sf, INT2FIX(MILLISECOND_IN_NANOSECONDS)));
    return s;
}


static void bpf_prog_get_stats(const struct bpf_prog *prog,
			       struct bpf_prog_kstats *stats)
{
	u64 nsecs = 0, cnt = 0, misses = 0;
	int cpu;

	for_each_possible_cpu(cpu) {
		const struct bpf_prog_stats *st;
		unsigned int start;
		u64 tnsecs, tcnt, tmisses;

		st = per_cpu_ptr(prog->stats, cpu);
		do {
			start = u64_stats_fetch_begin_irq(&st->syncp);
			tnsecs = u64_stats_read(&st->nsecs);
			tcnt = u64_stats_read(&st->cnt);
			tmisses = u64_stats_read(&st->misses);
		} while (u64_stats_fetch_retry_irq(&st->syncp, start));
		nsecs += tnsecs;
		cnt += tcnt;
		misses += tmisses;
	}
	stats->nsecs = nsecs;
	stats->cnt = cnt;
	stats->misses = misses;
}


static bool __reg64_bound_s32(s64 a)
{
	return a >= S32_MIN && a <= S32_MAX;
}


static int map_freeze(const union bpf_attr *attr)
{
	int err = 0, ufd = attr->map_fd;
	struct bpf_map *map;
	struct fd f;

	if (CHECK_ATTR(BPF_MAP_FREEZE))
		return -EINVAL;

	f = fdget(ufd);
	map = __bpf_map_get(f);
	if (IS_ERR(map))
		return PTR_ERR(map);

	if (map->map_type == BPF_MAP_TYPE_STRUCT_OPS ||
	    map_value_has_timer(map)) {
		fdput(f);
		return -ENOTSUPP;
	}

	mutex_lock(&map->freeze_mutex);
	if (bpf_map_write_active(map)) {
		err = -EBUSY;
		goto err_put;
	}
	if (READ_ONCE(map->frozen)) {
		err = -EBUSY;
		goto err_put;
	}
	if (!bpf_capable()) {
		err = -EPERM;
		goto err_put;
	}

	WRITE_ONCE(map->frozen, true);
err_put:
	mutex_unlock(&map->freeze_mutex);
	fdput(f);
	return err;
}


void bpf_prog_free_id(struct bpf_prog *prog, bool do_idr_lock)
{
	unsigned long flags;

	/* cBPF to eBPF migrations are currently not in the idr store.
	 * Offloaded programs are removed from the store when their device
	 * disappears - even if someone grabs an fd to them they are unusable,
	 * simply waiting for refcnt to drop to be freed.
	 */
	if (!prog->aux->id)
		return;

	if (do_idr_lock)
		spin_lock_irqsave(&prog_idr_lock, flags);
	else
		__acquire(&prog_idr_lock);

	idr_remove(&prog_idr, prog->aux->id);
	prog->aux->id = 0;

	if (do_idr_lock)
		spin_unlock_irqrestore(&prog_idr_lock, flags);
	else
		__release(&prog_idr_lock);
}


static const char *disasm_kfunc_name(void *data, const struct bpf_insn *insn)
{
	const struct btf_type *func;
	struct btf *desc_btf;

	if (insn->src_reg != BPF_PSEUDO_KFUNC_CALL)
		return NULL;

	desc_btf = find_kfunc_desc_btf(data, insn->imm, insn->off, NULL);
	if (IS_ERR(desc_btf))
		return "<error>";

	func = btf_type_by_id(desc_btf, insn->imm);
	return btf_name_by_offset(desc_btf, func->name_off);
}


static int process_timer_func(struct bpf_verifier_env *env, int regno,
			      struct bpf_call_arg_meta *meta)
{
	struct bpf_reg_state *regs = cur_regs(env), *reg = &regs[regno];
	bool is_const = tnum_is_const(reg->var_off);
	struct bpf_map *map = reg->map_ptr;
	u64 val = reg->var_off.value;

	if (!is_const) {
		verbose(env,
			"R%d doesn't have constant offset. bpf_timer has to be at the constant offset\n",
			regno);
		return -EINVAL;
	}
	if (!map->btf) {
		verbose(env, "map '%s' has to have BTF in order to use bpf_timer\n",
			map->name);
		return -EINVAL;
	}
	if (!map_value_has_timer(map)) {
		if (map->timer_off == -E2BIG)
			verbose(env,
				"map '%s' has more than one 'struct bpf_timer'\n",
				map->name);
		else if (map->timer_off == -ENOENT)
			verbose(env,
				"map '%s' doesn't have 'struct bpf_timer'\n",
				map->name);
		else
			verbose(env,
				"map '%s' is not a struct type or bpf_timer is mangled\n",
				map->name);
		return -EINVAL;
	}
	if (map->timer_off != val + reg->off) {
		verbose(env, "off %lld doesn't point to 'struct bpf_timer' that is at %d\n",
			val + reg->off, map->timer_off);
		return -EINVAL;
	}
	if (meta->map_ptr) {
		verbose(env, "verifier bug. Two map pointers in a timer helper\n");
		return -EFAULT;
	}
	meta->map_uid = reg->map_uid;
	meta->map_ptr = map;
	return 0;
}


static int propagate_precision(struct bpf_verifier_env *env,
			       const struct bpf_verifier_state *old)
{
	struct bpf_reg_state *state_reg;
	struct bpf_func_state *state;
	int i, err = 0;

	state = old->frame[old->curframe];
	state_reg = state->regs;
	for (i = 0; i < BPF_REG_FP; i++, state_reg++) {
		if (state_reg->type != SCALAR_VALUE ||
		    !state_reg->precise)
			continue;
		if (env->log.level & BPF_LOG_LEVEL2)
			verbose(env, "propagating r%d\n", i);
		err = mark_chain_precision(env, i);
		if (err < 0)
			return err;
	}

	for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
		if (!is_spilled_reg(&state->stack[i]))
			continue;
		state_reg = &state->stack[i].spilled_ptr;
		if (state_reg->type != SCALAR_VALUE ||
		    !state_reg->precise)
			continue;
		if (env->log.level & BPF_LOG_LEVEL2)
			verbose(env, "propagating fp%d\n",
				(-i - 1) * BPF_REG_SIZE);
		err = mark_chain_precision_stack(env, i);
		if (err < 0)
			return err;
	}
	return 0;
}


static int check_map_access(struct bpf_verifier_env *env, u32 regno,
			    int off, int size, bool zero_size_allowed)
{
	struct bpf_verifier_state *vstate = env->cur_state;
	struct bpf_func_state *state = vstate->frame[vstate->curframe];
	struct bpf_reg_state *reg = &state->regs[regno];
	struct bpf_map *map = reg->map_ptr;
	int err;

	err = check_mem_region_access(env, regno, off, size, map->value_size,
				      zero_size_allowed);
	if (err)
		return err;

	if (map_value_has_spin_lock(map)) {
		u32 lock = map->spin_lock_off;

		/* if any part of struct bpf_spin_lock can be touched by
		 * load/store reject this program.
		 * To check that [x1, x2) overlaps with [y1, y2)
		 * it is sufficient to check x1 < y2 && y1 < x2.
		 */
		if (reg->smin_value + off < lock + sizeof(struct bpf_spin_lock) &&
		     lock < reg->umax_value + off + size) {
			verbose(env, "bpf_spin_lock cannot be accessed directly by load/store\n");
			return -EACCES;
		}
	}
	if (map_value_has_timer(map)) {
		u32 t = map->timer_off;

		if (reg->smin_value + off < t + sizeof(struct bpf_timer) &&
		     t < reg->umax_value + off + size) {
			verbose(env, "bpf_timer cannot be accessed directly by load/store\n");
			return -EACCES;
		}
	}
	return err;
}