  uint64_t append(const void* data, uint64_t size) {
    uint64_t copy_size = std::min(size, reservableSize());
    if (copy_size == 0) {
      return 0;
    }
    uint8_t* dest = base_ + reservable_;
    reservable_ += copy_size;
    // NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
    memcpy(dest, data, copy_size);
    return copy_size;
  }


TEST_F(OwnedImplTest, AddBufferFragmentDynamicAllocation) {
  std::string input_str(2048, 'a');
  char* input = new char[2048];
  std::copy(input_str.c_str(), input_str.c_str() + 11, input);

  BufferFragmentImpl* frag = new BufferFragmentImpl(
      input, 2048, [this](const void* data, size_t, const BufferFragmentImpl* frag) {
        release_callback_called_ = true;
        delete[] static_cast<const char*>(data);
        delete frag;
      });

  Buffer::OwnedImpl buffer;
  buffer.addBufferFragment(*frag);
  EXPECT_EQ(2048, buffer.length());

  buffer.drain(2042);
  EXPECT_EQ(6, buffer.length());
  EXPECT_FALSE(release_callback_called_);

  buffer.drain(6);
  EXPECT_EQ(0, buffer.length());
  EXPECT_TRUE(release_callback_called_);
}


const SlicePtr& operator[](size_t i) const { return ring_[internalIndex(i)]; }


  getRawSlices(absl::optional<uint64_t> max_slices = absl::nullopt) const override {
    ASSERT(!max_slices.has_value() || max_slices.value() >= 1);
    return {{const_cast<char*>(start()), size_}};
  }


void OwnedImpl::move(Instance& rhs) {
  ASSERT(&rhs != this);
  // We do the static cast here because in practice we only have one buffer implementation right
  // now and this is safe. This is a reasonable compromise in a high performance path where we
  // want to maintain an abstraction.
  OwnedImpl& other = static_cast<OwnedImpl&>(rhs);
  while (!other.slices_.empty()) {
    const uint64_t slice_size = other.slices_.front()->dataSize();
    coalesceOrAddSlice(std::move(other.slices_.front()));
    other.length_ -= slice_size;
    other.slices_.pop_front();
  }
  other.postProcess();
}


  void pop_back() {
    if (size() == 0) {
      return;
    }
    back() = SlicePtr();
    size_--;
  }


TEST_F(OwnedImplTest, StartsWith) {
  // Populate a buffer with a string split across many small slices, to
  // exercise edge cases in the startsWith implementation.
  static const char* Inputs[] = {"ab", "a", "", "aaa", "b", "a", "aaa", "ab", "a"};
  Buffer::OwnedImpl buffer;
  for (const auto& input : Inputs) {
    buffer.appendSliceForTest(input);
  }
  EXPECT_STREQ("abaaaabaaaaaba", buffer.toString().c_str());

  EXPECT_FALSE(buffer.startsWith({"abaaaabaaaaabaXXX", 17}));
  EXPECT_FALSE(buffer.startsWith({"c", 1}));
  EXPECT_TRUE(buffer.startsWith({"", 0}));
  EXPECT_TRUE(buffer.startsWith({"a", 1}));
  EXPECT_TRUE(buffer.startsWith({"ab", 2}));
  EXPECT_TRUE(buffer.startsWith({"aba", 3}));
  EXPECT_TRUE(buffer.startsWith({"abaa", 4}));
  EXPECT_TRUE(buffer.startsWith({"abaaaab", 7}));
  EXPECT_TRUE(buffer.startsWith({"abaaaabaaaaaba", 14}));
  EXPECT_FALSE(buffer.startsWith({"ba", 2}));
}


SlicePtr& front() { return ring_[start_]; }


  OwnedBufferFragmentImpl(absl::string_view data, const Releasor& releasor)
      : releasor_(releasor), size_(data.size()) {
    ASSERT(releasor != nullptr);
    memcpy(data_, data.data(), data.size());
  }


TEST_F(OwnedImplTest, AddBufferFragmentNoCleanup) {
  char input[] = "hello world";
  BufferFragmentImpl frag(input, 11, nullptr);
  Buffer::OwnedImpl buffer;
  buffer.addBufferFragment(frag);
  EXPECT_EQ(11, buffer.length());

  buffer.drain(11);
  EXPECT_EQ(0, buffer.length());
}


TEST_F(OwnedImplTest, MoveBuffersWithSmallSlices) {
  // Small slices should be coalesced together
  TestBufferMove(1, 1, 1);
}


static void test_make_salt(void) {
        log_info("/* %s */", __func__);

        _cleanup_free_ char *s, *t;

        assert_se(make_salt(&s) == 0);
        log_info("got %s", s);

        assert_se(make_salt(&t) == 0);
        log_info("got %s", t);

        assert(!streq(s, t));
}


int fgetspent_sane(FILE *stream, struct spwd **sp) {
        struct spwd *s;

        assert(sp);
        assert(stream);

        errno = 0;
        s = fgetspent(stream);
        if (!s && errno != ENOENT)
                return errno_or_else(EIO);

        *sp = s;
        return !!s;
}


static void test_get_group_creds_one(const char *id, const char *name, gid_t gid) {
        gid_t rgid = GID_INVALID;
        int r;

        log_info("/* %s(\"%s\", \"%s\", "GID_FMT") */", __func__, id, name, gid);

        r = get_group_creds(&id, &rgid, 0);
        log_info_errno(r, "got \"%s\", "GID_FMT": %m", id, rgid);
        if (!synthesize_nobody() && streq(name, NOBODY_GROUP_NAME)) {
                log_info("(skipping detailed tests because nobody is not synthesized)");
                return;
        }
        assert_se(r == 0);
        assert_se(streq_ptr(id, name));
        assert_se(rgid == gid);
}


static void gadget_config_attr_release(struct config_item *item)
{
	struct config_usb_cfg *cfg = to_config_usb_cfg(item);

	WARN_ON(!list_empty(&cfg->c.functions));
	list_del(&cfg->c.list);
	kfree(cfg->c.label);
	kfree(cfg);
}


static int os_desc_link(struct config_item *os_desc_ci,
			struct config_item *usb_cfg_ci)
{
	struct gadget_info *gi = container_of(to_config_group(os_desc_ci),
					struct gadget_info, os_desc_group);
	struct usb_composite_dev *cdev = &gi->cdev;
	struct config_usb_cfg *c_target =
		container_of(to_config_group(usb_cfg_ci),
			     struct config_usb_cfg, group);
	struct usb_configuration *c;
	int ret;

	mutex_lock(&gi->lock);
	list_for_each_entry(c, &cdev->configs, list) {
		if (c == &c_target->c)
			break;
	}
	if (c != &c_target->c) {
		ret = -EINVAL;
		goto out;
	}

	if (cdev->os_desc_config) {
		ret = -EBUSY;
		goto out;
	}

	cdev->os_desc_config = &c_target->c;
	ret = 0;

out:
	mutex_unlock(&gi->lock);
	return ret;
}


static ssize_t ext_prop_data_store(struct config_item *item,
				   const char *page, size_t len)
{
	struct usb_os_desc_ext_prop *ext_prop = to_usb_os_desc_ext_prop(item);
	struct usb_os_desc *desc = to_usb_os_desc(ext_prop->item.ci_parent);
	char *new_data;
	size_t ret_len = len;

	if (page[len - 1] == '\n' || page[len - 1] == '\0')
		--len;
	new_data = kmemdup(page, len, GFP_KERNEL);
	if (!new_data)
		return -ENOMEM;

	if (desc->opts_mutex)
		mutex_lock(desc->opts_mutex);
	kfree(ext_prop->data);
	ext_prop->data = new_data;
	desc->ext_prop_len -= ext_prop->data_len;
	ext_prop->data_len = len;
	desc->ext_prop_len += ext_prop->data_len;
	if (ext_prop->type == USB_EXT_PROP_UNICODE ||
	    ext_prop->type == USB_EXT_PROP_UNICODE_ENV ||
	    ext_prop->type == USB_EXT_PROP_UNICODE_LINK) {
		desc->ext_prop_len -= ext_prop->data_len;
		ext_prop->data_len <<= 1;
		ext_prop->data_len += 2;
		desc->ext_prop_len += ext_prop->data_len;
	}
	if (desc->opts_mutex)
		mutex_unlock(desc->opts_mutex);
	return ret_len;
}


static ssize_t gadget_config_desc_bmAttributes_store(struct config_item *item,
		const char *page, size_t len)
{
	u8 val;
	int ret;
	ret = kstrtou8(page, 0, &val);
	if (ret)
		return ret;
	if (!(val & USB_CONFIG_ATT_ONE))
		return -EINVAL;
	if (val & ~(USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER |
				USB_CONFIG_ATT_WAKEUP))
		return -EINVAL;
	to_config_usb_cfg(item)->c.bmAttributes = val;
	return len;
}


static void composite_init_dev(struct usb_composite_dev *cdev)
{
	spin_lock_init(&cdev->lock);
	INIT_LIST_HEAD(&cdev->configs);
	INIT_LIST_HEAD(&cdev->gstrings);
}


static ssize_t interf_grp_compatible_id_store(struct config_item *item,
					      const char *page, size_t len)
{
	struct usb_os_desc *desc = to_usb_os_desc(item);
	int l;

	l = min_t(int, 8, len);
	if (page[l - 1] == '\n')
		--l;
	if (desc->opts_mutex)
		mutex_lock(desc->opts_mutex);
	memcpy(desc->ext_compat_id, page, l);

	if (desc->opts_mutex)
		mutex_unlock(desc->opts_mutex);

	return len;
}


static void gadget_info_attr_release(struct config_item *item)
{
	struct gadget_info *gi = to_gadget_info(item);

	WARN_ON(!list_empty(&gi->cdev.configs));
	WARN_ON(!list_empty(&gi->string_list));
	WARN_ON(!list_empty(&gi->available_func));
	kfree(gi->composite.gadget_driver.function);
	kfree(gi);
}


static ssize_t interf_grp_compatible_id_show(struct config_item *item,
					     char *page)
{
	memcpy(page, to_usb_os_desc(item)->ext_compat_id, 8);
	return 8;
}


static void configfs_composite_disconnect(struct usb_gadget *gadget)
{
	struct usb_composite_dev *cdev;
	struct gadget_info *gi;
	unsigned long flags;

	cdev = get_gadget_data(gadget);
	if (!cdev)
		return;

	gi = container_of(cdev, struct gadget_info, cdev);
	spin_lock_irqsave(&gi->spinlock, flags);
	cdev = get_gadget_data(gadget);
	if (!cdev || gi->unbind) {
		spin_unlock_irqrestore(&gi->spinlock, flags);
		return;
	}

	composite_disconnect(gadget);
	spin_unlock_irqrestore(&gi->spinlock, flags);
}


