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
29 #ifdef HAVE_SYS_TYPES_H
30 #include <sys/types.h>
31 #endif
32 #ifdef HAVE_ERRNO_H
33 #include <errno.h>
34 #endif
35 #ifdef HAVE_FCNTL_H
36 #include <fcntl.h>
37 #endif
38 #ifdef HAVE_STDLIB_H
39 #include <stdlib.h>
40 #endif
41 #ifdef HAVE_STRING_H
42 #include <string.h>
43 #endif
44 #if defined(_WIN32) && !defined(__CYGWIN__)
45 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
46 /* don't use bcrypt when XP needs to be supported */
47 #include <bcrypt.h>
48
49 /* Common in other bcrypt implementations, but missing from VS2008. */
50 #ifndef BCRYPT_SUCCESS
51 #define BCRYPT_SUCCESS(r) ((NTSTATUS)(r) == STATUS_SUCCESS)
52 #endif
53
54 #elif defined(HAVE_WINCRYPT_H)
55 #include <wincrypt.h>
56 #endif
57 #endif
58 #ifdef HAVE_ZLIB_H
59 #include <zlib.h>
60 #endif
61 #ifdef HAVE_LZMA_H
62 #include <lzma.h>
63 #endif
64 #ifdef HAVE_BZLIB_H
65 #include <bzlib.h>
66 #endif
67 #ifdef HAVE_LZ4_H
68 #include <lz4.h>
69 #endif
70
71 #include "archive.h"
72 #include "archive_private.h"
73 #include "archive_random_private.h"
74 #include "archive_string.h"
75
76 #ifndef O_CLOEXEC
77 #define O_CLOEXEC 0
78 #endif
79
80 static int archive_utility_string_sort_helper(char **, unsigned int);
81
82 /* Generic initialization of 'struct archive' objects. */
83 int
__archive_clean(struct archive * a)84 __archive_clean(struct archive *a)
85 {
86 archive_string_conversion_free(a);
87 return (ARCHIVE_OK);
88 }
89
90 int
archive_version_number(void)91 archive_version_number(void)
92 {
93 return (ARCHIVE_VERSION_NUMBER);
94 }
95
96 const char *
archive_version_string(void)97 archive_version_string(void)
98 {
99 return (ARCHIVE_VERSION_STRING);
100 }
101
102 int
archive_errno(struct archive * a)103 archive_errno(struct archive *a)
104 {
105 return (a->archive_error_number);
106 }
107
108 const char *
archive_error_string(struct archive * a)109 archive_error_string(struct archive *a)
110 {
111
112 if (a->error != NULL && *a->error != '\0')
113 return (a->error);
114 else
115 return (NULL);
116 }
117
118 int
archive_file_count(struct archive * a)119 archive_file_count(struct archive *a)
120 {
121 return (a->file_count);
122 }
123
124 int
archive_format(struct archive * a)125 archive_format(struct archive *a)
126 {
127 return (a->archive_format);
128 }
129
130 const char *
archive_format_name(struct archive * a)131 archive_format_name(struct archive *a)
132 {
133 return (a->archive_format_name);
134 }
135
136
137 int
archive_compression(struct archive * a)138 archive_compression(struct archive *a)
139 {
140 return archive_filter_code(a, 0);
141 }
142
143 const char *
archive_compression_name(struct archive * a)144 archive_compression_name(struct archive *a)
145 {
146 return archive_filter_name(a, 0);
147 }
148
149
150 /*
151 * Return a count of the number of compressed bytes processed.
152 */
153 la_int64_t
archive_position_compressed(struct archive * a)154 archive_position_compressed(struct archive *a)
155 {
156 return archive_filter_bytes(a, -1);
157 }
158
159 /*
160 * Return a count of the number of uncompressed bytes processed.
161 */
162 la_int64_t
archive_position_uncompressed(struct archive * a)163 archive_position_uncompressed(struct archive *a)
164 {
165 return archive_filter_bytes(a, 0);
166 }
167
168 void
archive_clear_error(struct archive * a)169 archive_clear_error(struct archive *a)
170 {
171 archive_string_empty(&a->error_string);
172 a->error = NULL;
173 a->archive_error_number = 0;
174 }
175
176 void
archive_set_error(struct archive * a,int error_number,const char * fmt,...)177 archive_set_error(struct archive *a, int error_number, const char *fmt, ...)
178 {
179 va_list ap;
180
181 a->archive_error_number = error_number;
182 if (fmt == NULL) {
183 a->error = NULL;
184 return;
185 }
186
187 archive_string_empty(&(a->error_string));
188 va_start(ap, fmt);
189 archive_string_vsprintf(&(a->error_string), fmt, ap);
190 va_end(ap);
191 a->error = a->error_string.s;
192 }
193
194 void
archive_copy_error(struct archive * dest,struct archive * src)195 archive_copy_error(struct archive *dest, struct archive *src)
196 {
197 dest->archive_error_number = src->archive_error_number;
198
199 archive_string_copy(&dest->error_string, &src->error_string);
200 dest->error = dest->error_string.s;
201 }
202
203 void
__archive_errx(int retvalue,const char * msg)204 __archive_errx(int retvalue, const char *msg)
205 {
206 static const char msg1[] = "Fatal Internal Error in libarchive: ";
207 size_t s;
208
209 s = write(2, msg1, strlen(msg1));
210 (void)s; /* UNUSED */
211 s = write(2, msg, strlen(msg));
212 (void)s; /* UNUSED */
213 s = write(2, "\n", 1);
214 (void)s; /* UNUSED */
215 exit(retvalue);
216 }
217
218 /*
219 * Create a temporary file
220 */
221 #if defined(_WIN32) && !defined(__CYGWIN__)
222
223 /*
224 * Do not use Windows tmpfile() function.
225 * It will make a temporary file under the root directory
226 * and it'll cause permission error if a user who is
227 * non-Administrator creates temporary files.
228 * Also Windows version of mktemp family including _mktemp_s
229 * are not secure.
230 */
231 static int
__archive_mktempx(const char * tmpdir,wchar_t * template)232 __archive_mktempx(const char *tmpdir, wchar_t *template)
233 {
234 static const wchar_t prefix[] = L"libarchive_";
235 static const wchar_t suffix[] = L"XXXXXXXXXX";
236 static const wchar_t num[] = {
237 L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7',
238 L'8', L'9', L'A', L'B', L'C', L'D', L'E', L'F',
239 L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N',
240 L'O', L'P', L'Q', L'R', L'S', L'T', L'U', L'V',
241 L'W', L'X', L'Y', L'Z', L'a', L'b', L'c', L'd',
242 L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l',
243 L'm', L'n', L'o', L'p', L'q', L'r', L's', L't',
244 L'u', L'v', L'w', L'x', L'y', L'z'
245 };
246 struct archive_wstring temp_name;
247 wchar_t *ws;
248 DWORD attr;
249 wchar_t *xp, *ep;
250 int fd;
251 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
252 BCRYPT_ALG_HANDLE hAlg = NULL;
253 #else
254 HCRYPTPROV hProv = (HCRYPTPROV)NULL;
255 #endif
256 fd = -1;
257 ws = NULL;
258 archive_string_init(&temp_name);
259
260 if (template == NULL) {
261 /* Get a temporary directory. */
262 if (tmpdir == NULL) {
263 size_t l;
264 wchar_t *tmp;
265
266 l = GetTempPathW(0, NULL);
267 if (l == 0) {
268 la_dosmaperr(GetLastError());
269 goto exit_tmpfile;
270 }
271 tmp = malloc(l*sizeof(wchar_t));
272 if (tmp == NULL) {
273 errno = ENOMEM;
274 goto exit_tmpfile;
275 }
276 GetTempPathW((DWORD)l, tmp);
277 archive_wstrcpy(&temp_name, tmp);
278 free(tmp);
279 } else {
280 if (archive_wstring_append_from_mbs(&temp_name, tmpdir,
281 strlen(tmpdir)) < 0)
282 goto exit_tmpfile;
283 if (temp_name.length == 0 ||
284 temp_name.s[temp_name.length-1] != L'/')
285 archive_wstrappend_wchar(&temp_name, L'/');
286 }
287
288 /* Check if temp_name is a directory. */
289 attr = GetFileAttributesW(temp_name.s);
290 if (attr == (DWORD)-1) {
291 if (GetLastError() != ERROR_FILE_NOT_FOUND) {
292 la_dosmaperr(GetLastError());
293 goto exit_tmpfile;
294 }
295 ws = __la_win_permissive_name_w(temp_name.s);
296 if (ws == NULL) {
297 errno = EINVAL;
298 goto exit_tmpfile;
299 }
300 attr = GetFileAttributesW(ws);
301 if (attr == (DWORD)-1) {
302 la_dosmaperr(GetLastError());
303 goto exit_tmpfile;
304 }
305 }
306 if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
307 errno = ENOTDIR;
308 goto exit_tmpfile;
309 }
310
311 /*
312 * Create a temporary file.
313 */
314 archive_wstrcat(&temp_name, prefix);
315 archive_wstrcat(&temp_name, suffix);
316 ep = temp_name.s + archive_strlen(&temp_name);
317 xp = ep - wcslen(suffix);
318 template = temp_name.s;
319 } else {
320 xp = wcschr(template, L'X');
321 if (xp == NULL) /* No X, programming error */
322 abort();
323 for (ep = xp; *ep == L'X'; ep++)
324 continue;
325 if (*ep) /* X followed by non X, programming error */
326 abort();
327 }
328
329 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
330 if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RNG_ALGORITHM,
331 NULL, 0))) {
332 la_dosmaperr(GetLastError());
333 goto exit_tmpfile;
334 }
335 #else
336 if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL,
337 CRYPT_VERIFYCONTEXT)) {
338 la_dosmaperr(GetLastError());
339 goto exit_tmpfile;
340 }
341 #endif
342
343 for (;;) {
344 wchar_t *p;
345 HANDLE h;
346 # if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */
347 CREATEFILE2_EXTENDED_PARAMETERS createExParams;
348 #endif
349
350 /* Generate a random file name through CryptGenRandom(). */
351 p = xp;
352 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
353 if (!BCRYPT_SUCCESS(BCryptGenRandom(hAlg, (PUCHAR)p,
354 (DWORD)(ep - p)*sizeof(wchar_t), 0))) {
355 la_dosmaperr(GetLastError());
356 goto exit_tmpfile;
357 }
358 #else
359 if (!CryptGenRandom(hProv, (DWORD)(ep - p)*sizeof(wchar_t),
360 (BYTE*)p)) {
361 la_dosmaperr(GetLastError());
362 goto exit_tmpfile;
363 }
364 #endif
365 for (; p < ep; p++)
366 *p = num[((DWORD)*p) % (sizeof(num)/sizeof(num[0]))];
367
368 free(ws);
369 ws = __la_win_permissive_name_w(template);
370 if (ws == NULL) {
371 errno = EINVAL;
372 goto exit_tmpfile;
373 }
374 if (template == temp_name.s) {
375 attr = FILE_ATTRIBUTE_TEMPORARY |
376 FILE_FLAG_DELETE_ON_CLOSE;
377 } else {
378 /* mkstemp */
379 attr = FILE_ATTRIBUTE_NORMAL;
380 }
381 # if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */
382 ZeroMemory(&createExParams, sizeof(createExParams));
383 createExParams.dwSize = sizeof(createExParams);
384 createExParams.dwFileAttributes = attr & 0xFFFF;
385 createExParams.dwFileFlags = attr & 0xFFF00000;
386 h = CreateFile2(ws,
387 GENERIC_READ | GENERIC_WRITE | DELETE,
388 0,/* Not share */
389 CREATE_NEW,
390 &createExParams);
391 #else
392 h = CreateFileW(ws,
393 GENERIC_READ | GENERIC_WRITE | DELETE,
394 0,/* Not share */
395 NULL,
396 CREATE_NEW,/* Create a new file only */
397 attr,
398 NULL);
399 #endif
400 if (h == INVALID_HANDLE_VALUE) {
401 /* The same file already exists. retry with
402 * a new filename. */
403 if (GetLastError() == ERROR_FILE_EXISTS)
404 continue;
405 /* Otherwise, fail creation temporary file. */
406 la_dosmaperr(GetLastError());
407 goto exit_tmpfile;
408 }
409 fd = _open_osfhandle((intptr_t)h, _O_BINARY | _O_RDWR);
410 if (fd == -1) {
411 la_dosmaperr(GetLastError());
412 CloseHandle(h);
413 goto exit_tmpfile;
414 } else
415 break;/* success! */
416 }
417 exit_tmpfile:
418 #if defined(HAVE_BCRYPT_H) && _WIN32_WINNT >= _WIN32_WINNT_VISTA
419 if (hAlg != NULL)
420 BCryptCloseAlgorithmProvider(hAlg, 0);
421 #else
422 if (hProv != (HCRYPTPROV)NULL)
423 CryptReleaseContext(hProv, 0);
424 #endif
425 free(ws);
426 if (template == temp_name.s)
427 archive_wstring_free(&temp_name);
428 return (fd);
429 }
430
431 int
__archive_mktemp(const char * tmpdir)432 __archive_mktemp(const char *tmpdir)
433 {
434 return __archive_mktempx(tmpdir, NULL);
435 }
436
437 int
__archive_mkstemp(wchar_t * template)438 __archive_mkstemp(wchar_t *template)
439 {
440 return __archive_mktempx(NULL, template);
441 }
442
443 #else
444
445 static int
get_tempdir(struct archive_string * temppath)446 get_tempdir(struct archive_string *temppath)
447 {
448 const char *tmp;
449
450 tmp = getenv("TMPDIR");
451 if (tmp == NULL)
452 #ifdef _PATH_TMP
453 tmp = _PATH_TMP;
454 #else
455 tmp = "/tmp";
456 #endif
457 archive_strcpy(temppath, tmp);
458 if (temppath->length == 0 || temppath->s[temppath->length-1] != '/')
459 archive_strappend_char(temppath, '/');
460 return (ARCHIVE_OK);
461 }
462
463 #if defined(HAVE_MKSTEMP)
464
465 /*
466 * We can use mkstemp().
467 */
468
469 int
__archive_mktemp(const char * tmpdir)470 __archive_mktemp(const char *tmpdir)
471 {
472 struct archive_string temp_name;
473 int fd = -1;
474
475 archive_string_init(&temp_name);
476 if (tmpdir == NULL) {
477 if (get_tempdir(&temp_name) != ARCHIVE_OK)
478 goto exit_tmpfile;
479 } else {
480 archive_strcpy(&temp_name, tmpdir);
481 if (temp_name.length == 0 ||
482 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
__archive_mkstemp(char * template)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
__archive_mktempx(const char * tmpdir,char * template)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.length > 0 && 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
__archive_mktemp(const char * tmpdir)592 __archive_mktemp(const char *tmpdir)
593 {
594 return __archive_mktempx(tmpdir, NULL);
595 }
596
597 int
__archive_mkstemp(char * template)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
__archive_ensure_cloexec_flag(int fd)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
archive_utility_string_sort_helper(char ** strings,unsigned int n)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 = realloc(lesser, lesser_count * sizeof(*tmp));
655 if (!tmp) {
656 free(greater);
657 free(lesser);
658 return (ARCHIVE_FATAL);
659 }
660 lesser = tmp;
661 lesser[lesser_count - 1] = strings[i];
662 }
663 else
664 {
665 greater_count++;
666 tmp = realloc(greater, greater_count * sizeof(*tmp));
667 if (!tmp) {
668 free(greater);
669 free(lesser);
670 return (ARCHIVE_FATAL);
671 }
672 greater = tmp;
673 greater[greater_count - 1] = strings[i];
674 }
675 }
676
677 /* quicksort(lesser) */
678 retval1 = archive_utility_string_sort_helper(lesser, lesser_count);
679 for (i = 0; i < lesser_count; i++)
680 strings[i] = lesser[i];
681 free(lesser);
682
683 /* pivot */
684 strings[lesser_count] = pivot;
685
686 /* quicksort(greater) */
687 retval2 = archive_utility_string_sort_helper(greater, greater_count);
688 for (i = 0; i < greater_count; i++)
689 strings[lesser_count + 1 + i] = greater[i];
690 free(greater);
691
692 return (retval1 < retval2) ? retval1 : retval2;
693 }
694
695 int
archive_utility_string_sort(char ** strings)696 archive_utility_string_sort(char **strings)
697 {
698 unsigned int size = 0;
699 while (strings[size] != NULL)
700 size++;
701 return archive_utility_string_sort_helper(strings, size);
702 }
703