xref: /freebsd/contrib/libarchive/libarchive/archive_util.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 /*-
2  * Copyright (c) 2009-2012,2014 Michihiro NAKAJIMA
3  * Copyright (c) 2003-2007 Tim Kientzle
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "archive_platform.h"
28 __FBSDID("$FreeBSD$");
29 
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33 #ifdef HAVE_ERRNO_H
34 #include <errno.h>
35 #endif
36 #ifdef HAVE_FCNTL_H
37 #include <fcntl.h>
38 #endif
39 #ifdef HAVE_STDLIB_H
40 #include <stdlib.h>
41 #endif
42 #ifdef HAVE_STRING_H
43 #include <string.h>
44 #endif
45 #if defined(_WIN32) && !defined(__CYGWIN__)
46 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
47 /* don't use bcrypt when XP needs to be supported */
48 #include <bcrypt.h>
49 
50 /* Common in other bcrypt implementations, but missing from VS2008. */
51 #ifndef BCRYPT_SUCCESS
52 #define BCRYPT_SUCCESS(r) ((NTSTATUS)(r) == STATUS_SUCCESS)
53 #endif
54 
55 #elif defined(HAVE_WINCRYPT_H)
56 #include <wincrypt.h>
57 #endif
58 #endif
59 #ifdef HAVE_ZLIB_H
60 #include <zlib.h>
61 #endif
62 #ifdef HAVE_LZMA_H
63 #include <lzma.h>
64 #endif
65 #ifdef HAVE_BZLIB_H
66 #include <bzlib.h>
67 #endif
68 #ifdef HAVE_LZ4_H
69 #include <lz4.h>
70 #endif
71 
72 #include "archive.h"
73 #include "archive_private.h"
74 #include "archive_random_private.h"
75 #include "archive_string.h"
76 
77 #ifndef O_CLOEXEC
78 #define O_CLOEXEC	0
79 #endif
80 
81 static int archive_utility_string_sort_helper(char **, unsigned int);
82 
83 /* Generic initialization of 'struct archive' objects. */
84 int
85 __archive_clean(struct archive *a)
86 {
87 	archive_string_conversion_free(a);
88 	return (ARCHIVE_OK);
89 }
90 
91 int
92 archive_version_number(void)
93 {
94 	return (ARCHIVE_VERSION_NUMBER);
95 }
96 
97 const char *
98 archive_version_string(void)
99 {
100 	return (ARCHIVE_VERSION_STRING);
101 }
102 
103 int
104 archive_errno(struct archive *a)
105 {
106 	return (a->archive_error_number);
107 }
108 
109 const char *
110 archive_error_string(struct archive *a)
111 {
112 
113 	if (a->error != NULL  &&  *a->error != '\0')
114 		return (a->error);
115 	else
116 		return (NULL);
117 }
118 
119 int
120 archive_file_count(struct archive *a)
121 {
122 	return (a->file_count);
123 }
124 
125 int
126 archive_format(struct archive *a)
127 {
128 	return (a->archive_format);
129 }
130 
131 const char *
132 archive_format_name(struct archive *a)
133 {
134 	return (a->archive_format_name);
135 }
136 
137 
138 int
139 archive_compression(struct archive *a)
140 {
141 	return archive_filter_code(a, 0);
142 }
143 
144 const char *
145 archive_compression_name(struct archive *a)
146 {
147 	return archive_filter_name(a, 0);
148 }
149 
150 
151 /*
152  * Return a count of the number of compressed bytes processed.
153  */
154 la_int64_t
155 archive_position_compressed(struct archive *a)
156 {
157 	return archive_filter_bytes(a, -1);
158 }
159 
160 /*
161  * Return a count of the number of uncompressed bytes processed.
162  */
163 la_int64_t
164 archive_position_uncompressed(struct archive *a)
165 {
166 	return archive_filter_bytes(a, 0);
167 }
168 
169 void
170 archive_clear_error(struct archive *a)
171 {
172 	archive_string_empty(&a->error_string);
173 	a->error = NULL;
174 	a->archive_error_number = 0;
175 }
176 
177 void
178 archive_set_error(struct archive *a, int error_number, const char *fmt, ...)
179 {
180 	va_list ap;
181 
182 	a->archive_error_number = error_number;
183 	if (fmt == NULL) {
184 		a->error = NULL;
185 		return;
186 	}
187 
188 	archive_string_empty(&(a->error_string));
189 	va_start(ap, fmt);
190 	archive_string_vsprintf(&(a->error_string), fmt, ap);
191 	va_end(ap);
192 	a->error = a->error_string.s;
193 }
194 
195 void
196 archive_copy_error(struct archive *dest, struct archive *src)
197 {
198 	dest->archive_error_number = src->archive_error_number;
199 
200 	archive_string_copy(&dest->error_string, &src->error_string);
201 	dest->error = dest->error_string.s;
202 }
203 
204 void
205 __archive_errx(int retvalue, const char *msg)
206 {
207 	static const char msg1[] = "Fatal Internal Error in libarchive: ";
208 	size_t s;
209 
210 	s = write(2, msg1, strlen(msg1));
211 	(void)s; /* UNUSED */
212 	s = write(2, msg, strlen(msg));
213 	(void)s; /* UNUSED */
214 	s = write(2, "\n", 1);
215 	(void)s; /* UNUSED */
216 	exit(retvalue);
217 }
218 
219 /*
220  * Create a temporary file
221  */
222 #if defined(_WIN32) && !defined(__CYGWIN__)
223 
224 /*
225  * Do not use Windows tmpfile() function.
226  * It will make a temporary file under the root directory
227  * and it'll cause permission error if a user who is
228  * non-Administrator creates temporary files.
229  * Also Windows version of mktemp family including _mktemp_s
230  * are not secure.
231  */
232 static int
233 __archive_mktempx(const char *tmpdir, wchar_t *template)
234 {
235 	static const wchar_t prefix[] = L"libarchive_";
236 	static const wchar_t suffix[] = L"XXXXXXXXXX";
237 	static const wchar_t num[] = {
238 		L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7',
239 		L'8', L'9', L'A', L'B', L'C', L'D', L'E', L'F',
240 		L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N',
241 		L'O', L'P', L'Q', L'R', L'S', L'T', L'U', L'V',
242 		L'W', L'X', L'Y', L'Z', L'a', L'b', L'c', L'd',
243 		L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l',
244 		L'm', L'n', L'o', L'p', L'q', L'r', L's', L't',
245 		L'u', L'v', L'w', L'x', L'y', L'z'
246 	};
247 	struct archive_wstring temp_name;
248 	wchar_t *ws;
249 	DWORD attr;
250 	wchar_t *xp, *ep;
251 	int fd;
252 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
253 	BCRYPT_ALG_HANDLE hAlg = NULL;
254 #else
255 	HCRYPTPROV hProv = (HCRYPTPROV)NULL;
256 #endif
257 	fd = -1;
258 	ws = NULL;
259 
260 	if (template == NULL) {
261 		archive_string_init(&temp_name);
262 
263 		/* Get a temporary directory. */
264 		if (tmpdir == NULL) {
265 			size_t l;
266 			wchar_t *tmp;
267 
268 			l = GetTempPathW(0, NULL);
269 			if (l == 0) {
270 				la_dosmaperr(GetLastError());
271 				goto exit_tmpfile;
272 			}
273 			tmp = malloc(l*sizeof(wchar_t));
274 			if (tmp == NULL) {
275 				errno = ENOMEM;
276 				goto exit_tmpfile;
277 			}
278 			GetTempPathW((DWORD)l, tmp);
279 			archive_wstrcpy(&temp_name, tmp);
280 			free(tmp);
281 		} else {
282 			if (archive_wstring_append_from_mbs(&temp_name, tmpdir,
283 			    strlen(tmpdir)) < 0)
284 				goto exit_tmpfile;
285 			if (temp_name.s[temp_name.length-1] != L'/')
286 				archive_wstrappend_wchar(&temp_name, L'/');
287 		}
288 
289 		/* Check if temp_name is a directory. */
290 		attr = GetFileAttributesW(temp_name.s);
291 		if (attr == (DWORD)-1) {
292 			if (GetLastError() != ERROR_FILE_NOT_FOUND) {
293 				la_dosmaperr(GetLastError());
294 				goto exit_tmpfile;
295 			}
296 			ws = __la_win_permissive_name_w(temp_name.s);
297 			if (ws == NULL) {
298 				errno = EINVAL;
299 				goto exit_tmpfile;
300 			}
301 			attr = GetFileAttributesW(ws);
302 			if (attr == (DWORD)-1) {
303 				la_dosmaperr(GetLastError());
304 				goto exit_tmpfile;
305 			}
306 		}
307 		if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
308 			errno = ENOTDIR;
309 			goto exit_tmpfile;
310 		}
311 
312 		/*
313 		 * Create a temporary file.
314 		 */
315 		archive_wstrcat(&temp_name, prefix);
316 		archive_wstrcat(&temp_name, suffix);
317 		ep = temp_name.s + archive_strlen(&temp_name);
318 		xp = ep - wcslen(suffix);
319 		template = temp_name.s;
320 	} else {
321 		xp = wcschr(template, L'X');
322 		if (xp == NULL)	/* No X, programming error */
323 			abort();
324 		for (ep = xp; *ep == L'X'; ep++)
325 			continue;
326 		if (*ep)	/* X followed by non X, programming error */
327 			abort();
328 	}
329 
330 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
331 	if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RNG_ALGORITHM,
332 		NULL, 0))) {
333 		la_dosmaperr(GetLastError());
334 		goto exit_tmpfile;
335 	}
336 #else
337 	if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
338 		CRYPT_VERIFYCONTEXT)) {
339 		la_dosmaperr(GetLastError());
340 		goto exit_tmpfile;
341 	}
342 #endif
343 
344 	for (;;) {
345 		wchar_t *p;
346 		HANDLE h;
347 # if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */
348 		CREATEFILE2_EXTENDED_PARAMETERS createExParams;
349 #endif
350 
351 		/* Generate a random file name through CryptGenRandom(). */
352 		p = xp;
353 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
354 		if (!BCRYPT_SUCCESS(BCryptGenRandom(hAlg, (PUCHAR)p,
355 		    (DWORD)(ep - p)*sizeof(wchar_t), 0))) {
356 			la_dosmaperr(GetLastError());
357 			goto exit_tmpfile;
358 		}
359 #else
360 		if (!CryptGenRandom(hProv, (DWORD)(ep - p)*sizeof(wchar_t),
361 		    (BYTE*)p)) {
362 			la_dosmaperr(GetLastError());
363 			goto exit_tmpfile;
364 		}
365 #endif
366 		for (; p < ep; p++)
367 			*p = num[((DWORD)*p) % (sizeof(num)/sizeof(num[0]))];
368 
369 		free(ws);
370 		ws = __la_win_permissive_name_w(template);
371 		if (ws == NULL) {
372 			errno = EINVAL;
373 			goto exit_tmpfile;
374 		}
375 		if (template == temp_name.s) {
376 			attr = FILE_ATTRIBUTE_TEMPORARY |
377 			       FILE_FLAG_DELETE_ON_CLOSE;
378 		} else {
379 			/* mkstemp */
380 			attr = FILE_ATTRIBUTE_NORMAL;
381 		}
382 # if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */
383 		ZeroMemory(&createExParams, sizeof(createExParams));
384 		createExParams.dwSize = sizeof(createExParams);
385 		createExParams.dwFileAttributes = attr & 0xFFFF;
386 		createExParams.dwFileFlags = attr & 0xFFF00000;
387 		h = CreateFile2(ws,
388 		    GENERIC_READ | GENERIC_WRITE | DELETE,
389 		    0,/* Not share */
390 			CREATE_NEW,
391 			&createExParams);
392 #else
393 		h = CreateFileW(ws,
394 		    GENERIC_READ | GENERIC_WRITE | DELETE,
395 		    0,/* Not share */
396 		    NULL,
397 		    CREATE_NEW,/* Create a new file only */
398 		    attr,
399 		    NULL);
400 #endif
401 		if (h == INVALID_HANDLE_VALUE) {
402 			/* The same file already exists. retry with
403 			 * a new filename. */
404 			if (GetLastError() == ERROR_FILE_EXISTS)
405 				continue;
406 			/* Otherwise, fail creation temporary file. */
407 			la_dosmaperr(GetLastError());
408 			goto exit_tmpfile;
409 		}
410 		fd = _open_osfhandle((intptr_t)h, _O_BINARY | _O_RDWR);
411 		if (fd == -1) {
412 			la_dosmaperr(GetLastError());
413 			CloseHandle(h);
414 			goto exit_tmpfile;
415 		} else
416 			break;/* success! */
417 	}
418 exit_tmpfile:
419 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
420 	if (hAlg != NULL)
421 		BCryptCloseAlgorithmProvider(hAlg, 0);
422 #else
423 	if (hProv != (HCRYPTPROV)NULL)
424 		CryptReleaseContext(hProv, 0);
425 #endif
426 	free(ws);
427 	if (template == temp_name.s)
428 		archive_wstring_free(&temp_name);
429 	return (fd);
430 }
431 
432 int
433 __archive_mktemp(const char *tmpdir)
434 {
435 	return __archive_mktempx(tmpdir, NULL);
436 }
437 
438 int
439 __archive_mkstemp(wchar_t *template)
440 {
441 	return __archive_mktempx(NULL, template);
442 }
443 
444 #else
445 
446 static int
447 get_tempdir(struct archive_string *temppath)
448 {
449 	const char *tmp;
450 
451 	tmp = getenv("TMPDIR");
452 	if (tmp == NULL)
453 #ifdef _PATH_TMP
454 		tmp = _PATH_TMP;
455 #else
456                 tmp = "/tmp";
457 #endif
458 	archive_strcpy(temppath, tmp);
459 	if (temppath->s[temppath->length-1] != '/')
460 		archive_strappend_char(temppath, '/');
461 	return (ARCHIVE_OK);
462 }
463 
464 #if defined(HAVE_MKSTEMP)
465 
466 /*
467  * We can use mkstemp().
468  */
469 
470 int
471 __archive_mktemp(const char *tmpdir)
472 {
473 	struct archive_string temp_name;
474 	int fd = -1;
475 
476 	archive_string_init(&temp_name);
477 	if (tmpdir == NULL) {
478 		if (get_tempdir(&temp_name) != ARCHIVE_OK)
479 			goto exit_tmpfile;
480 	} else {
481 		archive_strcpy(&temp_name, tmpdir);
482 		if (temp_name.s[temp_name.length-1] != '/')
483 			archive_strappend_char(&temp_name, '/');
484 	}
485 #ifdef O_TMPFILE
486 	fd = open(temp_name.s, O_RDWR|O_CLOEXEC|O_TMPFILE|O_EXCL, 0600);
487 	if(fd >= 0)
488 		goto exit_tmpfile;
489 #endif
490 	archive_strcat(&temp_name, "libarchive_XXXXXX");
491 	fd = mkstemp(temp_name.s);
492 	if (fd < 0)
493 		goto exit_tmpfile;
494 	__archive_ensure_cloexec_flag(fd);
495 	unlink(temp_name.s);
496 exit_tmpfile:
497 	archive_string_free(&temp_name);
498 	return (fd);
499 }
500 
501 int
502 __archive_mkstemp(char *template)
503 {
504 	int fd = -1;
505 	fd = mkstemp(template);
506 	if (fd >= 0)
507 		__archive_ensure_cloexec_flag(fd);
508 	return (fd);
509 }
510 
511 #else /* !HAVE_MKSTEMP */
512 
513 /*
514  * We use a private routine.
515  */
516 
517 static int
518 __archive_mktempx(const char *tmpdir, char *template)
519 {
520         static const char num[] = {
521 		'0', '1', '2', '3', '4', '5', '6', '7',
522 		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
523 		'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
524 		'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
525 		'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
526 		'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
527 		'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
528 		'u', 'v', 'w', 'x', 'y', 'z'
529         };
530 	struct archive_string temp_name;
531 	struct stat st;
532 	int fd;
533 	char *tp, *ep;
534 
535 	fd = -1;
536 	if (template == NULL) {
537 		archive_string_init(&temp_name);
538 		if (tmpdir == NULL) {
539 			if (get_tempdir(&temp_name) != ARCHIVE_OK)
540 				goto exit_tmpfile;
541 		} else
542 			archive_strcpy(&temp_name, tmpdir);
543 		if (temp_name.s[temp_name.length-1] == '/') {
544 			temp_name.s[temp_name.length-1] = '\0';
545 			temp_name.length --;
546 		}
547 		if (la_stat(temp_name.s, &st) < 0)
548 			goto exit_tmpfile;
549 		if (!S_ISDIR(st.st_mode)) {
550 			errno = ENOTDIR;
551 			goto exit_tmpfile;
552 		}
553 		archive_strcat(&temp_name, "/libarchive_");
554 		tp = temp_name.s + archive_strlen(&temp_name);
555 		archive_strcat(&temp_name, "XXXXXXXXXX");
556 		ep = temp_name.s + archive_strlen(&temp_name);
557 		template = temp_name.s;
558 	} else {
559 		tp = strchr(template, 'X');
560 		if (tp == NULL)	/* No X, programming error */
561 			abort();
562 		for (ep = tp; *ep == 'X'; ep++)
563 			continue;
564 		if (*ep)	/* X followed by non X, programming error */
565 			abort();
566 	}
567 
568 	do {
569 		char *p;
570 
571 		p = tp;
572 		archive_random(p, ep - p);
573 		while (p < ep) {
574 			int d = *((unsigned char *)p) % sizeof(num);
575 			*p++ = num[d];
576 		}
577 		fd = open(template, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC,
578 			  0600);
579 	} while (fd < 0 && errno == EEXIST);
580 	if (fd < 0)
581 		goto exit_tmpfile;
582 	__archive_ensure_cloexec_flag(fd);
583 	if (template == temp_name.s)
584 		unlink(temp_name.s);
585 exit_tmpfile:
586 	if (template == temp_name.s)
587 		archive_string_free(&temp_name);
588 	return (fd);
589 }
590 
591 int
592 __archive_mktemp(const char *tmpdir)
593 {
594 	return __archive_mktempx(tmpdir, NULL);
595 }
596 
597 int
598 __archive_mkstemp(char *template)
599 {
600 	return __archive_mktempx(NULL, template);
601 }
602 
603 #endif /* !HAVE_MKSTEMP */
604 #endif /* !_WIN32 || __CYGWIN__ */
605 
606 /*
607  * Set FD_CLOEXEC flag to a file descriptor if it is not set.
608  * We have to set the flag if the platform does not provide O_CLOEXEC
609  * or F_DUPFD_CLOEXEC flags.
610  *
611  * Note: This function is absolutely called after creating a new file
612  * descriptor even if the platform seemingly provides O_CLOEXEC or
613  * F_DUPFD_CLOEXEC macros because it is possible that the platform
614  * merely declares those macros, especially Linux 2.6.18 - 2.6.24 do it.
615  */
616 void
617 __archive_ensure_cloexec_flag(int fd)
618 {
619 #if defined(_WIN32) && !defined(__CYGWIN__)
620 	(void)fd; /* UNUSED */
621 #else
622 	int flags;
623 
624 	if (fd >= 0) {
625 		flags = fcntl(fd, F_GETFD);
626 		if (flags != -1 && (flags & FD_CLOEXEC) == 0)
627 			fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
628 	}
629 #endif
630 }
631 
632 /*
633  * Utility function to sort a group of strings using quicksort.
634  */
635 static int
636 archive_utility_string_sort_helper(char **strings, unsigned int n)
637 {
638 	unsigned int i, lesser_count, greater_count;
639 	char **lesser, **greater, **tmp, *pivot;
640 	int retval1, retval2;
641 
642 	/* A list of 0 or 1 elements is already sorted */
643 	if (n <= 1)
644 		return (ARCHIVE_OK);
645 
646 	lesser_count = greater_count = 0;
647 	lesser = greater = NULL;
648 	pivot = strings[0];
649 	for (i = 1; i < n; i++)
650 	{
651 		if (strcmp(strings[i], pivot) < 0)
652 		{
653 			lesser_count++;
654 			tmp = (char **)realloc(lesser,
655 				lesser_count * sizeof(char *));
656 			if (!tmp) {
657 				free(greater);
658 				free(lesser);
659 				return (ARCHIVE_FATAL);
660 			}
661 			lesser = tmp;
662 			lesser[lesser_count - 1] = strings[i];
663 		}
664 		else
665 		{
666 			greater_count++;
667 			tmp = (char **)realloc(greater,
668 				greater_count * sizeof(char *));
669 			if (!tmp) {
670 				free(greater);
671 				free(lesser);
672 				return (ARCHIVE_FATAL);
673 			}
674 			greater = tmp;
675 			greater[greater_count - 1] = strings[i];
676 		}
677 	}
678 
679 	/* quicksort(lesser) */
680 	retval1 = archive_utility_string_sort_helper(lesser, lesser_count);
681 	for (i = 0; i < lesser_count; i++)
682 		strings[i] = lesser[i];
683 	free(lesser);
684 
685 	/* pivot */
686 	strings[lesser_count] = pivot;
687 
688 	/* quicksort(greater) */
689 	retval2 = archive_utility_string_sort_helper(greater, greater_count);
690 	for (i = 0; i < greater_count; i++)
691 		strings[lesser_count + 1 + i] = greater[i];
692 	free(greater);
693 
694 	return (retval1 < retval2) ? retval1 : retval2;
695 }
696 
697 int
698 archive_utility_string_sort(char **strings)
699 {
700 	  unsigned int size = 0;
701 	  while (strings[size] != NULL)
702 		size++;
703 	  return archive_utility_string_sort_helper(strings, size);
704 }
705