static ssize_t os_desc_use_store(struct config_item *item, const char *page,
				 size_t len)
{
	struct gadget_info *gi = os_desc_item_to_gadget_info(item);
	int ret;
	bool use;

	mutex_lock(&gi->lock);
	ret = strtobool(page, &use);
	if (!ret) {
		gi->use_os_desc = use;
		ret = len;
	}
	mutex_unlock(&gi->lock);

	return ret;
}


static ssize_t phys_switch_id_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct net_device *netdev = to_net_dev(dev);
	ssize_t ret = -EINVAL;

	if (!rtnl_trylock())
		return restart_syscall();

	if (dev_isalive(netdev)) {
		struct netdev_phys_item_id ppid = { };

		ret = dev_get_port_parent_id(netdev, &ppid, false);
		if (!ret)
			ret = sprintf(buf, "%*phN\n", ppid.id_len, ppid.id);
	}
	rtnl_unlock();

	return ret;


exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d,
			       unsigned int ds, ExifLong o, ExifLong s)
{
	/* Sanity checks */
	if (o >= ds) {
		exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Bogus thumbnail offset (%u).", o);
		return;
	}
	if (CHECKOVERFLOW(o,ds,s)) {
		exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Bogus thumbnail size (%u), max would be %u.", s, ds-o);
		return;
	}
	if (data->data)
		exif_mem_free (data->priv->mem, data->data);
	if (!(data->data = exif_data_alloc (data, s))) {
		EXIF_LOG_NO_MEMORY (data->priv->log, "ExifData", s);
		data->size = 0;
		return;
	}
	data->size = s;
	memcpy (data->data, d + o, s);
}


void msusb_msconfig_free(MSUSB_CONFIG_DESCRIPTOR* MsConfig)
{
	if (MsConfig)
	{
		msusb_msinterface_free_list(MsConfig->MsInterfaces, MsConfig->NumInterfaces);
		MsConfig->MsInterfaces = NULL;
		free(MsConfig);
	}
}


RFX_MESSAGE* rfx_encode_message(RFX_CONTEXT* context, const RFX_RECT* rects, int numRects,
                                BYTE* data, int w, int h, int s)
{
	const UINT32 width = (UINT32)w;
	const UINT32 height = (UINT32)h;
	const UINT32 scanline = (UINT32)s;
	UINT32 i, maxNbTiles, maxTilesX, maxTilesY;
	UINT32 xIdx, yIdx, regionNbRects;
	UINT32 gridRelX, gridRelY, ax, ay, bytesPerPixel;
	RFX_TILE* tile;
	RFX_RECT* rfxRect;
	RFX_MESSAGE* message = NULL;
	PTP_WORK* workObject = NULL;
	RFX_TILE_COMPOSE_WORK_PARAM* workParam = NULL;
	BOOL success = FALSE;
	REGION16 rectsRegion, tilesRegion;
	RECTANGLE_16 currentTileRect;
	const RECTANGLE_16* regionRect;
	const RECTANGLE_16* extents;
	assert(data);
	assert(rects);
	assert(numRects > 0);
	assert(w > 0);
	assert(h > 0);
	assert(s > 0);

	if (!(message = (RFX_MESSAGE*)calloc(1, sizeof(RFX_MESSAGE))))
		return NULL;

	region16_init(&tilesRegion);
	region16_init(&rectsRegion);

	if (context->state == RFX_STATE_SEND_HEADERS)
		rfx_update_context_properties(context);

	message->frameIdx = context->frameIdx++;

	if (!context->numQuant)
	{
		if (!(context->quants = (UINT32*)malloc(sizeof(rfx_default_quantization_values))))
			goto skip_encoding_loop;

		CopyMemory(context->quants, &rfx_default_quantization_values,
		           sizeof(rfx_default_quantization_values));
		context->numQuant = 1;
		context->quantIdxY = 0;
		context->quantIdxCb = 0;
		context->quantIdxCr = 0;
	}

	message->numQuant = context->numQuant;
	message->quantVals = context->quants;
	bytesPerPixel = (context->bits_per_pixel / 8);

	if (!computeRegion(rects, numRects, &rectsRegion, width, height))
		goto skip_encoding_loop;

	extents = region16_extents(&rectsRegion);
	assert(extents->right - extents->left > 0);
	assert(extents->bottom - extents->top > 0);
	maxTilesX = 1 + TILE_NO(extents->right - 1) - TILE_NO(extents->left);
	maxTilesY = 1 + TILE_NO(extents->bottom - 1) - TILE_NO(extents->top);
	maxNbTiles = maxTilesX * maxTilesY;

	if (!(message->tiles = calloc(maxNbTiles, sizeof(RFX_TILE*))))
		goto skip_encoding_loop;

	if (!setupWorkers(context, maxNbTiles))
		goto skip_encoding_loop;

	if (context->priv->UseThreads)
	{
		workObject = context->priv->workObjects;
		workParam = context->priv->tileWorkParams;
	}

	regionRect = region16_rects(&rectsRegion, &regionNbRects);

	if (!(message->rects = calloc(regionNbRects, sizeof(RFX_RECT))))
		goto skip_encoding_loop;

	message->numRects = regionNbRects;

	for (i = 0, rfxRect = message->rects; i < regionNbRects; i++, regionRect++, rfxRect++)
	{
		UINT32 startTileX = regionRect->left / 64;
		UINT32 endTileX = (regionRect->right - 1) / 64;
		UINT32 startTileY = regionRect->top / 64;
		UINT32 endTileY = (regionRect->bottom - 1) / 64;
		rfxRect->x = regionRect->left;
		rfxRect->y = regionRect->top;
		rfxRect->width = (regionRect->right - regionRect->left);
		rfxRect->height = (regionRect->bottom - regionRect->top);

		for (yIdx = startTileY, gridRelY = startTileY * 64; yIdx <= endTileY;
		     yIdx++, gridRelY += 64)
		{
			UINT32 tileHeight = 64;

			if ((yIdx == endTileY) && (gridRelY + 64 > height))
				tileHeight = height - gridRelY;

			currentTileRect.top = gridRelY;
			currentTileRect.bottom = gridRelY + tileHeight;

			for (xIdx = startTileX, gridRelX = startTileX * 64; xIdx <= endTileX;
			     xIdx++, gridRelX += 64)
			{
				int tileWidth = 64;

				if ((xIdx == endTileX) && (gridRelX + 64 > width))
					tileWidth = width - gridRelX;

				currentTileRect.left = gridRelX;
				currentTileRect.right = gridRelX + tileWidth;

				/* checks if this tile is already treated */
				if (region16_intersects_rect(&tilesRegion, &currentTileRect))
					continue;

				if (!(tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool)))
					goto skip_encoding_loop;

				tile->xIdx = xIdx;
				tile->yIdx = yIdx;
				tile->x = gridRelX;
				tile->y = gridRelY;
				tile->scanline = scanline;
				tile->width = tileWidth;
				tile->height = tileHeight;
				ax = gridRelX;
				ay = gridRelY;

				if (tile->data && tile->allocated)
				{
					free(tile->data);
					tile->allocated = FALSE;
				}

				tile->data = &data[(ay * scanline) + (ax * bytesPerPixel)];
				tile->quantIdxY = context->quantIdxY;
				tile->quantIdxCb = context->quantIdxCb;
				tile->quantIdxCr = context->quantIdxCr;
				tile->YLen = tile->CbLen = tile->CrLen = 0;

				if (!(tile->YCbCrData = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1)))
					goto skip_encoding_loop;

				tile->YData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 0) + 16]);
				tile->CbData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 1) + 16]);
				tile->CrData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 2) + 16]);
				message->tiles[message->numTiles] = tile;
				message->numTiles++;

				if (context->priv->UseThreads)
				{
					workParam->context = context;
					workParam->tile = tile;

					if (!(*workObject = CreateThreadpoolWork(rfx_compose_message_tile_work_callback,
					                                         (void*)workParam,
					                                         &context->priv->ThreadPoolEnv)))
					{
						goto skip_encoding_loop;
					}

					SubmitThreadpoolWork(*workObject);
					workObject++;
					workParam++;
				}
				else
				{
					rfx_encode_rgb(context, tile);
				}

				if (!region16_union_rect(&tilesRegion, &tilesRegion, &currentTileRect))
					goto skip_encoding_loop;
			} /* xIdx */
		}     /* yIdx */
	}         /* rects */

	success = TRUE;
skip_encoding_loop:

	if (success && message->numTiles != maxNbTiles)
	{
		if (message->numTiles > 0)
		{
			void* pmem = realloc((void*)message->tiles, sizeof(RFX_TILE*) * message->numTiles);

			if (pmem)
				message->tiles = (RFX_TILE**)pmem;
			else
				success = FALSE;
		}
		else
			success = FALSE;
	}

	/* when using threads ensure all computations are done */
	if (success)
	{
		message->tilesDataSize = 0;
		workObject = context->priv->workObjects;

		for (i = 0; i < message->numTiles; i++)
		{
			tile = message->tiles[i];

			if (context->priv->UseThreads)
			{
				if (*workObject)
				{
					WaitForThreadpoolWorkCallbacks(*workObject, FALSE);
					CloseThreadpoolWork(*workObject);
				}

				workObject++;
			}

			message->tilesDataSize += rfx_tile_length(tile);
		}

		region16_uninit(&tilesRegion);
		region16_uninit(&rectsRegion);

		return message;
	}

	WLog_ERR(TAG, "%s: failed", __FUNCTION__);
	message->freeRects = TRUE;
	rfx_message_free(context, message);
	return NULL;
}


