Home | History | Annotate | Line # | Download | only in libprop
      1 /*	$NetBSD: prop_extern.c,v 1.2 2025/05/14 03:25:46 thorpej Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2006, 2007, 2025 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Jason R. Thorpe.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include "prop_object_impl.h"
     33 #include <prop/prop_object.h>
     34 
     35 #if !defined(_KERNEL) && !defined(_STANDALONE)
     36 #include <sys/stat.h>
     37 #include <errno.h>
     38 #include <fcntl.h>
     39 #include <limits.h>
     40 #include <unistd.h>
     41 #endif /* !_KERNEL && !_STANDALONE */
     42 
     43 #define	BUF_EXPAND	256
     44 #define	PLISTTMP	"/.plistXXXXXX"
     45 
     46 static prop_format_t	_prop_format_default = PROP_FORMAT_XML;
     47 
     48 /*
     49  * _prop_extern_append_char --
     50  *	Append a single character to the externalize buffer.
     51  */
     52 bool
     53 _prop_extern_append_char(
     54     struct _prop_object_externalize_context *ctx, unsigned char c)
     55 {
     56 
     57 	_PROP_ASSERT(ctx->poec_capacity != 0);
     58 	_PROP_ASSERT(ctx->poec_buf != NULL);
     59 	_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
     60 
     61 	if (ctx->poec_len == ctx->poec_capacity) {
     62 		char *cp = _PROP_REALLOC(ctx->poec_buf,
     63 					 ctx->poec_capacity + BUF_EXPAND,
     64 					 M_TEMP);
     65 		if (cp == NULL) {
     66 			return false;
     67 		}
     68 		ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
     69 		ctx->poec_buf = cp;
     70 	}
     71 
     72 	ctx->poec_buf[ctx->poec_len++] = c;
     73 
     74 	return true;
     75 }
     76 
     77 /*
     78  * _prop_extern_append_cstring --
     79  *	Append a C string to the externalize buffer.
     80  */
     81 bool
     82 _prop_extern_append_cstring(
     83     struct _prop_object_externalize_context *ctx, const char *cp)
     84 {
     85 
     86 	while (*cp != '\0') {
     87 		if (_prop_extern_append_char(ctx,
     88 					     (unsigned char)*cp) == false) {
     89 			return false;
     90 		}
     91 		cp++;
     92 	}
     93 	return true;
     94 }
     95 
     96 /*
     97  * _prop_json_extern_append_encoded_cstring --
     98  *	Append a C string to the externalize buffer, JSON-encoded.
     99  */
    100 static bool
    101 _prop_json_extern_append_escu(
    102     struct _prop_object_externalize_context *ctx, uint16_t val)
    103 {
    104 	char tmpstr[sizeof("\\uXXXX")];
    105 
    106 	snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val);
    107 	return _prop_extern_append_cstring(ctx, tmpstr);
    108 }
    109 
    110 static bool
    111 _prop_json_extern_append_encoded_cstring(
    112     struct _prop_object_externalize_context *ctx, const char *cp)
    113 {
    114 	bool esc;
    115 	unsigned char ch;
    116 
    117 	for (; (ch = *cp) != '\0'; cp++) {
    118 		esc = true;
    119 		switch (ch) {
    120 		/*
    121 		 * First, the two explicit exclusions.  They must be
    122 		 * escaped.
    123 		 */
    124 		case '"':	/* U+0022 quotation mark */
    125 			goto emit;
    126 
    127 		case '\\':	/* U+005C reverse solidus */
    128 			goto emit;
    129 
    130 		/*
    131 		 * And some special cases that are explcit in the grammar.
    132 		 */
    133 		case '/':	/* U+002F solidus (XXX this one seems silly) */
    134 			goto emit;
    135 
    136 		case 0x08:	/* U+0008 backspace */
    137 			ch = 'b';
    138 			goto emit;
    139 
    140 		case 0x0c:	/* U+000C form feed */
    141 			ch = 'f';
    142 			goto emit;
    143 
    144 		case 0x0a:	/* U+000A line feed */
    145 			ch = 'n';
    146 			goto emit;
    147 
    148 		case 0x0d:	/* U+000D carriage return */
    149 			ch = 'r';
    150 			goto emit;
    151 
    152 		case 0x09:	/* U+0009 tab */
    153 			ch = 't';
    154 			goto emit;
    155 
    156 		default:
    157 			/*
    158 			 * \u-escape all other single-byte ASCII control
    159 			 * characters, per RFC 8259:
    160 			 *
    161 			 * <quote>
    162 			 * All Unicode characters may be placed within the
    163 			 * quotation marks, except for the characters that
    164 			 * MUST be escaped: quotation mark, reverse solidus,
    165 			 * and the control characters (U+0000 through U+001F).
    166 			 * </quote>
    167 			 */
    168 			if (ch < 0x20) {
    169 				if (_prop_json_extern_append_escu(ctx,
    170 							ch) == false) {
    171 					return false;
    172 				}
    173 				break;
    174 			}
    175 			/*
    176 			 * We're going to just treat everything else like
    177 			 * UTF-8 (we've been handed a C-string, after all)
    178 			 * and pretend it will be OK.
    179 			 */
    180 			esc = false;
    181 		emit:
    182 			if ((esc && _prop_extern_append_char(ctx,
    183 							'\\') == false) ||
    184 			    _prop_extern_append_char(ctx, ch) == false) {
    185 				return false;
    186 			}
    187 			break;
    188 		}
    189 	}
    190 
    191 	return true;
    192 }
    193 
    194 /*
    195  * _prop_xml_extern_append_encoded_cstring --
    196  *	Append a C string to the externalize buffer, XML-encoded.
    197  */
    198 static bool
    199 _prop_xml_extern_append_encoded_cstring(
    200     struct _prop_object_externalize_context *ctx, const char *cp)
    201 {
    202 	bool rv;
    203 	unsigned char ch;
    204 
    205 	for (rv = true; rv && (ch = *cp) != '\0'; cp++) {
    206 		switch (ch) {
    207 		case '<':
    208 			rv = _prop_extern_append_cstring(ctx, "&lt;");
    209 			break;
    210 		case '>':
    211 			rv = _prop_extern_append_cstring(ctx, "&gt;");
    212 			break;
    213 		case '&':
    214 			rv = _prop_extern_append_cstring(ctx, "&amp;");
    215 			break;
    216 		default:
    217 			rv = _prop_extern_append_char(ctx, ch);
    218 			break;
    219 		}
    220 	}
    221 
    222 	return rv;
    223 }
    224 
    225 /*
    226  * _prop_extern_append_encoded_cstring --
    227  *	Append a C string to the externalize buffer, encoding it for
    228  *	the selected format.
    229  */
    230 bool
    231 _prop_extern_append_encoded_cstring(
    232     struct _prop_object_externalize_context *ctx, const char *cp)
    233 {
    234 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    235 		     ctx->poec_format == PROP_FORMAT_JSON);
    236 
    237 	switch (ctx->poec_format) {
    238 	case PROP_FORMAT_JSON:
    239 		return _prop_json_extern_append_encoded_cstring(ctx, cp);
    240 
    241 	default:		/* PROP_FORMAT_XML */
    242 		return _prop_xml_extern_append_encoded_cstring(ctx, cp);
    243 	}
    244 }
    245 
    246 /*
    247  * _prop_extern_start_line --
    248  *	Append the start-of-line character sequence.
    249  */
    250 bool
    251 _prop_extern_start_line(
    252     struct _prop_object_externalize_context *ctx)
    253 {
    254 	unsigned int i;
    255 
    256 	for (i = 0; i < ctx->poec_depth; i++) {
    257 		if (_prop_extern_append_char(ctx, '\t') == false) {
    258 			return false;
    259 		}
    260 	}
    261 	return true;
    262 }
    263 
    264 /*
    265  * _prop_extern_end_line --
    266  *	Append the end-of-line character sequence.
    267  */
    268 bool
    269 _prop_extern_end_line(
    270     struct _prop_object_externalize_context *ctx, const char *trailer)
    271 {
    272 	if (trailer != NULL &&
    273 	    _prop_extern_append_cstring(ctx, trailer) == false) {
    274 		return false;
    275 	}
    276 	return _prop_extern_append_char(ctx, '\n');
    277 }
    278 
    279 /*
    280  * _prop_extern_append_start_tag --
    281  *	Append an item's start tag to the externalize buffer.
    282  */
    283 bool
    284 _prop_extern_append_start_tag(
    285     struct _prop_object_externalize_context *ctx,
    286     const struct _prop_object_type_tags *tags,
    287     const char *tagattrs)
    288 {
    289 	bool rv;
    290 
    291 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    292 		     ctx->poec_format == PROP_FORMAT_JSON);
    293 
    294 	switch (ctx->poec_format) {
    295 	case PROP_FORMAT_JSON:
    296 		rv = tags->json_open_tag == NULL ||
    297 		     _prop_extern_append_cstring(ctx, tags->json_open_tag);
    298 		break;
    299 
    300 	default:		/* PROP_FORMAT_XML */
    301 		rv = _prop_extern_append_char(ctx, '<') &&
    302 		     _prop_extern_append_cstring(ctx, tags->xml_tag) &&
    303 		     (tagattrs == NULL ||
    304 		      (_prop_extern_append_char(ctx, ' ') &&
    305 		       _prop_extern_append_cstring(ctx, tagattrs))) &&
    306 		      _prop_extern_append_char(ctx, '>');
    307 		break;
    308 	}
    309 
    310 	return rv;
    311 }
    312 
    313 /*
    314  * _prop_extern_append_end_tag --
    315  *	Append an item's end tag to the externalize buffer.
    316  */
    317 bool
    318 _prop_extern_append_end_tag(
    319     struct _prop_object_externalize_context *ctx,
    320     const struct _prop_object_type_tags *tags)
    321 {
    322 	bool rv;
    323 
    324 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    325 		     ctx->poec_format == PROP_FORMAT_JSON);
    326 
    327 	switch (ctx->poec_format) {
    328 	case PROP_FORMAT_JSON:
    329 		rv = tags->json_close_tag == NULL ||
    330 		     _prop_extern_append_cstring(ctx, tags->json_close_tag);
    331 		break;
    332 
    333 	default:		/* PROP_FORMAT_XML */
    334 		rv = _prop_extern_append_char(ctx, '<') &&
    335 		     _prop_extern_append_char(ctx, '/') &&
    336 		     _prop_extern_append_cstring(ctx, tags->xml_tag) &&
    337 		     _prop_extern_append_char(ctx, '>');
    338 		break;
    339 	}
    340 
    341 	return rv;
    342 }
    343 
    344 /*
    345  * _prop_extern_append_empty_tag --
    346  *	Append an item's empty tag to the externalize buffer.
    347  */
    348 bool
    349 _prop_extern_append_empty_tag(
    350     struct _prop_object_externalize_context *ctx,
    351     const struct _prop_object_type_tags *tags)
    352 {
    353 	bool rv;
    354 
    355 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    356 		     ctx->poec_format == PROP_FORMAT_JSON);
    357 
    358 	switch (ctx->poec_format) {
    359 	case PROP_FORMAT_JSON:
    360 		if (tags->json_open_tag == NULL ||
    361 		    _prop_extern_append_cstring(ctx,
    362 					tags->json_open_tag) == false) {
    363 			return false;
    364 		}
    365 		if (tags->json_empty_sep != NULL &&
    366 		    _prop_extern_append_cstring(ctx,
    367 					tags->json_empty_sep) == false) {
    368 			return false;
    369 		}
    370 		if (tags->json_close_tag != NULL) {
    371 			rv = _prop_extern_append_cstring(ctx,
    372 					tags->json_close_tag);
    373 		} else {
    374 			rv = true;
    375 		}
    376 		break;
    377 
    378 	default:		/* PROP_FORMAT_XML */
    379 		rv = _prop_extern_append_char(ctx, '<') &&
    380 		     _prop_extern_append_cstring(ctx, tags->xml_tag) &&
    381 		     _prop_extern_append_char(ctx, '/') &&
    382 		     _prop_extern_append_char(ctx, '>');
    383 		break;
    384 	}
    385 
    386 	return rv;
    387 }
    388 
    389 static const struct _prop_object_type_tags _plist_type_tags = {
    390 	.xml_tag	=	"plist",
    391 };
    392 
    393 /*
    394  * _prop_extern_append_header --
    395  *	Append the header to the externalize buffer.
    396  */
    397 static bool
    398 _prop_extern_append_header(struct _prop_object_externalize_context *ctx)
    399 {
    400 	static const char _plist_xml_header[] =
    401 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    402 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
    403 
    404 	if (ctx->poec_format != PROP_FORMAT_XML) {
    405 		return true;
    406 	}
    407 
    408 	if (_prop_extern_append_cstring(ctx, _plist_xml_header) == false ||
    409 	    _prop_extern_append_start_tag(ctx,
    410 					&_plist_type_tags,
    411 					"version=\"1.0\"") == false ||
    412 	    _prop_extern_append_char(ctx, '\n') == false) {
    413 		return false;
    414 	}
    415 
    416 	return true;
    417 }
    418 
    419 /*
    420  * _prop_extern_append_footer --
    421  *	Append the footer to the externalize buffer.  This also
    422  *	NUL-terminates the buffer.
    423  */
    424 static bool
    425 _prop_extern_append_footer(struct _prop_object_externalize_context *ctx)
    426 {
    427 	if (_prop_extern_end_line(ctx, NULL) == false) {
    428 		return false;
    429 	}
    430 
    431 	if (ctx->poec_format == PROP_FORMAT_XML) {
    432 		if (_prop_extern_append_end_tag(ctx,
    433 					&_plist_type_tags) == false ||
    434 		    _prop_extern_end_line(ctx, NULL) == false) {
    435 			return false;
    436 		}
    437 	}
    438 
    439 	return _prop_extern_append_char(ctx, '\0');
    440 }
    441 
    442 /*
    443  * _prop_extern_context_alloc --
    444  *	Allocate an externalize context.
    445  */
    446 static struct _prop_object_externalize_context *
    447 _prop_extern_context_alloc(prop_format_t fmt)
    448 {
    449 	struct _prop_object_externalize_context *ctx;
    450 
    451 	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
    452 	if (ctx != NULL) {
    453 		ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
    454 		if (ctx->poec_buf == NULL) {
    455 			_PROP_FREE(ctx, M_TEMP);
    456 			return NULL;
    457 		}
    458 		ctx->poec_len = 0;
    459 		ctx->poec_capacity = BUF_EXPAND;
    460 		ctx->poec_depth = 0;
    461 		ctx->poec_format = fmt;
    462 	}
    463 	return ctx;
    464 }
    465 
    466 /*
    467  * _prop_extern_context_free --
    468  *	Free an externalize context.
    469  */
    470 static void
    471 _prop_extern_context_free(struct _prop_object_externalize_context *ctx)
    472 {
    473 	/* Buffer is always freed by the caller. */
    474 	_PROP_FREE(ctx, M_TEMP);
    475 }
    476 
    477 /*
    478  * _prop_object_externalize --
    479  *	Externalize an object, returning a NUL-terminated buffer
    480  *	containing the serialized data in either XML or JSON format.
    481  *	The buffer is allocated with the M_TEMP memory type.
    482  */
    483 char *
    484 _prop_object_externalize(struct _prop_object *obj, prop_format_t fmt)
    485 {
    486 	struct _prop_object_externalize_context *ctx;
    487 	char *cp = NULL;
    488 
    489 	if (obj == NULL || obj->po_type->pot_extern == NULL) {
    490 		return NULL;
    491 	}
    492 	if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) {
    493 		return NULL;
    494 	}
    495 
    496 	ctx = _prop_extern_context_alloc(fmt);
    497 	if (ctx == NULL) {
    498 		return NULL;
    499 	}
    500 
    501 	if (_prop_extern_append_header(ctx) == false ||
    502 	    obj->po_type->pot_extern(ctx, obj) == false ||
    503 	    _prop_extern_append_footer(ctx) == false) {
    504 		/* We are responsible for releasing the buffer. */
    505 		_PROP_FREE(ctx->poec_buf, M_TEMP);
    506 		goto bad;
    507 	}
    508 
    509 	cp = ctx->poec_buf;
    510  bad:
    511 	_prop_extern_context_free(ctx);
    512 	return cp;
    513 }
    514 
    515 #if !defined(_KERNEL) && !defined(_STANDALONE)
    516 /*
    517  * _prop_extern_file_dirname --
    518  *	dirname(3), basically.  We have to roll our own because the
    519  *	system dirname(3) isn't reentrant.
    520  */
    521 static void
    522 _prop_extern_file_dirname(const char *path, char *result)
    523 {
    524 	const char *lastp;
    525 	size_t len;
    526 
    527 	/*
    528 	 * If `path' is a NULL pointer or points to an empty string,
    529 	 * return ".".
    530 	 */
    531 	if (path == NULL || *path == '\0') {
    532 		goto singledot;
    533 	}
    534 
    535 	/* Strip trailing slashes, if any. */
    536 	lastp = path + strlen(path) - 1;
    537 	while (lastp != path && *lastp == '/') {
    538 		lastp--;
    539 	}
    540 
    541 	/* Terminate path at the last occurrence of '/'. */
    542 	do {
    543 		if (*lastp == '/') {
    544 			/* Strip trailing slashes, if any. */
    545 			while (lastp != path && *lastp == '/') {
    546 				lastp--;
    547 			}
    548 
    549 			/* ...and copy the result into the result buffer. */
    550 			len = (lastp - path) + 1 /* last char */;
    551 			if (len > (PATH_MAX - 1)) {
    552 				len = PATH_MAX - 1;
    553 			}
    554 
    555 			memcpy(result, path, len);
    556 			result[len] = '\0';
    557 			return;
    558 		}
    559 	} while (--lastp >= path);
    560 
    561 	/* No /'s found, return ".". */
    562  singledot:
    563 	strcpy(result, ".");
    564 }
    565 
    566 /*
    567  * _prop_extern_write_file --
    568  *	Write an externalized object to the specified file.
    569  *	The file is written atomically from the caller's perspective,
    570  *	and the mode set to 0666 modified by the caller's umask.
    571  */
    572 static bool
    573 _prop_extern_write_file(const char *fname, const char *data, size_t len)
    574 {
    575 	char tname_store[PATH_MAX];
    576 	char *tname = NULL;
    577 	int fd = -1;
    578 	int save_errno;
    579 	mode_t myumask;
    580 	bool rv = false;
    581 
    582 	if (len > SSIZE_MAX) {
    583 		errno = EFBIG;
    584 		return false;
    585 	}
    586 
    587 	/*
    588 	 * Get the directory name where the file is to be written
    589 	 * and create the temporary file.
    590 	 */
    591 	_prop_extern_file_dirname(fname, tname_store);
    592 	if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) {
    593 		errno = ENAMETOOLONG;
    594 		return false;
    595 	}
    596 	strcat(tname_store, PLISTTMP);
    597 
    598 	if ((fd = mkstemp(tname_store)) == -1) {
    599 		return false;
    600 	}
    601 	tname = tname_store;
    602 
    603 	if (write(fd, data, len) != (ssize_t)len) {
    604 		goto bad;
    605 	}
    606 
    607 	if (fsync(fd) == -1) {
    608 		goto bad;
    609 	}
    610 
    611 	myumask = umask(0);
    612 	(void)umask(myumask);
    613 	if (fchmod(fd, 0666 & ~myumask) == -1) {
    614 		goto bad;
    615 	}
    616 
    617 	if (rename(tname, fname) == -1) {
    618 		goto bad;
    619 	}
    620 	tname = NULL;
    621 
    622 	rv = true;
    623 
    624  bad:
    625 	save_errno = errno;
    626 	if (fd != -1) {
    627 		(void) close(fd);
    628 	}
    629 	if (tname != NULL) {
    630 		(void) unlink(tname);
    631 	}
    632 	errno = save_errno;
    633 	return rv;
    634 }
    635 
    636 /*
    637  * _prop_object_externalize_to_file --
    638  *	Externalize an object to the specified file.
    639  */
    640 bool
    641 _prop_object_externalize_to_file(struct _prop_object *obj, const char *fname,
    642     prop_format_t fmt)
    643 {
    644 	char *data = _prop_object_externalize(obj, fmt);
    645 	if (data == NULL) {
    646 		return false;
    647 	}
    648 	bool rv = _prop_extern_write_file(fname, data, strlen(data));
    649 	int save_errno = errno;
    650 	_PROP_FREE(data, M_TEMP);
    651 	errno = save_errno;
    652 
    653 	return rv;
    654 }
    655 
    656 /*
    657  * prop_object_externalize_to_file --
    658  *	Externalize an object to the specifed file in the default format.
    659  */
    660 _PROP_EXPORT bool
    661 prop_object_externalize_to_file(prop_object_t po, const char *fname)
    662 {
    663 	return _prop_object_externalize_to_file((struct _prop_object *)po,
    664 	    fname, _prop_format_default);
    665 }
    666 
    667 /*
    668  * prop_object_externalize_to_file_with_format --
    669  *	Externalize an object to the specifed file in the specified format.
    670  */
    671 _PROP_EXPORT bool
    672 prop_object_externalize_to_file_with_format(prop_object_t po,
    673     const char *fname, prop_format_t fmt)
    674 {
    675 	return _prop_object_externalize_to_file((struct _prop_object *)po,
    676 	    fname, fmt);
    677 }
    678 #endif /* !_KERNEL && !_STANDALONE */
    679 
    680 /*
    681  * prop_object_externalize --
    682  *	Externalize an object in the default format.
    683  */
    684 _PROP_EXPORT char *
    685 prop_object_externalize(prop_object_t po)
    686 {
    687 	return _prop_object_externalize((struct _prop_object *)po,
    688 	    _prop_format_default);
    689 }
    690 
    691 /*
    692  * prop_object_externalize_with_format --
    693  *	Externalize an object in the specified format.
    694  */
    695 _PROP_EXPORT char *
    696 prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt)
    697 {
    698 	return _prop_object_externalize((struct _prop_object *)po, fmt);
    699 }
    700