6256b34ccc
* Add proplib-0.4.1 source and use it in XBPS. This is to avoid an external dependency, so that we depend on the features of the internal library. This also means that proplib is not required anymore. * Added support to read/write gzip compressed plists by default, thanks to proplib-0.4 that gained new functionality. That means that from now, XBPS will be able to write compressed gzip plist files for all metadata related work. This will vastly reduce bandwidth required for fetching remote repo's pkg index file and binary packages. --HG-- extra : convert_revision : xtraeme%40gmail.com-20100420122238-zcb85rudt9p34e10
1242 lines
28 KiB
C
1242 lines
28 KiB
C
/* $NetBSD: prop_object.c,v 1.23 2008/11/30 00:17:07 haad Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2006, 2007 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Jason R. Thorpe.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <prop/prop_object.h>
|
|
#include "prop_object_impl.h"
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
|
|
#include <zlib.h>
|
|
|
|
/*
|
|
* _prop_object_init --
|
|
* Initialize an object. Called when sub-classes create
|
|
* an instance.
|
|
*/
|
|
void
|
|
_prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
|
|
{
|
|
|
|
po->po_type = pot;
|
|
po->po_refcnt = 1;
|
|
}
|
|
|
|
/*
|
|
* _prop_object_fini --
|
|
* Finalize an object. Called when sub-classes destroy
|
|
* an instance.
|
|
*/
|
|
/*ARGSUSED*/
|
|
void
|
|
_prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
|
|
{
|
|
/* Nothing to do, currently. */
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_start_tag --
|
|
* Append an XML-style start tag to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_start_tag(
|
|
struct _prop_object_externalize_context *ctx, const char *tag)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->poec_depth; i++) {
|
|
if (_prop_object_externalize_append_char(ctx, '\t') == false)
|
|
return (false);
|
|
}
|
|
if (_prop_object_externalize_append_char(ctx, '<') == false ||
|
|
_prop_object_externalize_append_cstring(ctx, tag) == false ||
|
|
_prop_object_externalize_append_char(ctx, '>') == false)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_end_tag --
|
|
* Append an XML-style end tag to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_end_tag(
|
|
struct _prop_object_externalize_context *ctx, const char *tag)
|
|
{
|
|
|
|
if (_prop_object_externalize_append_char(ctx, '<') == false ||
|
|
_prop_object_externalize_append_char(ctx, '/') == false ||
|
|
_prop_object_externalize_append_cstring(ctx, tag) == false ||
|
|
_prop_object_externalize_append_char(ctx, '>') == false ||
|
|
_prop_object_externalize_append_char(ctx, '\n') == false)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_empty_tag --
|
|
* Append an XML-style empty tag to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_empty_tag(
|
|
struct _prop_object_externalize_context *ctx, const char *tag)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ctx->poec_depth; i++) {
|
|
if (_prop_object_externalize_append_char(ctx, '\t') == false)
|
|
return (false);
|
|
}
|
|
|
|
if (_prop_object_externalize_append_char(ctx, '<') == false ||
|
|
_prop_object_externalize_append_cstring(ctx, tag) == false ||
|
|
_prop_object_externalize_append_char(ctx, '/') == false ||
|
|
_prop_object_externalize_append_char(ctx, '>') == false ||
|
|
_prop_object_externalize_append_char(ctx, '\n') == false)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_append_cstring --
|
|
* Append a C string to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_append_cstring(
|
|
struct _prop_object_externalize_context *ctx, const char *cp)
|
|
{
|
|
|
|
while (*cp != '\0') {
|
|
if (_prop_object_externalize_append_char(ctx,
|
|
(unsigned char) *cp) == false)
|
|
return (false);
|
|
cp++;
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_append_encoded_cstring --
|
|
* Append an encoded C string to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_append_encoded_cstring(
|
|
struct _prop_object_externalize_context *ctx, const char *cp)
|
|
{
|
|
|
|
while (*cp != '\0') {
|
|
switch (*cp) {
|
|
case '<':
|
|
if (_prop_object_externalize_append_cstring(ctx,
|
|
"<") == false)
|
|
return (false);
|
|
break;
|
|
case '>':
|
|
if (_prop_object_externalize_append_cstring(ctx,
|
|
">") == false)
|
|
return (false);
|
|
break;
|
|
case '&':
|
|
if (_prop_object_externalize_append_cstring(ctx,
|
|
"&") == false)
|
|
return (false);
|
|
break;
|
|
default:
|
|
if (_prop_object_externalize_append_char(ctx,
|
|
(unsigned char) *cp) == false)
|
|
return (false);
|
|
break;
|
|
}
|
|
cp++;
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
#define BUF_EXPAND 256
|
|
|
|
/*
|
|
* _prop_object_externalize_append_char --
|
|
* Append a single character to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_append_char(
|
|
struct _prop_object_externalize_context *ctx, unsigned char c)
|
|
{
|
|
|
|
_PROP_ASSERT(ctx->poec_capacity != 0);
|
|
_PROP_ASSERT(ctx->poec_buf != NULL);
|
|
_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
|
|
|
|
if (ctx->poec_len == ctx->poec_capacity) {
|
|
char *cp = _PROP_REALLOC(ctx->poec_buf,
|
|
ctx->poec_capacity + BUF_EXPAND,
|
|
M_TEMP);
|
|
if (cp == NULL)
|
|
return (false);
|
|
ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
|
|
ctx->poec_buf = cp;
|
|
}
|
|
|
|
ctx->poec_buf[ctx->poec_len++] = c;
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_header --
|
|
* Append the standard XML header to the externalize buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
|
|
{
|
|
static const char _plist_xml_header[] =
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
|
|
|
|
if (_prop_object_externalize_append_cstring(ctx,
|
|
_plist_xml_header) == false ||
|
|
_prop_object_externalize_start_tag(ctx,
|
|
"plist version=\"1.0\"") == false ||
|
|
_prop_object_externalize_append_char(ctx, '\n') == false)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_footer --
|
|
* Append the standard XML footer to the externalize buffer. This
|
|
* also NUL-terminates the buffer.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
|
|
{
|
|
|
|
if (_prop_object_externalize_end_tag(ctx, "plist") == false ||
|
|
_prop_object_externalize_append_char(ctx, '\0') == false)
|
|
return (false);
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_context_alloc --
|
|
* Allocate an externalize context.
|
|
*/
|
|
struct _prop_object_externalize_context *
|
|
_prop_object_externalize_context_alloc(void)
|
|
{
|
|
struct _prop_object_externalize_context *ctx;
|
|
|
|
ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
|
|
if (ctx != NULL) {
|
|
ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
|
|
if (ctx->poec_buf == NULL) {
|
|
_PROP_FREE(ctx, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
ctx->poec_len = 0;
|
|
ctx->poec_capacity = BUF_EXPAND;
|
|
ctx->poec_depth = 0;
|
|
}
|
|
return (ctx);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_context_free --
|
|
* Free an externalize context.
|
|
*/
|
|
void
|
|
_prop_object_externalize_context_free(
|
|
struct _prop_object_externalize_context *ctx)
|
|
{
|
|
|
|
/* Buffer is always freed by the caller. */
|
|
_PROP_FREE(ctx, M_TEMP);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_skip_comment --
|
|
* Skip the body and end tag of a comment.
|
|
*/
|
|
static bool
|
|
_prop_object_internalize_skip_comment(
|
|
struct _prop_object_internalize_context *ctx)
|
|
{
|
|
const char *cp = ctx->poic_cp;
|
|
|
|
while (!_PROP_EOF(*cp)) {
|
|
if (cp[0] == '-' &&
|
|
cp[1] == '-' &&
|
|
cp[2] == '>') {
|
|
ctx->poic_cp = cp + 3;
|
|
return (true);
|
|
}
|
|
cp++;
|
|
}
|
|
|
|
return (false); /* ran out of buffer */
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_find_tag --
|
|
* Find the next tag in an XML stream. Optionally compare the found
|
|
* tag to an expected tag name. State of the context is undefined
|
|
* if this routine returns false. Upon success, the context points
|
|
* to the first octet after the tag.
|
|
*/
|
|
bool
|
|
_prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
|
|
const char *tag, _prop_tag_type_t type)
|
|
{
|
|
const char *cp;
|
|
size_t taglen;
|
|
|
|
if (tag != NULL)
|
|
taglen = strlen(tag);
|
|
else
|
|
taglen = 0;
|
|
|
|
start_over:
|
|
cp = ctx->poic_cp;
|
|
|
|
/*
|
|
* Find the start of the tag.
|
|
*/
|
|
while (_PROP_ISSPACE(*cp))
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
if (*cp != '<')
|
|
return (false);
|
|
|
|
ctx->poic_tag_start = cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
if (*cp == '!') {
|
|
if (cp[1] != '-' || cp[2] != '-')
|
|
return (false);
|
|
/*
|
|
* Comment block -- only allowed if we are allowed to
|
|
* return a start tag.
|
|
*/
|
|
if (type == _PROP_TAG_TYPE_END)
|
|
return (false);
|
|
ctx->poic_cp = cp + 3;
|
|
if (_prop_object_internalize_skip_comment(ctx) == false)
|
|
return (false);
|
|
goto start_over;
|
|
}
|
|
|
|
if (*cp == '/') {
|
|
if (type != _PROP_TAG_TYPE_END &&
|
|
type != _PROP_TAG_TYPE_EITHER)
|
|
return (false);
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
ctx->poic_tag_type = _PROP_TAG_TYPE_END;
|
|
} else {
|
|
if (type != _PROP_TAG_TYPE_START &&
|
|
type != _PROP_TAG_TYPE_EITHER)
|
|
return (false);
|
|
ctx->poic_tag_type = _PROP_TAG_TYPE_START;
|
|
}
|
|
|
|
ctx->poic_tagname = cp;
|
|
|
|
while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>')
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
ctx->poic_tagname_len = cp - ctx->poic_tagname;
|
|
|
|
/* Make sure this is the tag we're looking for. */
|
|
if (tag != NULL &&
|
|
(taglen != ctx->poic_tagname_len ||
|
|
memcmp(tag, ctx->poic_tagname, taglen) != 0))
|
|
return (false);
|
|
|
|
/* Check for empty tag. */
|
|
if (*cp == '/') {
|
|
if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
|
|
return(false); /* only valid on start tags */
|
|
ctx->poic_is_empty_element = true;
|
|
cp++;
|
|
if (_PROP_EOF(*cp) || *cp != '>')
|
|
return (false);
|
|
} else
|
|
ctx->poic_is_empty_element = false;
|
|
|
|
/* Easy case of no arguments. */
|
|
if (*cp == '>') {
|
|
ctx->poic_tagattr = NULL;
|
|
ctx->poic_tagattr_len = 0;
|
|
ctx->poic_tagattrval = NULL;
|
|
ctx->poic_tagattrval_len = 0;
|
|
ctx->poic_cp = cp + 1;
|
|
return (true);
|
|
}
|
|
|
|
_PROP_ASSERT(!_PROP_EOF(*cp));
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
while (_PROP_ISSPACE(*cp))
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
ctx->poic_tagattr = cp;
|
|
|
|
while (!_PROP_ISSPACE(*cp) && *cp != '=')
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
|
|
|
|
cp++;
|
|
if (*cp != '\"')
|
|
return (false);
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
|
|
ctx->poic_tagattrval = cp;
|
|
while (*cp != '\"')
|
|
cp++;
|
|
if (_PROP_EOF(*cp))
|
|
return (false);
|
|
ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
|
|
|
|
cp++;
|
|
if (*cp != '>')
|
|
return (false);
|
|
|
|
ctx->poic_cp = cp + 1;
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_decode_string --
|
|
* Decode an encoded string.
|
|
*/
|
|
bool
|
|
_prop_object_internalize_decode_string(
|
|
struct _prop_object_internalize_context *ctx,
|
|
char *target, size_t targsize, size_t *sizep,
|
|
const char **cpp)
|
|
{
|
|
const char *src;
|
|
size_t tarindex;
|
|
char c;
|
|
|
|
tarindex = 0;
|
|
src = ctx->poic_cp;
|
|
|
|
for (;;) {
|
|
if (_PROP_EOF(*src))
|
|
return (false);
|
|
if (*src == '<') {
|
|
break;
|
|
}
|
|
|
|
if ((c = *src) == '&') {
|
|
if (src[1] == 'a' &&
|
|
src[2] == 'm' &&
|
|
src[3] == 'p' &&
|
|
src[4] == ';') {
|
|
c = '&';
|
|
src += 5;
|
|
} else if (src[1] == 'l' &&
|
|
src[2] == 't' &&
|
|
src[3] == ';') {
|
|
c = '<';
|
|
src += 4;
|
|
} else if (src[1] == 'g' &&
|
|
src[2] == 't' &&
|
|
src[3] == ';') {
|
|
c = '>';
|
|
src += 4;
|
|
} else if (src[1] == 'a' &&
|
|
src[2] == 'p' &&
|
|
src[3] == 'o' &&
|
|
src[4] == 's' &&
|
|
src[5] == ';') {
|
|
c = '\'';
|
|
src += 6;
|
|
} else if (src[1] == 'q' &&
|
|
src[2] == 'u' &&
|
|
src[3] == 'o' &&
|
|
src[4] == 't' &&
|
|
src[5] == ';') {
|
|
c = '\"';
|
|
src += 6;
|
|
} else
|
|
return (false);
|
|
} else
|
|
src++;
|
|
if (target) {
|
|
if (tarindex >= targsize)
|
|
return (false);
|
|
target[tarindex] = c;
|
|
}
|
|
tarindex++;
|
|
}
|
|
|
|
_PROP_ASSERT(*src == '<');
|
|
if (sizep != NULL)
|
|
*sizep = tarindex;
|
|
if (cpp != NULL)
|
|
*cpp = src;
|
|
|
|
return (true);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_match --
|
|
* Returns true if the two character streams match.
|
|
*/
|
|
bool
|
|
_prop_object_internalize_match(const char *str1, size_t len1,
|
|
const char *str2, size_t len2)
|
|
{
|
|
|
|
return (len1 == len2 && memcmp(str1, str2, len1) == 0);
|
|
}
|
|
|
|
#define INTERNALIZER(t, f) \
|
|
{ t, sizeof(t) - 1, f }
|
|
|
|
static const struct _prop_object_internalizer {
|
|
const char *poi_tag;
|
|
size_t poi_taglen;
|
|
prop_object_internalizer_t poi_intern;
|
|
} _prop_object_internalizer_table[] = {
|
|
INTERNALIZER("array", _prop_array_internalize),
|
|
|
|
INTERNALIZER("true", _prop_bool_internalize),
|
|
INTERNALIZER("false", _prop_bool_internalize),
|
|
|
|
INTERNALIZER("data", _prop_data_internalize),
|
|
|
|
INTERNALIZER("dict", _prop_dictionary_internalize),
|
|
|
|
INTERNALIZER("integer", _prop_number_internalize),
|
|
|
|
INTERNALIZER("string", _prop_string_internalize),
|
|
|
|
{ 0, 0, NULL }
|
|
};
|
|
|
|
#undef INTERNALIZER
|
|
|
|
/*
|
|
* _prop_object_internalize_by_tag --
|
|
* Determine the object type from the tag in the context and
|
|
* internalize it.
|
|
*/
|
|
prop_object_t
|
|
_prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
|
|
{
|
|
const struct _prop_object_internalizer *poi;
|
|
prop_object_t obj, parent_obj;
|
|
void *data, *iter;
|
|
prop_object_internalizer_continue_t iter_func;
|
|
struct _prop_stack stack;
|
|
|
|
_prop_stack_init(&stack);
|
|
|
|
match_start:
|
|
for (poi = _prop_object_internalizer_table;
|
|
poi->poi_tag != NULL; poi++) {
|
|
if (_prop_object_internalize_match(ctx->poic_tagname,
|
|
ctx->poic_tagname_len,
|
|
poi->poi_tag,
|
|
poi->poi_taglen))
|
|
break;
|
|
}
|
|
if (poi == NULL) {
|
|
while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
|
|
iter_func = (prop_object_internalizer_continue_t)iter;
|
|
(*iter_func)(&stack, &obj, ctx, data, NULL);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
obj = NULL;
|
|
if (!(*poi->poi_intern)(&stack, &obj, ctx))
|
|
goto match_start;
|
|
|
|
parent_obj = obj;
|
|
while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
|
|
iter_func = (prop_object_internalizer_continue_t)iter;
|
|
if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj))
|
|
goto match_start;
|
|
obj = parent_obj;
|
|
}
|
|
|
|
return (parent_obj);
|
|
}
|
|
|
|
prop_object_t
|
|
_prop_generic_internalize(const char *xml, const char *master_tag)
|
|
{
|
|
prop_object_t obj = NULL;
|
|
struct _prop_object_internalize_context *ctx;
|
|
|
|
ctx = _prop_object_internalize_context_alloc(xml);
|
|
if (ctx == NULL)
|
|
return (NULL);
|
|
|
|
/* We start with a <plist> tag. */
|
|
if (_prop_object_internalize_find_tag(ctx, "plist",
|
|
_PROP_TAG_TYPE_START) == false)
|
|
goto out;
|
|
|
|
/* Plist elements cannot be empty. */
|
|
if (ctx->poic_is_empty_element)
|
|
goto out;
|
|
|
|
/*
|
|
* We don't understand any plist attributes, but Apple XML
|
|
* property lists often have a "version" attribute. If we
|
|
* see that one, we simply ignore it.
|
|
*/
|
|
if (ctx->poic_tagattr != NULL &&
|
|
!_PROP_TAGATTR_MATCH(ctx, "version"))
|
|
goto out;
|
|
|
|
/* Next we expect to see opening master_tag. */
|
|
if (_prop_object_internalize_find_tag(ctx, master_tag,
|
|
_PROP_TAG_TYPE_START) == false)
|
|
goto out;
|
|
|
|
obj = _prop_object_internalize_by_tag(ctx);
|
|
if (obj == NULL)
|
|
goto out;
|
|
|
|
/*
|
|
* We've advanced past the closing master_tag.
|
|
* Now we want </plist>.
|
|
*/
|
|
if (_prop_object_internalize_find_tag(ctx, "plist",
|
|
_PROP_TAG_TYPE_END) == false) {
|
|
prop_object_release(obj);
|
|
obj = NULL;
|
|
}
|
|
|
|
out:
|
|
_prop_object_internalize_context_free(ctx);
|
|
return (obj);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_context_alloc --
|
|
* Allocate an internalize context.
|
|
*/
|
|
struct _prop_object_internalize_context *
|
|
_prop_object_internalize_context_alloc(const char *xml)
|
|
{
|
|
struct _prop_object_internalize_context *ctx;
|
|
|
|
ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context),
|
|
M_TEMP);
|
|
if (ctx == NULL)
|
|
return (NULL);
|
|
|
|
ctx->poic_xml = ctx->poic_cp = xml;
|
|
|
|
/*
|
|
* Skip any whitespace and XML preamble stuff that we don't
|
|
* know about / care about.
|
|
*/
|
|
for (;;) {
|
|
while (_PROP_ISSPACE(*xml))
|
|
xml++;
|
|
if (_PROP_EOF(*xml) || *xml != '<')
|
|
goto bad;
|
|
|
|
#define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0)
|
|
|
|
/*
|
|
* Skip over the XML preamble that Apple XML property
|
|
* lists usually include at the top of the file.
|
|
*/
|
|
if (MATCH("?xml ") ||
|
|
MATCH("!DOCTYPE plist")) {
|
|
while (*xml != '>' && !_PROP_EOF(*xml))
|
|
xml++;
|
|
if (_PROP_EOF(*xml))
|
|
goto bad;
|
|
xml++; /* advance past the '>' */
|
|
continue;
|
|
}
|
|
|
|
if (MATCH("<!--")) {
|
|
ctx->poic_cp = xml + 4;
|
|
if (_prop_object_internalize_skip_comment(ctx) == false)
|
|
goto bad;
|
|
xml = ctx->poic_cp;
|
|
continue;
|
|
}
|
|
|
|
#undef MATCH
|
|
|
|
/*
|
|
* We don't think we should skip it, so let's hope we can
|
|
* parse it.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
ctx->poic_cp = xml;
|
|
return (ctx);
|
|
bad:
|
|
_PROP_FREE(ctx, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_context_free --
|
|
* Free an internalize context.
|
|
*/
|
|
void
|
|
_prop_object_internalize_context_free(
|
|
struct _prop_object_internalize_context *ctx)
|
|
{
|
|
|
|
_PROP_FREE(ctx, M_TEMP);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_file_dirname --
|
|
* dirname(3), basically. We have to roll our own because the
|
|
* system dirname(3) isn't reentrant.
|
|
*/
|
|
static void
|
|
_prop_object_externalize_file_dirname(const char *path, char *result)
|
|
{
|
|
const char *lastp;
|
|
size_t len;
|
|
|
|
/*
|
|
* If `path' is a NULL pointer or points to an empty string,
|
|
* return ".".
|
|
*/
|
|
if (path == NULL || *path == '\0')
|
|
goto singledot;
|
|
|
|
/* String trailing slashes, if any. */
|
|
lastp = path + strlen(path) - 1;
|
|
while (lastp != path && *lastp == '/')
|
|
lastp--;
|
|
|
|
/* Terminate path at the last occurrence of '/'. */
|
|
do {
|
|
if (*lastp == '/') {
|
|
/* Strip trailing slashes, if any. */
|
|
while (lastp != path && *lastp == '/')
|
|
lastp--;
|
|
|
|
/* ...and copy the result into the result buffer. */
|
|
len = (lastp - path) + 1 /* last char */;
|
|
if (len > (PATH_MAX - 1))
|
|
len = PATH_MAX - 1;
|
|
|
|
memcpy(result, path, len);
|
|
result[len] = '\0';
|
|
return;
|
|
}
|
|
} while (--lastp >= path);
|
|
|
|
/* No /'s found, return ".". */
|
|
singledot:
|
|
strcpy(result, ".");
|
|
}
|
|
|
|
/*
|
|
* _prop_object_externalize_write_file --
|
|
* Write an externalized dictionary to the specified file.
|
|
* The file is written atomically from the caller's perspective,
|
|
* and the mode set to 0666 modified by the caller's umask.
|
|
*
|
|
* The 'compress' argument enables gzip (via zlib) compression
|
|
* for the file to be written.
|
|
*/
|
|
bool
|
|
_prop_object_externalize_write_file(const char *fname, const char *xml,
|
|
size_t len, bool do_compress)
|
|
{
|
|
gzFile *gzf = NULL;
|
|
char tname[PATH_MAX], *otname;
|
|
int fd;
|
|
int save_errno;
|
|
mode_t myumask;
|
|
|
|
if (len > SSIZE_MAX) {
|
|
errno = EFBIG;
|
|
return (false);
|
|
}
|
|
|
|
/*
|
|
* Get the directory name where the file is to be written
|
|
* and create the temporary file.
|
|
*/
|
|
_prop_object_externalize_file_dirname(fname, tname);
|
|
|
|
#ifdef HAVE_STRLCAT
|
|
if (strlcat(tname, "/.plistXXXXXX", sizeof(tname)) >= sizeof(tname)) {
|
|
errno = ENAMETOOLONG;
|
|
return (false);
|
|
}
|
|
#else
|
|
otname = strncat(tname, "/.plistXXXXXX", sizeof(tname));
|
|
|
|
if (sizeof(*otname) >= sizeof(tname)) {
|
|
errno = ENAMETOOLONG;
|
|
return (false);
|
|
}
|
|
#endif
|
|
if ((fd = mkstemp(tname)) == -1)
|
|
return (false);
|
|
|
|
if (do_compress) {
|
|
if ((gzf = gzdopen(fd, "a")) == NULL)
|
|
goto bad;
|
|
|
|
if (gzsetparams(gzf, Z_BEST_COMPRESSION, Z_DEFAULT_STRATEGY))
|
|
goto bad;
|
|
|
|
if (gzwrite(gzf, xml, len) != (ssize_t)len)
|
|
goto bad;
|
|
} else {
|
|
if (write(fd, xml, len) != (ssize_t)len)
|
|
goto bad;
|
|
}
|
|
|
|
if (fsync(fd) == -1)
|
|
goto bad;
|
|
|
|
myumask = umask(0);
|
|
(void)umask(myumask);
|
|
if (fchmod(fd, 0666 & ~myumask) == -1)
|
|
goto bad;
|
|
|
|
if (do_compress)
|
|
(void)gzclose(gzf);
|
|
else
|
|
(void)close(fd);
|
|
fd = -1;
|
|
|
|
if (rename(tname, fname) == -1)
|
|
goto bad;
|
|
|
|
return (true);
|
|
|
|
bad:
|
|
save_errno = errno;
|
|
if (do_compress && gzf != NULL)
|
|
(void)gzclose(gzf);
|
|
else if (fd != -1)
|
|
(void)close(fd);
|
|
(void) unlink(tname);
|
|
errno = save_errno;
|
|
return (false);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_map_file --
|
|
* Map a file for the purpose of internalizing it.
|
|
*/
|
|
struct _prop_object_internalize_mapped_file *
|
|
_prop_object_internalize_map_file(const char *fname)
|
|
{
|
|
struct stat sb;
|
|
struct _prop_object_internalize_mapped_file *mf;
|
|
size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
|
|
size_t pgmask = pgsize - 1;
|
|
bool need_guard = false;
|
|
int fd;
|
|
|
|
mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
|
|
if (mf == NULL)
|
|
return (NULL);
|
|
|
|
fd = open(fname, O_RDONLY, 0400);
|
|
if (fd == -1) {
|
|
_PROP_FREE(mf, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
|
|
if (fstat(fd, &sb) == -1) {
|
|
(void) close(fd);
|
|
_PROP_FREE(mf, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
|
|
if (mf->poimf_mapsize < sb.st_size) {
|
|
(void) close(fd);
|
|
_PROP_FREE(mf, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* If the file length is an integral number of pages, then we
|
|
* need to map a guard page at the end in order to provide the
|
|
* necessary NUL-termination of the buffer.
|
|
*/
|
|
if ((sb.st_size & pgmask) == 0)
|
|
need_guard = true;
|
|
|
|
mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
|
|
: mf->poimf_mapsize,
|
|
PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
|
|
(void) close(fd);
|
|
if (mf->poimf_xml == MAP_FAILED) {
|
|
_PROP_FREE(mf, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);
|
|
|
|
if (need_guard) {
|
|
if (mmap(mf->poimf_xml + mf->poimf_mapsize,
|
|
pgsize, PROT_READ,
|
|
MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
|
|
(off_t)0) == MAP_FAILED) {
|
|
(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
|
|
_PROP_FREE(mf, M_TEMP);
|
|
return (NULL);
|
|
}
|
|
mf->poimf_mapsize += pgsize;
|
|
}
|
|
|
|
return (mf);
|
|
}
|
|
|
|
/*
|
|
* _prop_object_internalize_unmap_file --
|
|
* Unmap a file previously mapped for internalizing.
|
|
*/
|
|
void
|
|
_prop_object_internalize_unmap_file(
|
|
struct _prop_object_internalize_mapped_file *mf)
|
|
{
|
|
|
|
(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
|
|
(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
|
|
_PROP_FREE(mf, M_TEMP);
|
|
}
|
|
|
|
/*
|
|
* Retain / release serialization --
|
|
*
|
|
* Eventually we would like to use atomic operations. But until we have
|
|
* an MI API for them that is common to userland and the kernel, we will
|
|
* use a lock instead.
|
|
*
|
|
* We use a single global mutex for all serialization. In the kernel, because
|
|
* we are still under a biglock, this will basically never contend (properties
|
|
* cannot be manipulated at interrupt level). In userland, this will cost
|
|
* nothing for single-threaded programs. For multi-threaded programs, there
|
|
* could be contention, but it probably won't cost that much unless the program
|
|
* makes heavy use of property lists.
|
|
*/
|
|
_PROP_MUTEX_DECL_STATIC(_prop_refcnt_mutex)
|
|
#define _PROP_REFCNT_LOCK() _PROP_MUTEX_LOCK(_prop_refcnt_mutex)
|
|
#define _PROP_REFCNT_UNLOCK() _PROP_MUTEX_UNLOCK(_prop_refcnt_mutex)
|
|
|
|
/*
|
|
* prop_object_retain --
|
|
* Increment the reference count on an object.
|
|
*/
|
|
void
|
|
prop_object_retain(prop_object_t obj)
|
|
{
|
|
struct _prop_object *po = obj;
|
|
uint32_t ocnt;
|
|
|
|
_PROP_REFCNT_LOCK();
|
|
ocnt = po->po_refcnt++;
|
|
_PROP_REFCNT_UNLOCK();
|
|
|
|
_PROP_ASSERT(ocnt != 0xffffffffU);
|
|
}
|
|
|
|
/*
|
|
* prop_object_release_emergency
|
|
* A direct free with prop_object_release failed.
|
|
* Walk down the tree until a leaf is found and
|
|
* free that. Do not recurse to avoid stack overflows.
|
|
*
|
|
* This is a slow edge condition, but necessary to
|
|
* guarantee that an object can always be freed.
|
|
*/
|
|
static void
|
|
prop_object_release_emergency(prop_object_t obj)
|
|
{
|
|
struct _prop_object *po;
|
|
void (*unlock)(void);
|
|
prop_object_t parent = NULL;
|
|
uint32_t ocnt;
|
|
|
|
for (;;) {
|
|
po = obj;
|
|
_PROP_ASSERT(obj);
|
|
|
|
if (po->po_type->pot_lock != NULL)
|
|
po->po_type->pot_lock();
|
|
|
|
/* Save pointerto unlock function */
|
|
unlock = po->po_type->pot_unlock;
|
|
|
|
_PROP_REFCNT_LOCK();
|
|
ocnt = po->po_refcnt--;
|
|
_PROP_REFCNT_UNLOCK();
|
|
|
|
_PROP_ASSERT(ocnt != 0);
|
|
if (ocnt != 1) {
|
|
if (unlock != NULL)
|
|
unlock();
|
|
break;
|
|
}
|
|
|
|
_PROP_ASSERT(po->po_type);
|
|
if ((po->po_type->pot_free)(NULL, &obj) ==
|
|
_PROP_OBJECT_FREE_DONE) {
|
|
if (unlock != NULL)
|
|
unlock();
|
|
break;
|
|
}
|
|
|
|
if (unlock != NULL)
|
|
unlock();
|
|
|
|
parent = po;
|
|
_PROP_REFCNT_LOCK();
|
|
++po->po_refcnt;
|
|
_PROP_REFCNT_UNLOCK();
|
|
}
|
|
_PROP_ASSERT(parent);
|
|
/* One object was just freed. */
|
|
po = parent;
|
|
(*po->po_type->pot_emergency_free)(parent);
|
|
}
|
|
|
|
/*
|
|
* prop_object_release --
|
|
* Decrement the reference count on an object.
|
|
*
|
|
* Free the object if we are releasing the final
|
|
* reference.
|
|
*/
|
|
void
|
|
prop_object_release(prop_object_t obj)
|
|
{
|
|
struct _prop_object *po;
|
|
struct _prop_stack stack;
|
|
void (*unlock)(void);
|
|
int ret;
|
|
uint32_t ocnt;
|
|
|
|
_prop_stack_init(&stack);
|
|
|
|
do {
|
|
do {
|
|
po = obj;
|
|
_PROP_ASSERT(obj);
|
|
|
|
if (po->po_type->pot_lock != NULL)
|
|
po->po_type->pot_lock();
|
|
|
|
/* Save pointer to object unlock function */
|
|
unlock = po->po_type->pot_unlock;
|
|
|
|
_PROP_REFCNT_LOCK();
|
|
ocnt = po->po_refcnt--;
|
|
_PROP_REFCNT_UNLOCK();
|
|
|
|
_PROP_ASSERT(ocnt != 0);
|
|
if (ocnt != 1) {
|
|
ret = 0;
|
|
if (unlock != NULL)
|
|
unlock();
|
|
break;
|
|
}
|
|
|
|
ret = (po->po_type->pot_free)(&stack, &obj);
|
|
|
|
if (unlock != NULL)
|
|
unlock();
|
|
|
|
if (ret == _PROP_OBJECT_FREE_DONE)
|
|
break;
|
|
|
|
_PROP_REFCNT_LOCK();
|
|
++po->po_refcnt;
|
|
_PROP_REFCNT_UNLOCK();
|
|
} while (ret == _PROP_OBJECT_FREE_RECURSE);
|
|
if (ret == _PROP_OBJECT_FREE_FAILED)
|
|
prop_object_release_emergency(obj);
|
|
} while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
|
|
}
|
|
|
|
/*
|
|
* prop_object_type --
|
|
* Return the type of an object.
|
|
*/
|
|
prop_type_t
|
|
prop_object_type(prop_object_t obj)
|
|
{
|
|
struct _prop_object *po = obj;
|
|
|
|
if (obj == NULL)
|
|
return (PROP_TYPE_UNKNOWN);
|
|
|
|
return (po->po_type->pot_type);
|
|
}
|
|
|
|
/*
|
|
* prop_object_equals --
|
|
* Returns true if thw two objects are equivalent.
|
|
*/
|
|
bool
|
|
prop_object_equals(prop_object_t obj1, prop_object_t obj2)
|
|
{
|
|
return (prop_object_equals_with_error(obj1, obj2, NULL));
|
|
}
|
|
|
|
bool
|
|
prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
|
|
bool *error_flag)
|
|
{
|
|
struct _prop_object *po1;
|
|
struct _prop_object *po2;
|
|
void *stored_pointer1, *stored_pointer2;
|
|
prop_object_t next_obj1, next_obj2;
|
|
struct _prop_stack stack;
|
|
_prop_object_equals_rv_t ret;
|
|
|
|
_prop_stack_init(&stack);
|
|
if (error_flag)
|
|
*error_flag = false;
|
|
|
|
start_subtree:
|
|
stored_pointer1 = NULL;
|
|
stored_pointer2 = NULL;
|
|
po1 = obj1;
|
|
po2 = obj2;
|
|
|
|
if (po1->po_type != po2->po_type)
|
|
return (false);
|
|
|
|
continue_subtree:
|
|
ret = (*po1->po_type->pot_equals)(obj1, obj2,
|
|
&stored_pointer1, &stored_pointer2,
|
|
&next_obj1, &next_obj2);
|
|
if (ret == _PROP_OBJECT_EQUALS_FALSE)
|
|
goto finish;
|
|
if (ret == _PROP_OBJECT_EQUALS_TRUE) {
|
|
if (!_prop_stack_pop(&stack, &obj1, &obj2,
|
|
&stored_pointer1, &stored_pointer2))
|
|
return true;
|
|
goto continue_subtree;
|
|
}
|
|
_PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
|
|
|
|
if (!_prop_stack_push(&stack, obj1, obj2,
|
|
stored_pointer1, stored_pointer2)) {
|
|
if (error_flag)
|
|
*error_flag = true;
|
|
goto finish;
|
|
}
|
|
obj1 = next_obj1;
|
|
obj2 = next_obj2;
|
|
goto start_subtree;
|
|
|
|
finish:
|
|
while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
|
|
po1 = obj1;
|
|
(*po1->po_type->pot_equals_finish)(obj1, obj2);
|
|
}
|
|
return (false);
|
|
}
|
|
|
|
/*
|
|
* prop_object_iterator_next --
|
|
* Return the next item during an iteration.
|
|
*/
|
|
prop_object_t
|
|
prop_object_iterator_next(prop_object_iterator_t pi)
|
|
{
|
|
|
|
return ((*pi->pi_next_object)(pi));
|
|
}
|
|
|
|
/*
|
|
* prop_object_iterator_reset --
|
|
* Reset the iterator to the first object so as to restart
|
|
* iteration.
|
|
*/
|
|
void
|
|
prop_object_iterator_reset(prop_object_iterator_t pi)
|
|
{
|
|
|
|
(*pi->pi_reset)(pi);
|
|
}
|
|
|
|
/*
|
|
* prop_object_iterator_release --
|
|
* Release the object iterator.
|
|
*/
|
|
void
|
|
prop_object_iterator_release(prop_object_iterator_t pi)
|
|
{
|
|
|
|
prop_object_release(pi->pi_obj);
|
|
_PROP_FREE(pi, M_TEMP);
|
|
}
|