static BOOL rfx_process_message_channels(RFX_CONTEXT* context, wStream* s)
{
	BYTE channelId;
	BYTE numChannels;
	context->decodedHeaderBlocks &= ~_RFX_DECODED_CHANNELS;

	if (Stream_GetRemainingLength(s) < 1)
	{
		WLog_ERR(TAG, "RfxMessageChannels packet too small");
		return FALSE;
	}

	Stream_Read_UINT8(s, numChannels); /* numChannels (1 byte), must bet set to 0x01 */

	/* In RDVH sessions, numChannels will represent the number of virtual monitors
	 * configured and does not always be set to 0x01 as [MS-RDPRFX] said.
	 */
	if (numChannels < 1)
	{
		WLog_ERR(TAG, "no channels announced");
		return FALSE;
	}

	if (Stream_GetRemainingLength(s) < (size_t)(numChannels * 5))
	{
		WLog_ERR(TAG, "RfxMessageChannels packet too small for numChannels=%" PRIu8 "",
		         numChannels);
		return FALSE;
	}

	/* RFX_CHANNELT */
	Stream_Read_UINT8(s, channelId); /* channelId (1 byte), must be set to 0x00 */

	if (channelId != 0x00)
	{
		WLog_ERR(TAG, "channelId:0x%02" PRIX8 ", expected:0x00", channelId);
		return FALSE;
	}

	Stream_Read_UINT16(s, context->width);  /* width (2 bytes) */
	Stream_Read_UINT16(s, context->height); /* height (2 bytes) */

	if (!context->width || !context->height)
	{
		WLog_ERR(TAG, "%s: invalid channel with/height: %" PRIu16 "x%" PRIu16 "", __FUNCTION__,
		         context->width, context->height);
		return FALSE;
	}

	/* Now, only the first monitor can be used, therefore the other channels will be ignored. */
	Stream_Seek(s, 5 * (numChannels - 1));
	WLog_Print(context->priv->log, WLOG_DEBUG,
	           "numChannels %" PRIu8 " id %" PRIu8 ", %" PRIu16 "x%" PRIu16 ".", numChannels,
	           channelId, context->width, context->height);
	context->decodedHeaderBlocks |= _RFX_DECODED_CHANNELS;
	return TRUE;
}


static BOOL rfx_process_message_frame_end(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s,
                                          UINT16* pExpectedBlockType)
{
	if (*pExpectedBlockType != WBT_FRAME_END)
	{
		WLog_ERR(TAG, "%s: message unexpected, wants WBT_FRAME_END", __FUNCTION__);
		return FALSE;
	}

	*pExpectedBlockType = WBT_FRAME_BEGIN;
	WLog_Print(context->priv->log, WLOG_DEBUG, "RFX_FRAME_END");
	return TRUE;
}


static RFX_TILE* rfx_message_get_tile(RFX_MESSAGE* message, UINT32 index)
{
	return message->tiles[index];
}


static int rfx_tile_length(RFX_TILE* tile)
{
	return 19 + tile->YLen + tile->CbLen + tile->CrLen;
}


static void rfx_write_message_sync(RFX_CONTEXT* context, wStream* s)
{
	Stream_Write_UINT16(s, WBT_SYNC);       /* BlockT.blockType (2 bytes) */
	Stream_Write_UINT32(s, 12);             /* BlockT.blockLen (4 bytes) */
	Stream_Write_UINT32(s, WF_MAGIC);       /* magic (4 bytes) */
	Stream_Write_UINT16(s, WF_VERSION_1_0); /* version (2 bytes) */
}


static void oss_dump_info (struct oss_params *req, struct oss_params *obt)
{
    dolog ("parameter | requested value | obtained value\n");
    dolog ("format    |      %10d |     %10d\n", req->fmt, obt->fmt);
    dolog ("channels  |      %10d |     %10d\n",
           req->nchannels, obt->nchannels);
    dolog ("frequency |      %10d |     %10d\n", req->freq, obt->freq);
    dolog ("nfrags    |      %10d |     %10d\n", req->nfrags, obt->nfrags);
    dolog ("fragsize  |      %10d |     %10d\n",
           req->fragsize, obt->fragsize);
}


static void *oss_audio_init(Audiodev *dev)
{
    AudiodevOssOptions *oopts;
    assert(dev->driver == AUDIODEV_DRIVER_OSS);

    oopts = &dev->u.oss;
    oss_init_per_direction(oopts->in);
    oss_init_per_direction(oopts->out);

    if (access(oopts->in->has_dev ? oopts->in->dev : "/dev/dsp",
               R_OK | W_OK) < 0 ||
        access(oopts->out->has_dev ? oopts->out->dev : "/dev/dsp",
               R_OK | W_OK) < 0) {
        return NULL;
    }
    return dev;
}


Status RoleGraph::createRole(const RoleName& role) {
    if (roleExists(role)) {
        return Status(ErrorCodes::Error(51007),
                      str::stream() << "Role: " << role.getFullName() << " already exists");
    }

    _createRoleDontCheckIfRoleExists(role);
    return Status::OK();
}


Status RoleGraph::deleteRole(const RoleName& role) {
    if (!roleExists(role)) {
        return Status(ErrorCodes::RoleNotFound,
                      str::stream() << "Role: " << role.getFullName() << " does not exist");
    }
    if (isBuiltinRole(role)) {
        return Status(ErrorCodes::InvalidRoleModification,
                      str::stream() << "Cannot delete built-in role: " << role.getFullName());
    }

    for (std::vector<RoleName>::iterator it = _roleToSubordinates[role].begin();
         it != _roleToSubordinates[role].end();
         ++it) {
        _roleToMembers[*it].erase(
            std::find(_roleToMembers[*it].begin(), _roleToMembers[*it].end(), role));
    }
    for (std::vector<RoleName>::iterator it = _roleToMembers[role].begin();
         it != _roleToMembers[role].end();
         ++it) {
        _roleToSubordinates[*it].erase(
            std::find(_roleToSubordinates[*it].begin(), _roleToSubordinates[*it].end(), role));
    }
    _roleToSubordinates.erase(role);
    _roleToIndirectSubordinates.erase(role);
    _roleToMembers.erase(role);
    _directPrivilegesForRole.erase(role);
    _allPrivilegesForRole.erase(role);
    _allRoles.erase(role);
    return Status::OK();
}


Status RoleGraph::addRoleToRole(const RoleName& recipient, const RoleName& role) {
    if (!roleExists(recipient)) {
        return Status(ErrorCodes::RoleNotFound,
                      str::stream() << "Role: " << recipient.getFullName() << " does not exist");
    }
    if (isBuiltinRole(recipient)) {
        return Status(ErrorCodes::InvalidRoleModification,
                      str::stream()
                          << "Cannot grant roles to built-in role: " << role.getFullName());
    }
    if (!roleExists(role)) {
        return Status(ErrorCodes::RoleNotFound,
                      str::stream() << "Role: " << role.getFullName() << " does not exist");
    }

    if (std::find(_roleToSubordinates[recipient].begin(),
                  _roleToSubordinates[recipient].end(),
                  role) == _roleToSubordinates[recipient].end()) {
        // Only add role if it's not already present
        _roleToSubordinates[recipient].push_back(role);
        _roleToMembers[role].push_back(recipient);
    }

    return Status::OK();
}


Status RoleGraph::removeAllRolesFromRole(const RoleName& victim) {
    typedef std::vector<RoleName> RoleNameVector;
    if (!roleExists(victim)) {
        return Status(ErrorCodes::RoleNotFound,
                      str::stream() << "Role: " << victim.getFullName() << " does not exist");
    }
    if (isBuiltinRole(victim)) {
        return Status(ErrorCodes::InvalidRoleModification,
                      str::stream()
                          << "Cannot remove roles from built-in role: " << victim.getFullName());
    }

    RoleNameVector& subordinatesOfVictim = _roleToSubordinates[victim];
    for (RoleNameVector::const_iterator subordinateRole = subordinatesOfVictim.begin(),
                                        end = subordinatesOfVictim.end();
         subordinateRole != end;
         ++subordinateRole) {
        RoleNameVector& membersOfSubordinate = _roleToMembers[*subordinateRole];
        RoleNameVector::iterator toErase =
            std::find(membersOfSubordinate.begin(), membersOfSubordinate.end(), victim);
        fassert(17173, toErase != membersOfSubordinate.end());
        membersOfSubordinate.erase(toErase);
    }
    subordinatesOfVictim.clear();
    return Status::OK();
}


RoleNameIterator RoleGraph::getDirectSubordinates(const RoleName& role) {
    if (!roleExists(role))
        return RoleNameIterator(NULL);
    return makeRoleNameIteratorForContainer(_roleToSubordinates[role]);
}


Status RoleGraph::removeAllPrivilegesFromRole(const RoleName& role) {
    if (!roleExists(role)) {
        return Status(ErrorCodes::RoleNotFound,
                      mongoutils::str::stream() << "Role: " << role.getFullName()
                                                << " does not exist");
    }
    if (isBuiltinRole(role)) {
        return Status(ErrorCodes::InvalidRoleModification,
                      mongoutils::str::stream() << "Cannot remove privileges from built-in role: "
                                                << role.getFullName());
    }
    _directPrivilegesForRole[role].clear();
    return Status::OK();
}


TEST_F(AuthorizationManagerLogOpTest, testDropAuthCollectionAddsRecoveryUnits) {
    authzManager->logOp(&opCtx,
                        "c",
                        {"admin", "$cmd"},
                        BSON("drop"
                             << "system.users"),
                        nullptr);
    ASSERT_EQ(size_t(1), registeredChanges);

    authzManager->logOp(&opCtx,
                        "c",
                        {"admin", "$cmd"},
                        BSON("drop"
                             << "system.roles"),
                        nullptr);
    ASSERT_EQ(size_t(2), registeredChanges);

    authzManager->logOp(&opCtx,
                        "c",
                        {"admin", "$cmd"},
                        BSON("drop"
                             << "system.version"),
                        nullptr);
    ASSERT_EQ(size_t(3), registeredChanges);

    authzManager->logOp(&opCtx,
                        "c",
                        {"admin", "$cmd"},
                        BSON("drop"
                             << "system.profile"),
                        nullptr);
    ASSERT_EQ(size_t(3), registeredChanges);
}


TEST_F(AuthorizationManagerTest, testAcquireV2UserWithUnrecognizedActions) {
    OperationContextNoop opCtx;

    ASSERT_OK(
        externalState->insertPrivilegeDocument(&opCtx,
                                               BSON("_id"
                                                    << "admin.myUser"
                                                    << "user"
                                                    << "myUser"
                                                    << "db"
                                                    << "test"
                                                    << "credentials"
                                                    << BSON("MONGODB-CR"
                                                            << "password")
                                                    << "roles"
                                                    << BSON_ARRAY(BSON("role"
                                                                       << "myRole"
                                                                       << "db"
                                                                       << "test"))
                                                    << "inheritedPrivileges"
                                                    << BSON_ARRAY(BSON(
                                                           "resource" << BSON("db"
                                                                              << "test"
                                                                              << "collection"
                                                                              << "")
                                                                      << "actions"
                                                                      << BSON_ARRAY("find"
                                                                                << "fakeAction"
                                                                                << "insert")))),
                                               BSONObj()));

    User* myUser;
    ASSERT_OK(authzManager->acquireUser(&opCtx, UserName("myUser", "test"), &myUser));
    ASSERT_EQUALS(UserName("myUser", "test"), myUser->getName());
    ASSERT(myUser->isValid());
    ASSERT_EQUALS(1U, myUser->getRefCount());
    RoleNameIterator roles = myUser->getRoles();
    ASSERT_EQUALS(RoleName("myRole", "test"), roles.next());
    ASSERT_FALSE(roles.more());
    auto privilegeMap = myUser->getPrivileges();
    auto testDBPrivilege = privilegeMap[ResourcePattern::forDatabaseName("test")];
    ActionSet actions = testDBPrivilege.getActions();
    ASSERT(actions.contains(ActionType::find));
    ASSERT(actions.contains(ActionType::insert));
    actions.removeAction(ActionType::find);
    actions.removeAction(ActionType::insert);
    ASSERT(actions.empty());

    // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure
    authzManager->releaseUser(myUser);
}


virDomainMemoryDefCheckABIStability(virDomainMemoryDefPtr src,
                                    virDomainMemoryDefPtr dst)
{
    if (src->model != dst->model) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target memory device model '%s' "
                         "doesn't match source model '%s'"),
                       virDomainMemoryModelTypeToString(dst->model),
                       virDomainMemoryModelTypeToString(src->model));
        return false;
    }

    if (src->targetNode != dst->targetNode) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target memory device targetNode '%d' "
                         "doesn't match source targetNode '%d'"),
                       dst->targetNode, src->targetNode);
        return false;
    }

    if (src->size != dst->size) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target memory device size '%llu' doesn't match "
                         "source memory device size '%llu'"),
                       dst->size, src->size);
        return false;
    }

    if (src->model == VIR_DOMAIN_MEMORY_MODEL_NVDIMM) {
        if (src->labelsize != dst->labelsize) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Target NVDIMM label size '%llu' doesn't match "
                             "source NVDIMM label size '%llu'"),
                           src->labelsize, dst->labelsize);
            return false;
        }

        if (src->alignsize != dst->alignsize) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Target NVDIMM alignment '%llu' doesn't match "
                             "source NVDIMM alignment '%llu'"),
                           src->alignsize, dst->alignsize);
            return false;
        }

        if (src->nvdimmPmem != dst->nvdimmPmem) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Target NVDIMM pmem flag doesn't match "
                             "source NVDIMM pmem flag"));
            return false;
        }

        if (src->readonly != dst->readonly) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("Target NVDIMM readonly flag doesn't match "
                             "source NVDIMM readonly flag"));
            return false;
        }
    }

    return virDomainDeviceInfoCheckABIStability(&src->info, &dst->info);
}


virDomainTimerDefFormat(virBufferPtr buf,
                        virDomainTimerDefPtr def)
{
    const char *name = virDomainTimerNameTypeToString(def->name);

    if (!name) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unexpected timer name %d"), def->name);
        return -1;
    }
    virBufferAsprintf(buf, "<timer name='%s'", name);

    if (def->present == 0) {
        virBufferAddLit(buf, " present='no'");
    } else if (def->present == 1) {
        virBufferAddLit(buf, " present='yes'");
    }

    if (def->tickpolicy != -1) {
        const char *tickpolicy
            = virDomainTimerTickpolicyTypeToString(def->tickpolicy);
        if (!tickpolicy) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("unexpected timer tickpolicy %d"),
                           def->tickpolicy);
            return -1;
        }
        virBufferAsprintf(buf, " tickpolicy='%s'", tickpolicy);
    }

    if ((def->name == VIR_DOMAIN_TIMER_NAME_PLATFORM)
        || (def->name == VIR_DOMAIN_TIMER_NAME_RTC)) {
        if (def->track != -1) {
            const char *track
                = virDomainTimerTrackTypeToString(def->track);
            if (!track) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unexpected timer track %d"),
                               def->track);
                return -1;
            }
            virBufferAsprintf(buf, " track='%s'", track);
        }
    }

    if (def->name == VIR_DOMAIN_TIMER_NAME_TSC) {
        if (def->frequency > 0)
            virBufferAsprintf(buf, " frequency='%lu'", def->frequency);

        if (def->mode != -1) {
            const char *mode
                = virDomainTimerModeTypeToString(def->mode);
            if (!mode) {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unexpected timer mode %d"),
                               def->mode);
                return -1;
            }
            virBufferAsprintf(buf, " mode='%s'", mode);
        }
    }

    if (def->catchup.threshold == 0 && def->catchup.slew == 0 &&
        def->catchup.limit == 0) {
        virBufferAddLit(buf, "/>\n");
    } else {
        virBufferAddLit(buf, ">\n");
        virBufferAdjustIndent(buf, 2);
        virBufferAddLit(buf, "<catchup");
        if (def->catchup.threshold > 0)
            virBufferAsprintf(buf, " threshold='%lu'", def->catchup.threshold);
        if (def->catchup.slew > 0)
            virBufferAsprintf(buf, " slew='%lu'", def->catchup.slew);
        if (def->catchup.limit > 0)
            virBufferAsprintf(buf, " limit='%lu'", def->catchup.limit);
        virBufferAddLit(buf, "/>\n");
        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</timer>\n");
    }

    return 0;
}


virDomainDiskDefValidate(const virDomainDef *def,
                         const virDomainDiskDef *disk)
{
    virStorageSourcePtr next;

    /* Validate LUN configuration */
    if (disk->device == VIR_DOMAIN_DISK_DEVICE_LUN) {
        /* volumes haven't been translated at this point, so accept them */
        if (!(disk->src->type == VIR_STORAGE_TYPE_BLOCK ||
              disk->src->type == VIR_STORAGE_TYPE_VOLUME ||
              (disk->src->type == VIR_STORAGE_TYPE_NETWORK &&
               disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI))) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("disk '%s' improperly configured for a "
                             "device='lun'"), disk->dst);
            return -1;
        }
    }

    if (disk->src->pr &&
        disk->device != VIR_DOMAIN_DISK_DEVICE_LUN) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("<reservations/> allowed only for lun devices"));
        return -1;
    }

    /* Reject disks with a bus type that is not compatible with the
     * given address type. The function considers only buses that are
     * handled in common code. For other bus types it's not possible
     * to decide compatibility in common code.
     */
    if (!virDomainDiskAddressDiskBusCompatibility(disk->bus, disk->info.type)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Invalid address type '%s' for the disk '%s' with the bus type '%s'"),
                       virDomainDeviceAddressTypeToString(disk->info.type),
                       disk->dst,
                       virDomainDiskBusTypeToString(disk->bus));
        return -1;
    }

    if (disk->queues && disk->bus != VIR_DOMAIN_DISK_BUS_VIRTIO) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("queues attribute in disk driver element is only "
                         "supported by virtio-blk"));
        return -1;
    }

    if (disk->bus != VIR_DOMAIN_DISK_BUS_VIRTIO &&
        (disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO ||
         disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO_TRANSITIONAL ||
         disk->model == VIR_DOMAIN_DISK_MODEL_VIRTIO_NON_TRANSITIONAL)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("disk model '%s' not supported for bus '%s'"),
                       virDomainDiskModelTypeToString(disk->model),
                       virDomainDiskBusTypeToString(disk->bus));
        return -1;
    }

    if (disk->src->type == VIR_STORAGE_TYPE_NVME) {
        /* NVMe namespaces start from 1 */
        if (disk->src->nvme->namespc == 0) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("NVMe namespace can't be zero"));
            return -1;
        }
    }

    for (next = disk->src; next; next = next->backingStore) {
        if (virSecurityDeviceLabelDefValidateXML(next->seclabels,
                                                 next->nseclabels,
                                                 def->seclabels,
                                                 def->nseclabels) < 0)
            return -1;
    }

    return 0;
}


virDomainMemorytuneDefParse(virDomainDefPtr def,
                            xmlXPathContextPtr ctxt,
                            xmlNodePtr node,
                            unsigned int flags)
{
    VIR_XPATH_NODE_AUTORESTORE(ctxt);
    virDomainResctrlDefPtr resctrl = NULL;
    virDomainResctrlDefPtr newresctrl = NULL;
    g_autoptr(virBitmap) vcpus = NULL;
    g_autofree xmlNodePtr *nodes = NULL;
    g_autoptr(virResctrlAlloc) alloc = NULL;
    ssize_t i = 0;
    size_t nmons = 0;
    size_t ret = -1;

    int n;

    ctxt->node = node;

    if (virDomainResctrlParseVcpus(def, node, &vcpus) < 0)
        return -1;

    if (virBitmapIsAllClear(vcpus))
        return 0;

    if ((n = virXPathNodeSet("./node", ctxt, &nodes)) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Cannot extract memory nodes under memorytune"));
        return -1;
    }

    if (virDomainResctrlVcpuMatch(def, vcpus, &resctrl) < 0)
        return -1;

    if (resctrl) {
        alloc = virObjectRef(resctrl->alloc);
    } else {
        if (!(alloc = virResctrlAllocNew()))
            return -1;
    }

    /* First, parse <memorytune/node> element if any <node> element exists */
    for (i = 0; i < n; i++) {
        if (virDomainMemorytuneDefParseMemory(ctxt, nodes[i], alloc) < 0)
            return -1;
    }

    /*
     * If this is a new allocation, format ID and append to resctrl, otherwise
     * just update the existing alloc information, which is done in above
     * virDomainMemorytuneDefParseMemory */
    if (!resctrl) {
        if (!(newresctrl = virDomainResctrlNew(node, alloc, vcpus, flags)))
            return -1;

        resctrl = newresctrl;
    }

    /* Next, parse <memorytune/monitor> element */
    nmons = resctrl->nmonitors;
    if (virDomainResctrlMonDefParse(def, ctxt, node,
                                    VIR_RESCTRL_MONITOR_TYPE_MEMBW,
                                    resctrl) < 0)
        goto cleanup;

    nmons = resctrl->nmonitors - nmons;
    /* Now @nmons contains the new <monitor> element number found in current
     * <memorytune> element, and @n holds the number of new <node> element,
     * only append the new @newresctrl object to domain if any of them is
     * not zero. */
    if (newresctrl && (nmons || n)) {
        if (VIR_APPEND_ELEMENT(def->resctrls, def->nresctrls, newresctrl) < 0)
            goto cleanup;
    }

    ret = 0;
 cleanup:
    virDomainResctrlDefFree(newresctrl);
    return ret;
}


virSysinfoBaseBoardParseXML(xmlXPathContextPtr ctxt,
                            virSysinfoBaseBoardDefPtr *baseBoard,
                            size_t *nbaseBoard)
{
    size_t i, nboards = 0;
    VIR_XPATH_NODE_AUTORESTORE(ctxt);
    int n;
    g_autofree virSysinfoBaseBoardDefPtr boards = NULL;
    g_autofree xmlNodePtr *nodes = NULL;

    if ((n = virXPathNodeSet("./baseBoard", ctxt, &nodes)) < 0)
        return -1;

    if (n && VIR_ALLOC_N(boards, n) < 0)
        return -1;

    for (i = 0; i < n; i++) {
        virSysinfoBaseBoardDefPtr def = boards + nboards;

        ctxt->node = nodes[i];

        def->manufacturer =
            virXPathString("string(entry[@name='manufacturer'])", ctxt);
        def->product =
            virXPathString("string(entry[@name='product'])", ctxt);
        def->version =
            virXPathString("string(entry[@name='version'])", ctxt);
        def->serial =
            virXPathString("string(entry[@name='serial'])", ctxt);
        def->asset =
            virXPathString("string(entry[@name='asset'])", ctxt);
        def->location =
            virXPathString("string(entry[@name='location'])", ctxt);

        if (!def->manufacturer && !def->product && !def->version &&
            !def->serial && !def->asset && !def->location) {
            /* nada */
        } else {
            nboards++;
        }
    }

    *baseBoard = g_steal_pointer(&boards);
    *nbaseBoard = nboards;

    return 0;
}


virDomainDiskSourceNVMeParse(xmlNodePtr node,
                             xmlXPathContextPtr ctxt,
                             virStorageSourcePtr src)
{
    g_autoptr(virStorageSourceNVMeDef) nvme = NULL;
    g_autofree char *type = NULL;
    g_autofree char *namespc = NULL;
    g_autofree char *managed = NULL;
    xmlNodePtr address;

    nvme = g_new0(virStorageSourceNVMeDef, 1);

    if (!(type = virXMLPropString(node, "type"))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing 'type' attribute to disk source"));
        return -1;
    }

    if (STRNEQ(type, "pci")) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("unsupported source type '%s'"),
                       type);
        return -1;
    }

    if (!(namespc = virXMLPropString(node, "namespace"))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing 'namespace' attribute to disk source"));
        return -1;
    }

    if (virStrToLong_ull(namespc, NULL, 10, &nvme->namespc) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("malformed namespace '%s'"),
                       namespc);
        return -1;
    }

    if ((managed = virXMLPropString(node, "managed"))) {
        if ((nvme->managed = virTristateBoolTypeFromString(managed)) <= 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("malformed managed value '%s'"),
                           managed);
            return -1;
        }
    }

    if (!(address = virXPathNode("./address", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("NVMe disk source is missing address"));
        return -1;
    }

    if (virPCIDeviceAddressParseXML(address, &nvme->pciAddr) < 0)
        return -1;

    src->nvme = g_steal_pointer(&nvme);
    return 0;
}


virDomainDiskGetFormat(virDomainDiskDefPtr def)
{
    return def->src->format;
}


virDomainInputDefFind(const virDomainDef *def,
                      const virDomainInputDef *input)
{
    size_t i;

    for (i = 0; i < def->ninputs; i++) {
        if (virDomainInputDefEquals(input, def->inputs[i]))
            return i;
    }

    return -1;
}


virDomainHasNet(virDomainDefPtr def, virDomainNetDefPtr net)
{
    size_t i;
    bool PCIAddrSpecified = virDomainDeviceAddressIsValid(&net->info,
                                                          VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI);

    for (i = 0; i < def->nnets; i++) {
        if (virMacAddrCmp(&def->nets[i]->mac, &net->mac))
            continue;

        if (PCIAddrSpecified) {
            if (virPCIDeviceAddressEqual(&def->nets[i]->info.addr.pci,
                                         &net->info.addr.pci))
                return true;
        } else {
            return true;
        }
    }
    return false;
}


virDomainDiskSourcePRParse(xmlNodePtr node,
                           xmlXPathContextPtr ctxt,
                           virStoragePRDefPtr *pr)
{
    VIR_XPATH_NODE_AUTORESTORE(ctxt);

    ctxt->node = node;

    if (!(ctxt->node = virXPathNode("./reservations", ctxt)))
        return 0;

    if (!(*pr = virStoragePRDefParseXML(ctxt)))
        return -1;

    return 0;
}


virDomainDefSave(virDomainDefPtr def,
                 virDomainXMLOptionPtr xmlopt,
                 const char *configDir)
{
    g_autofree char *xml = NULL;

    if (!(xml = virDomainDefFormat(def, xmlopt, VIR_DOMAIN_DEF_FORMAT_SECURE)))
        return -1;

    return virDomainDefSaveXML(def, configDir, xml);
}


virDomainKeyWrapDefParseXML(virDomainDefPtr def, xmlXPathContextPtr ctxt)
{
    size_t i;
    int n;
    g_autofree xmlNodePtr *nodes = NULL;
    g_autofree virDomainKeyWrapDefPtr keywrap = NULL;

    if ((n = virXPathNodeSet("./keywrap/cipher", ctxt, &nodes)) < 0)
        return n;

    if (VIR_ALLOC(keywrap) < 0)
        return -1;

    for (i = 0; i < n; i++) {
        if (virDomainKeyWrapCipherDefParseXML(keywrap, nodes[i]) < 0)
            return -1;
    }

    if (keywrap->aes || keywrap->dea)
        def->keywrap = g_steal_pointer(&keywrap);

    return 0;
}


virDomainHostdevSubsysSCSIVHostDefParseXML(xmlNodePtr sourcenode,
                                           virDomainHostdevDefPtr def)
{
    virDomainHostdevSubsysSCSIVHostPtr hostsrc = &def->source.subsys.u.scsi_host;
    g_autofree char *protocol = NULL;
    g_autofree char *wwpn = NULL;

    if (!(protocol = virXMLPropString(sourcenode, "protocol"))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("Missing scsi_host subsystem protocol"));
        return -1;
    }

    if ((hostsrc->protocol =
         virDomainHostdevSubsysSCSIHostProtocolTypeFromString(protocol)) <= 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Unknown scsi_host subsystem protocol '%s'"),
                       protocol);
        return -1;
    }

    switch ((virDomainHostdevSubsysSCSIHostProtocolType) hostsrc->protocol) {
    case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_VHOST:
        if (!(wwpn = virXMLPropString(sourcenode, "wwpn"))) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("missing vhost-scsi hostdev source wwpn"));
            return -1;
        }

        if (!STRPREFIX(wwpn, "naa.") ||
            !virValidateWWN(wwpn + 4)) {
            virReportError(VIR_ERR_XML_ERROR, "%s", _("malformed 'wwpn' value"));
            return -1;
        }
        hostsrc->wwpn = g_steal_pointer(&wwpn);
        break;
    case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_NONE:
    case VIR_DOMAIN_HOSTDEV_SUBSYS_SCSI_HOST_PROTOCOL_TYPE_LAST:
        virReportError(VIR_ERR_XML_ERROR,
                       _("Invalid hostdev protocol '%s'"),
                       virDomainHostdevSubsysSCSIHostProtocolTypeToString(hostsrc->protocol));
        return -1;
        break;
    }

    return 0;
}


virDomainDiskDefParseValidate(const virDomainDiskDef *def)

{
    virStorageSourcePtr next;

    if (def->bus != VIR_DOMAIN_DISK_BUS_VIRTIO) {
        if (def->event_idx != VIR_TRISTATE_SWITCH_ABSENT) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("disk event_idx mode supported only for virtio bus"));
            return -1;
        }

        if (def->ioeventfd != VIR_TRISTATE_SWITCH_ABSENT) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("disk ioeventfd mode supported only for virtio bus"));
            return -1;
        }
    }

    if (def->device != VIR_DOMAIN_DISK_DEVICE_LUN) {
        if (def->rawio != VIR_TRISTATE_BOOL_ABSENT) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("rawio can be used only with device='lun'"));
            return -1;
        }

        if (def->sgio != VIR_DOMAIN_DEVICE_SGIO_DEFAULT) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                           _("sgio can be used only with device='lun'"));
            return -1;
        }
    }

    if (def->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
        def->bus != VIR_DOMAIN_DISK_BUS_FDC) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid bus type '%s' for floppy disk"),
                       virDomainDiskBusTypeToString(def->bus));
        return -1;
    }

    if (def->device != VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
        def->bus == VIR_DOMAIN_DISK_BUS_FDC) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid bus type '%s' for disk"),
                       virDomainDiskBusTypeToString(def->bus));
        return -1;
    }

    if (def->removable != VIR_TRISTATE_SWITCH_ABSENT &&
        def->bus != VIR_DOMAIN_DISK_BUS_USB) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("removable is only valid for usb disks"));
        return -1;
    }

    if (def->startupPolicy != VIR_DOMAIN_STARTUP_POLICY_DEFAULT) {
        if (def->src->type == VIR_STORAGE_TYPE_NETWORK) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("Setting disk %s is not allowed for "
                             "disk of network type"),
                           virDomainStartupPolicyTypeToString(def->startupPolicy));
            return -1;
        }

        if (def->device != VIR_DOMAIN_DISK_DEVICE_CDROM &&
            def->device != VIR_DOMAIN_DISK_DEVICE_FLOPPY &&
            def->startupPolicy == VIR_DOMAIN_STARTUP_POLICY_REQUISITE) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("Setting disk 'requisite' is allowed only for "
                             "cdrom or floppy"));
            return -1;
        }
    }

    for (next = def->src; next; next = next->backingStore) {
        if (virDomainDiskSourceDefParseAuthValidate(next) < 0)
            return -1;

        if (next->encryption) {
            virStorageEncryptionPtr encryption = next->encryption;

            if (encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS &&
                encryption->encinfo.cipher_name) {

                virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                               _("supplying <cipher> for domain disk definition "
                                 "is unnecessary"));
                return -1;
            }
        }
    }

    return 0;
}


virDomainRedirFilterDefFormat(virBufferPtr buf,
                              virDomainRedirFilterDefPtr filter)
{
    size_t i;

    /* no need format an empty redirfilter */
    if (filter->nusbdevs == 0)
        return 0;

    virBufferAddLit(buf, "<redirfilter>\n");
    virBufferAdjustIndent(buf, 2);
    for (i = 0; i < filter->nusbdevs; i++) {
        virDomainRedirFilterUSBDevDefPtr usbdev = filter->usbdevs[i];
        virBufferAddLit(buf, "<usbdev");
        if (usbdev->usbClass >= 0)
            virBufferAsprintf(buf, " class='0x%02X'", usbdev->usbClass);

        if (usbdev->vendor >= 0)
            virBufferAsprintf(buf, " vendor='0x%04X'", usbdev->vendor);

        if (usbdev->product >= 0)
            virBufferAsprintf(buf, " product='0x%04X'", usbdev->product);

        if (usbdev->version >= 0)
            virBufferAsprintf(buf, " version='%d.%02d'",
                                 ((usbdev->version & 0xf000) >> 12) * 10 +
                                 ((usbdev->version & 0x0f00) >>  8),
                                 ((usbdev->version & 0x00f0) >>  4) * 10 +
                                 ((usbdev->version & 0x000f) >>  0));

        virBufferAsprintf(buf, " allow='%s'/>\n", usbdev->allow ? "yes" : "no");

    }
    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</redirfilter>\n");
    return 0;
}


const char *virDomainInputDefGetPath(virDomainInputDefPtr input)
{
    switch ((virDomainInputType) input->type) {
    case VIR_DOMAIN_INPUT_TYPE_MOUSE:
    case VIR_DOMAIN_INPUT_TYPE_TABLET:
    case VIR_DOMAIN_INPUT_TYPE_KBD:
    case VIR_DOMAIN_INPUT_TYPE_LAST:
        return NULL;
        break;

    case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
        return input->source.evdev;
    }
    return NULL;
}


virDomainNetNotifyActualDevice(virConnectPtr conn,
                               virDomainDefPtr dom,
                               virDomainNetDefPtr iface)
{
    virDomainNetType actualType = virDomainNetGetActualType(iface);

    if (virDomainNetCreatePort(conn, dom, iface,
                               VIR_NETWORK_PORT_CREATE_RECLAIM) < 0) {
        return;
    }

    if (actualType == VIR_DOMAIN_NET_TYPE_NETWORK ||
        actualType == VIR_DOMAIN_NET_TYPE_BRIDGE) {
        /*
         * NB: we can't notify the guest of any MTU change anyway,
         * so there is no point in trying to learn the actualMTU
         * (final arg to virNetDevTapReattachBridge())
         */
        ignore_value(virNetDevTapReattachBridge(iface->ifname,
                                                iface->data.network.actual->data.bridge.brname,
                                                &iface->mac, dom->uuid,
                                                virDomainNetGetActualVirtPortProfile(iface),
                                                virDomainNetGetActualVlan(iface),
                                                virDomainNetGetActualPortOptionsIsolated(iface),
                                                iface->mtu, NULL));
    }
}


virDomainDefParseBootOptions(virDomainDefPtr def,
                             xmlXPathContextPtr ctxt)
{
    char *name = NULL;
    size_t i;
    int n;
    g_autofree xmlNodePtr *nodes = NULL;
    g_autofree char *tmp = NULL;

    /*
     * Booting options for different OS types....
     *
     *   - A bootloader (and optional kernel+initrd)  (xen)
     *   - A kernel + initrd                          (xen)
     *   - A boot device (and optional kernel+initrd) (hvm)
     *   - An init script                             (exe)
     */

    if (def->os.type == VIR_DOMAIN_OSTYPE_EXE) {
        def->os.init = virXPathString("string(./os/init[1])", ctxt);
        def->os.cmdline = virXPathString("string(./os/cmdline[1])", ctxt);
        def->os.initdir = virXPathString("string(./os/initdir[1])", ctxt);
        def->os.inituser = virXPathString("string(./os/inituser[1])", ctxt);
        def->os.initgroup = virXPathString("string(./os/initgroup[1])", ctxt);

        if ((n = virXPathNodeSet("./os/initarg", ctxt, &nodes)) < 0)
            return -1;

        if (VIR_ALLOC_N(def->os.initargv, n+1) < 0)
            return -1;
        for (i = 0; i < n; i++) {
            if (!nodes[i]->children ||
                !nodes[i]->children->content) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("No data supplied for <initarg> element"));
                return -1;
            }
            def->os.initargv[i] = g_strdup((const char *)nodes[i]->children->content);
        }
        def->os.initargv[n] = NULL;
        VIR_FREE(nodes);

        if ((n = virXPathNodeSet("./os/initenv", ctxt, &nodes)) < 0)
            return -1;

        if (VIR_ALLOC_N(def->os.initenv, n+1) < 0)
            return -1;
        for (i = 0; i < n; i++) {
            if (!(name = virXMLPropString(nodes[i], "name"))) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                                _("No name supplied for <initenv> element"));
                return -1;
            }

            if (!nodes[i]->children ||
                !nodes[i]->children->content) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("No value supplied for <initenv name='%s'> element"),
                               name);
                return -1;
            }

            if (VIR_ALLOC(def->os.initenv[i]) < 0)
                return -1;

            def->os.initenv[i]->name = name;
            def->os.initenv[i]->value = g_strdup((const char *)nodes[i]->children->content);
        }
        def->os.initenv[n] = NULL;
        VIR_FREE(nodes);
    }

    if (def->os.type == VIR_DOMAIN_OSTYPE_XEN ||
        def->os.type == VIR_DOMAIN_OSTYPE_XENPVH ||
        def->os.type == VIR_DOMAIN_OSTYPE_HVM ||
        def->os.type == VIR_DOMAIN_OSTYPE_UML) {
        g_autofree char *firmware = NULL;
        xmlNodePtr loader_node;

        def->os.kernel = virXPathString("string(./os/kernel[1])", ctxt);
        def->os.initrd = virXPathString("string(./os/initrd[1])", ctxt);
        def->os.cmdline = virXPathString("string(./os/cmdline[1])", ctxt);
        def->os.dtb = virXPathString("string(./os/dtb[1])", ctxt);
        def->os.root = virXPathString("string(./os/root[1])", ctxt);

        if (def->os.type == VIR_DOMAIN_OSTYPE_HVM &&
            (firmware = virXPathString("string(./os/@firmware)", ctxt))) {
            int fw = virDomainOsDefFirmwareTypeFromString(firmware);

            if (fw <= 0) {
                virReportError(VIR_ERR_XML_ERROR,
                               _("unknown firmware value %s"),
                               firmware);
                return -1;
            }

            def->os.firmware = fw;
        }

        if ((loader_node = virXPathNode("./os/loader[1]", ctxt))) {
            const bool fwAutoSelect = def->os.firmware != VIR_DOMAIN_OS_DEF_FIRMWARE_NONE;

            if (VIR_ALLOC(def->os.loader) < 0)
                return -1;

            if (virDomainLoaderDefParseXML(loader_node,
                                           def->os.loader,
                                           fwAutoSelect) < 0)
                return -1;

            def->os.loader->nvram = virXPathString("string(./os/nvram[1])", ctxt);
            if (!fwAutoSelect)
                def->os.loader->templt = virXPathString("string(./os/nvram[1]/@template)", ctxt);
        }
    }

    if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
        if ((n = virXPathNodeSet("./os/acpi/table", ctxt, &nodes)) < 0)
            return -1;

        if (n > 1) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("Only one acpi table is supported"));
            return -1;
        }

        if (n == 1) {
            tmp = virXMLPropString(nodes[0], "type");

            if (!tmp) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("Missing acpi table type"));
                return -1;
            }

            if (STREQ_NULLABLE(tmp, "slic")) {
                VIR_FREE(tmp);
                tmp = virXMLNodeContentString(nodes[0]);
                def->os.slic_table = virFileSanitizePath(tmp);
            } else {
                virReportError(VIR_ERR_XML_ERROR,
                               _("Unknown acpi table type: %s"),
                               tmp);
                return -1;
            }
        }

        if (virDomainDefParseBootXML(ctxt, def) < 0)
            return -1;
    }

    return 0;
}


virDomainHostdevMatch(virDomainHostdevDefPtr a,
                      virDomainHostdevDefPtr b)
{
    if (a->mode != b->mode)
        return 0;

    switch (a->mode) {
    case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
        return virDomainHostdevMatchSubsys(a, b);
    case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
        return virDomainHostdevMatchCaps(a, b);
    }
    return 0;
}


virDomainChrSourceReconnectDefFormat(virBufferPtr buf,
                                     virDomainChrSourceReconnectDefPtr def)
{
    if (def->enabled == VIR_TRISTATE_BOOL_ABSENT)
        return;

    virBufferAsprintf(buf, "<reconnect enabled='%s'",
                      virTristateBoolTypeToString(def->enabled));

    if (def->enabled == VIR_TRISTATE_BOOL_YES)
        virBufferAsprintf(buf, " timeout='%u'", def->timeout);

    virBufferAddLit(buf, "/>\n");
}


virDomainMemoryFindByDefInternal(virDomainDefPtr def,
                                 virDomainMemoryDefPtr mem,
                                 bool allowAddressFallback)
{
    size_t i;

    for (i = 0; i < def->nmems; i++) {
        virDomainMemoryDefPtr tmp = def->mems[i];

        /* address, if present */
        if (allowAddressFallback) {
            if (tmp->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE)
                continue;
        } else {
            if (mem->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
                !virDomainDeviceInfoAddressIsEqual(&tmp->info, &mem->info))
                continue;
        }

        /* alias, if present */
        if (mem->info.alias &&
            STRNEQ_NULLABLE(tmp->info.alias, mem->info.alias))
            continue;

        /* target info -> always present */
        if (tmp->model != mem->model ||
            tmp->targetNode != mem->targetNode ||
            tmp->size != mem->size)
            continue;

        switch ((virDomainMemoryModel) mem->model) {
        case VIR_DOMAIN_MEMORY_MODEL_DIMM:
            /* source stuff -> match with device */
            if (tmp->pagesize != mem->pagesize)
                continue;

            if (!virBitmapEqual(tmp->sourceNodes, mem->sourceNodes))
                continue;
            break;

        case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
            if (STRNEQ(tmp->nvdimmPath, mem->nvdimmPath))
                continue;
            break;

        case VIR_DOMAIN_MEMORY_MODEL_NONE:
        case VIR_DOMAIN_MEMORY_MODEL_LAST:
            break;
        }

        break;
    }

    if (i == def->nmems)
        return -1;

    return i;
}


virDomainDiskSetSource(virDomainDiskDefPtr def, const char *src)
{
    char *tmp = g_strdup(src);
    g_free(def->src->path);
    def->src->path = tmp;
    return 0;
}


virDomainDefAddDiskControllersForType(virDomainDefPtr def,
                                      int controllerType,
                                      int diskBus)
{
    size_t i;
    int maxController = -1;

    for (i = 0; i < def->ndisks; i++) {
        if (def->disks[i]->bus != diskBus)
            continue;

        if (def->disks[i]->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_DRIVE)
            continue;

        if ((int)def->disks[i]->info.addr.drive.controller > maxController)
            maxController = def->disks[i]->info.addr.drive.controller;
    }

    if (maxController == -1)
        return 0;

    for (i = 0; i <= maxController; i++) {
        if (virDomainDefMaybeAddController(def, controllerType, i, -1) < 0)
            return -1;
    }

    return 0;
}


virDomainObjUpdateModificationImpact(virDomainObjPtr vm,
                                     unsigned int *flags)
{
    bool isActive = virDomainObjIsActive(vm);

    if ((*flags & (VIR_DOMAIN_AFFECT_LIVE | VIR_DOMAIN_AFFECT_CONFIG)) ==
        VIR_DOMAIN_AFFECT_CURRENT) {
        if (isActive)
            *flags |= VIR_DOMAIN_AFFECT_LIVE;
        else
            *flags |= VIR_DOMAIN_AFFECT_CONFIG;
    }

    if (!isActive && (*flags & VIR_DOMAIN_AFFECT_LIVE)) {
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                       _("domain is not running"));
        return -1;
    }

    if (!vm->persistent && (*flags & VIR_DOMAIN_AFFECT_CONFIG)) {
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                       _("transient domains do not have any "
                         "persistent config"));
        return -1;
    }

    return 0;
}


virDomainDefOSValidate(const virDomainDef *def,
                       virDomainXMLOptionPtr xmlopt)
{
    if (!def->os.loader)
        return 0;

    if (def->os.firmware &&
        !(xmlopt->config.features & VIR_DOMAIN_DEF_FEATURE_FW_AUTOSELECT)) {
        virReportError(VIR_ERR_XML_DETAIL, "%s",
                       _("firmware auto selection not implemented for this driver"));
        return -1;
    }

    if (!def->os.loader->path &&
        def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE) {
        virReportError(VIR_ERR_XML_DETAIL, "%s",
                       _("no loader path specified and firmware auto selection disabled"));
        return -1;
    }

    return 0;
}


virDomainDeviceDefPostParseCommon(virDomainDeviceDefPtr dev,
                                  const virDomainDef *def,
                                  unsigned int parseFlags G_GNUC_UNUSED,
                                  virDomainXMLOptionPtr xmlopt)
{
    int ret = -1;

    switch ((virDomainDeviceType)dev->type) {
    case VIR_DOMAIN_DEVICE_CHR:
        ret = virDomainChrDefPostParse(dev->data.chr, def);
        break;

    case VIR_DOMAIN_DEVICE_RNG:
        ret = virDomainRNGDefPostParse(dev->data.rng);
        break;

    case VIR_DOMAIN_DEVICE_DISK:
        ret = virDomainDiskDefPostParse(dev->data.disk, def, xmlopt);
        break;

    case VIR_DOMAIN_DEVICE_VIDEO:
        ret = virDomainVideoDefPostParse(dev->data.video, def);
        break;

    case VIR_DOMAIN_DEVICE_HOSTDEV:
        ret = virDomainHostdevDefPostParse(dev->data.hostdev, def, xmlopt);
        break;

    case VIR_DOMAIN_DEVICE_CONTROLLER:
        ret = virDomainControllerDefPostParse(dev->data.controller);
        break;

    case VIR_DOMAIN_DEVICE_NET:
        ret = virDomainNetDefPostParse(dev->data.net);
        break;

    case VIR_DOMAIN_DEVICE_VSOCK:
        ret = virDomainVsockDefPostParse(dev->data.vsock);
        break;

    case VIR_DOMAIN_DEVICE_LEASE:
    case VIR_DOMAIN_DEVICE_FS:
    case VIR_DOMAIN_DEVICE_INPUT:
    case VIR_DOMAIN_DEVICE_SOUND:
    case VIR_DOMAIN_DEVICE_WATCHDOG:
    case VIR_DOMAIN_DEVICE_GRAPHICS:
    case VIR_DOMAIN_DEVICE_HUB:
    case VIR_DOMAIN_DEVICE_REDIRDEV:
    case VIR_DOMAIN_DEVICE_SMARTCARD:
    case VIR_DOMAIN_DEVICE_MEMBALLOON:
    case VIR_DOMAIN_DEVICE_NVRAM:
    case VIR_DOMAIN_DEVICE_SHMEM:
    case VIR_DOMAIN_DEVICE_TPM:
    case VIR_DOMAIN_DEVICE_PANIC:
    case VIR_DOMAIN_DEVICE_MEMORY:
    case VIR_DOMAIN_DEVICE_IOMMU:
        ret = 0;
        break;

    case VIR_DOMAIN_DEVICE_NONE:
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("unexpected VIR_DOMAIN_DEVICE_NONE"));
        break;

    case VIR_DOMAIN_DEVICE_LAST:
    default:
        virReportEnumRangeError(virDomainDeviceType, dev->type);
        break;
    }

    return ret;
}


virDomainVsockDefNew(virDomainXMLOptionPtr xmlopt)
{
    virDomainVsockDefPtr ret = NULL;
    virDomainVsockDefPtr vsock;

    if (VIR_ALLOC(vsock) < 0)
        return NULL;

    if (xmlopt &&
        xmlopt->privateData.vsockNew &&
        !(vsock->privateData = xmlopt->privateData.vsockNew()))
        goto cleanup;

    ret = g_steal_pointer(&vsock);
 cleanup:
    virDomainVsockDefFree(vsock);
    return ret;
}


virDomainObjRemoveTransientDef(virDomainObjPtr domain)
{
    if (!domain->newDef)
        return;

    virDomainDefFree(domain->def);
    domain->def = domain->newDef;
    domain->def->id = -1;
    domain->newDef = NULL;
}


virDomainNetDefCheckABIStability(virDomainNetDefPtr src,
                                 virDomainNetDefPtr dst)
{
    char srcmac[VIR_MAC_STRING_BUFLEN];
    char dstmac[VIR_MAC_STRING_BUFLEN];

    if (virMacAddrCmp(&src->mac, &dst->mac) != 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target network card mac %s"
                         " does not match source %s"),
                       virMacAddrFormat(&dst->mac, dstmac),
                       virMacAddrFormat(&src->mac, srcmac));
        return false;
    }

    if (STRNEQ_NULLABLE(src->modelstr, dst->modelstr)) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target network card model %s does not match source %s"),
                       NULLSTR(dst->modelstr), NULLSTR(src->modelstr));
        return false;
    }

    if (src->model != dst->model) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target network card model %s does not match source %s"),
                       virDomainNetModelTypeToString(dst->model),
                       virDomainNetModelTypeToString(src->model));
        return false;
    }

    if (src->mtu != dst->mtu) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("Target network card MTU %d does not match source %d"),
                       dst->mtu, src->mtu);
        return false;
    }

    if (src->virtio && dst->virtio &&
        !virDomainVirtioOptionsCheckABIStability(src->virtio, dst->virtio))
        return false;

    if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
        return false;

    return true;
}


virDomainNetFindIdx(virDomainDefPtr def, virDomainNetDefPtr net)
{
    size_t i;
    int matchidx = -1;
    char mac[VIR_MAC_STRING_BUFLEN];
    bool MACAddrSpecified = !net->mac_generated;
    bool PCIAddrSpecified = virDomainDeviceAddressIsValid(&net->info,
                                                          VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI);

    for (i = 0; i < def->nnets; i++) {
        if (MACAddrSpecified &&
            virMacAddrCmp(&def->nets[i]->mac, &net->mac) != 0)
            continue;

        if (PCIAddrSpecified &&
            !virPCIDeviceAddressEqual(&def->nets[i]->info.addr.pci,
                                      &net->info.addr.pci))
            continue;

        if (matchidx >= 0) {
            /* there were multiple matches on mac address, and no
             * qualifying guest-side PCI address was given, so we must
             * fail (NB: a USB address isn't adequate, since it may
             * specify only vendor and product ID, and there may be
             * multiples of those.
             */
            if (MACAddrSpecified) {
                virReportError(VIR_ERR_OPERATION_FAILED,
                               _("multiple devices matching MAC address %s found"),
                               virMacAddrFormat(&net->mac, mac));
            } else {
                virReportError(VIR_ERR_OPERATION_FAILED, "%s",
                               _("multiple matching devices found"));
            }

            return -1;
        }

        matchidx = i;
    }

    if (matchidx < 0) {
        if (MACAddrSpecified && PCIAddrSpecified) {
            virReportError(VIR_ERR_DEVICE_MISSING,
                           _("no device matching MAC address %s found on "
                             VIR_PCI_DEVICE_ADDRESS_FMT),
                           virMacAddrFormat(&net->mac, mac),
                           net->info.addr.pci.domain,
                           net->info.addr.pci.bus,
                           net->info.addr.pci.slot,
                           net->info.addr.pci.function);
        } else if (PCIAddrSpecified) {
            virReportError(VIR_ERR_DEVICE_MISSING,
                           _("no device found on " VIR_PCI_DEVICE_ADDRESS_FMT),
                           net->info.addr.pci.domain,
                           net->info.addr.pci.bus,
                           net->info.addr.pci.slot,
                           net->info.addr.pci.function);
        } else if (MACAddrSpecified) {
            virReportError(VIR_ERR_DEVICE_MISSING,
                           _("no device matching MAC address %s found"),
                           virMacAddrFormat(&net->mac, mac));
        } else {
            virReportError(VIR_ERR_DEVICE_MISSING, "%s",
                           _("no matching device found"));
        }
    }
    return matchidx;
}


virDomainDeviceDimmAddressParseXML(xmlNodePtr node,
                                   virDomainDeviceDimmAddressPtr addr)
{
    g_autofree char *tmp = NULL;

    if (!(tmp = virXMLPropString(node, "slot")) ||
        virStrToLong_uip(tmp, NULL, 10, &addr->slot) < 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid or missing dimm slot id '%s'"),
                       NULLSTR(tmp));
        return -1;
    }
    VIR_FREE(tmp);

    if ((tmp = virXMLPropString(node, "base"))) {
        if (virStrToLong_ullp(tmp, NULL, 16, &addr->base) < 0) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("invalid dimm base address '%s'"), tmp);
            return -1;
        }
    }

    return 0;
}


virDomainVideoAccelDefFormat(virBufferPtr buf,
                             virDomainVideoAccelDefPtr def)
{
    virBufferAddLit(buf, "<acceleration");
    if (def->accel3d) {
        virBufferAsprintf(buf, " accel3d='%s'",
                          virTristateBoolTypeToString(def->accel3d));
    }
    if (def->accel2d) {
        virBufferAsprintf(buf, " accel2d='%s'",
                          virTristateBoolTypeToString(def->accel2d));
    }
    virBufferEscapeString(buf, " rendernode='%s'", def->rendernode);
    virBufferAddLit(buf, "/>\n");
}


virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt,
                               virStorageSourcePtr src,
                               unsigned int flags,
                               virDomainXMLOptionPtr xmlopt)
{
    VIR_XPATH_NODE_AUTORESTORE(ctxt);
    xmlNodePtr source;
    g_autoptr(virStorageSource) backingStore = NULL;
    g_autofree char *type = NULL;
    g_autofree char *format = NULL;
    g_autofree char *idx = NULL;

    if (!(ctxt->node = virXPathNode("./backingStore", ctxt)))
        return 0;

    /* terminator does not have a type */
    if (!(type = virXMLPropString(ctxt->node, "type"))) {
        if (!(src->backingStore = virStorageSourceNew()))
            return -1;
        return 0;
    }

    if (!(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE))
        idx = virXMLPropString(ctxt->node, "index");

    if (!(format = virXPathString("string(./format/@type)", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing disk backing store format"));
        return -1;
    }

    if (!(source = virXPathNode("./source", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing disk backing store source"));
        return -1;
    }

    if (!(backingStore = virDomainStorageSourceParseBase(type, format, idx)))
        return -1;

    /* backing store is always read-only */
    backingStore->readonly = true;

    if (virDomainStorageSourceParse(source, ctxt, backingStore, flags, xmlopt) < 0 ||
        virDomainDiskBackingStoreParse(ctxt, backingStore, flags, xmlopt) < 0)
        return -1;

    src->backingStore = g_steal_pointer(&backingStore);

    return 0;
}


virDomainControllerFindByPCIAddress(virDomainDefPtr def,
                                    virPCIDeviceAddressPtr addr)
{
    size_t i;

    for (i = 0; i < def->ncontrollers; i++) {
        virDomainDeviceInfoPtr info = &def->controllers[i]->info;

        if (info->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI &&
            virPCIDeviceAddressEqual(&info->addr.pci, addr))
            return i;
    }

    return -1;
}


virSecurityDeviceLabelDefValidateXML(virSecurityDeviceLabelDefPtr *seclabels,
                                     size_t nseclabels,
                                     virSecurityLabelDefPtr *vmSeclabels,
                                     size_t nvmSeclabels)
{
    virSecurityDeviceLabelDefPtr seclabel;
    size_t i;
    size_t j;

    for (i = 0; i < nseclabels; i++) {
        seclabel = seclabels[i];

        /* find the security label that it's being overridden */
        for (j = 0; j < nvmSeclabels; j++) {
            if (STRNEQ_NULLABLE(vmSeclabels[j]->model, seclabel->model))
                continue;

            if (!vmSeclabels[j]->relabel) {
                virReportError(VIR_ERR_XML_ERROR, "%s",
                               _("label overrides require relabeling to be "
                                 "enabled at the domain level"));
                return -1;
            }
        }
    }

    return 0;
}


virDomainNetAllocateActualDevice(virConnectPtr conn,
                                 virDomainDefPtr dom,
                                 virDomainNetDefPtr iface)
{
    return virDomainNetCreatePort(conn, dom, iface, 0);
}


virDomainDiskSourcePoolDefParse(xmlNodePtr node,
                                virStorageSourcePoolDefPtr *srcpool)
{
    virStorageSourcePoolDefPtr source;
    int ret = -1;
    g_autofree char *mode = NULL;

    *srcpool = NULL;

    if (VIR_ALLOC(source) < 0)
        return -1;

    source->pool = virXMLPropString(node, "pool");
    source->volume = virXMLPropString(node, "volume");
    mode = virXMLPropString(node, "mode");

    /* CD-ROM and Floppy allows no source */
    if (!source->pool && !source->volume) {
        ret = 0;
        goto cleanup;
    }

    if (!source->pool || !source->volume) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("'pool' and 'volume' must be specified together "
                         "for 'pool' type source"));
        goto cleanup;
    }

    if (mode &&
        (source->mode = virStorageSourcePoolModeTypeFromString(mode)) <= 0) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                       _("unknown source mode '%s' for volume type disk"),
                       mode);
        goto cleanup;
    }

    *srcpool = g_steal_pointer(&source);
    ret = 0;

 cleanup:
    virStorageSourcePoolDefFree(source);
    return ret;
}


virDomainNetIPInfoParseXML(const char *source,
                           xmlXPathContextPtr ctxt,
                           virNetDevIPInfoPtr def)
{
    virNetDevIPRoutePtr route = NULL;
    int nnodes;
    int ret = -1;
    size_t i;
    g_autofree xmlNodePtr *nodes = NULL;
    g_autofree virNetDevIPAddrPtr ip = NULL;

    if ((nnodes = virXPathNodeSet("./ip", ctxt, &nodes)) < 0)
        goto cleanup;

    for (i = 0; i < nnodes; i++) {
        if (!(ip = virDomainNetIPParseXML(nodes[i])) ||
            VIR_APPEND_ELEMENT(def->ips, def->nips, ip) < 0)
            goto cleanup;
    }
    VIR_FREE(nodes);

    if ((nnodes = virXPathNodeSet("./route", ctxt, &nodes)) < 0)
        goto cleanup;

    for (i = 0; i < nnodes; i++) {
        if (!(route = virNetDevIPRouteParseXML(source, nodes[i], ctxt)) ||
            VIR_APPEND_ELEMENT(def->routes, def->nroutes, route) < 0)
            goto cleanup;
    }

    ret = 0;
 cleanup:
    if (ret < 0)
        virNetDevIPInfoClear(def);
    virNetDevIPRouteFree(route);
    return ret;
}


virDomainGraphicsGetRenderNode(const virDomainGraphicsDef *graphics)
{
    const char *ret = NULL;

    switch (graphics->type) {
    case VIR_DOMAIN_GRAPHICS_TYPE_SPICE:
        ret = graphics->data.spice.rendernode;
        break;
    case VIR_DOMAIN_GRAPHICS_TYPE_EGL_HEADLESS:
        ret = graphics->data.egl_headless.rendernode;
        break;
    case VIR_DOMAIN_GRAPHICS_TYPE_SDL:
    case VIR_DOMAIN_GRAPHICS_TYPE_VNC:
    case VIR_DOMAIN_GRAPHICS_TYPE_RDP:
    case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP:
    case VIR_DOMAIN_GRAPHICS_TYPE_LAST:
        break;
    }

    return ret;
}


virDomainHostdevSubsysUSBDefParseXML(xmlNodePtr node,
                                     virDomainHostdevDefPtr def)
{
    bool got_product, got_vendor;
    xmlNodePtr cur;
    virDomainHostdevSubsysUSBPtr usbsrc = &def->source.subsys.u.usb;
    g_autofree char *startupPolicy = NULL;
    g_autofree char *autoAddress = NULL;

    if ((startupPolicy = virXMLPropString(node, "startupPolicy"))) {
        def->startupPolicy =
            virDomainStartupPolicyTypeFromString(startupPolicy);
        if (def->startupPolicy <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Unknown startup policy '%s'"),
                           startupPolicy);
            return -1;
        }
    }

    if ((autoAddress = virXMLPropString(node, "autoAddress")))
        ignore_value(virStringParseYesNo(autoAddress, &usbsrc->autoAddress));

    /* Product can validly be 0, so we need some extra help to determine
     * if it is uninitialized */
    got_product = false;
    got_vendor = false;

    cur = node->children;
    while (cur != NULL) {
        if (cur->type == XML_ELEMENT_NODE) {
            if (virXMLNodeNameEqual(cur, "vendor")) {
                g_autofree char *vendor = virXMLPropString(cur, "id");

                if (vendor) {
                    got_vendor = true;
                    if (virStrToLong_ui(vendor, NULL, 0, &usbsrc->vendor) < 0) {
                        virReportError(VIR_ERR_INTERNAL_ERROR,
                                       _("cannot parse vendor id %s"), vendor);
                        return -1;
                    }
                } else {
                    virReportError(VIR_ERR_INTERNAL_ERROR,
                                   "%s", _("usb vendor needs id"));
                    return -1;
                }
            } else if (virXMLNodeNameEqual(cur, "product")) {
                g_autofree char *product = virXMLPropString(cur, "id");

                if (product) {
                    got_product = true;
                    if (virStrToLong_ui(product, NULL, 0,
                                        &usbsrc->product) < 0) {
                        virReportError(VIR_ERR_INTERNAL_ERROR,
                                       _("cannot parse product %s"),
                                       product);
                        return -1;
                    }
                } else {
                    virReportError(VIR_ERR_INTERNAL_ERROR,
                                   "%s", _("usb product needs id"));
                    return -1;
                }
            } else if (virXMLNodeNameEqual(cur, "address")) {
                g_autofree char *bus = NULL;
                g_autofree char *device = NULL;

                bus = virXMLPropString(cur, "bus");
                if (bus) {
                    if (virStrToLong_ui(bus, NULL, 0, &usbsrc->bus) < 0) {
                        virReportError(VIR_ERR_INTERNAL_ERROR,
                                       _("cannot parse bus %s"), bus);
                        return -1;
                    }
                } else {
                    virReportError(VIR_ERR_INTERNAL_ERROR,
                                   "%s", _("usb address needs bus id"));
                    return -1;
                }

                device = virXMLPropString(cur, "device");
                if (device) {
                    if (virStrToLong_ui(device, NULL, 0, &usbsrc->device) < 0) {
                        virReportError(VIR_ERR_INTERNAL_ERROR,
                                       _("cannot parse device %s"),
                                       device);
                        return -1;
                    }
                } else {
                    virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                                   _("usb address needs device id"));
                    return -1;
                }
            } else {
                virReportError(VIR_ERR_INTERNAL_ERROR,
                               _("unknown usb source type '%s'"),
                               cur->name);
                return -1;
            }
        }
        cur = cur->next;
    }

    if (got_vendor && usbsrc->vendor == 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("vendor cannot be 0."));
        return -1;
    }

    if (!got_vendor && got_product) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("missing vendor"));
        return -1;
    }
    if (got_vendor && !got_product) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       "%s", _("missing product"));
        return -1;
    }

    return 0;
}