1
2/*
3 * sysinfo.c :  information about the running system
4 *
5 * ====================================================================
6 *    Licensed to the Apache Software Foundation (ASF) under one
7 *    or more contributor license agreements.  See the NOTICE file
8 *    distributed with this work for additional information
9 *    regarding copyright ownership.  The ASF licenses this file
10 *    to you under the Apache License, Version 2.0 (the
11 *    "License"); you may not use this file except in compliance
12 *    with the License.  You may obtain a copy of the License at
13 *
14 *      http://www.apache.org/licenses/LICENSE-2.0
15 *
16 *    Unless required by applicable law or agreed to in writing,
17 *    software distributed under the License is distributed on an
18 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 *    KIND, either express or implied.  See the License for the
20 *    specific language governing permissions and limitations
21 *    under the License.
22 * ====================================================================
23 */
24
25
26
27#define APR_WANT_STRFUNC
28#include <apr_want.h>
29
30#include <apr_lib.h>
31#include <apr_pools.h>
32#include <apr_file_info.h>
33#include <apr_signal.h>
34#include <apr_strings.h>
35#include <apr_thread_proc.h>
36#include <apr_version.h>
37#include <apu_version.h>
38
39#include "svn_pools.h"
40#include "svn_ctype.h"
41#include "svn_dirent_uri.h"
42#include "svn_error.h"
43#include "svn_io.h"
44#include "svn_string.h"
45#include "svn_utf.h"
46#include "svn_version.h"
47
48#include "private/svn_sqlite.h"
49#include "private/svn_subr_private.h"
50#include "private/svn_utf_private.h"
51
52#include "sysinfo.h"
53#include "svn_private_config.h"
54
55#if HAVE_SYS_TYPES_H
56#include <sys/types.h>
57#endif
58
59#if HAVE_SYS_UTSNAME_H
60#include <sys/utsname.h>
61#endif
62
63#if HAVE_UNISTD_H
64#include <unistd.h>
65#endif
66
67#if HAVE_ELF_H
68#include <elf.h>
69#endif
70
71#ifdef SVN_HAVE_MACOS_PLIST
72#include <CoreFoundation/CoreFoundation.h>
73#include <AvailabilityMacros.h>
74# ifndef MAC_OS_X_VERSION_10_6
75#  define MAC_OS_X_VERSION_10_6  1060
76# endif
77#endif
78
79#ifdef SVN_HAVE_MACHO_ITERATE
80#include <mach-o/dyld.h>
81#include <mach-o/loader.h>
82#endif
83
84#if HAVE_UNAME
85static const char *canonical_host_from_uname(apr_pool_t *pool);
86# ifndef SVN_HAVE_MACOS_PLIST
87static const char *release_name_from_uname(apr_pool_t *pool);
88# endif
89#endif
90
91#ifdef WIN32
92static const char *win32_canonical_host(apr_pool_t *pool);
93static const char *win32_release_name(apr_pool_t *pool);
94static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
95#endif /* WIN32 */
96
97#ifdef SVN_HAVE_MACOS_PLIST
98static const char *macos_release_name(apr_pool_t *pool);
99#endif
100
101#ifdef SVN_HAVE_MACHO_ITERATE
102static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
103#endif
104
105
106#if __linux__
107static const char *linux_release_name(apr_pool_t *pool);
108static const apr_array_header_t *linux_shared_libs(apr_pool_t *pool);
109#endif
110
111const char *
112svn_sysinfo__canonical_host(apr_pool_t *pool)
113{
114#ifdef WIN32
115  return win32_canonical_host(pool);
116#elif HAVE_UNAME
117  return canonical_host_from_uname(pool);
118#else
119  return "unknown-unknown-unknown";
120#endif
121}
122
123
124const char *
125svn_sysinfo__release_name(apr_pool_t *pool)
126{
127#ifdef WIN32
128  return win32_release_name(pool);
129#elif defined(SVN_HAVE_MACOS_PLIST)
130  return macos_release_name(pool);
131#elif __linux__
132  return linux_release_name(pool);
133#elif HAVE_UNAME
134  return release_name_from_uname(pool);
135#else
136  return NULL;
137#endif
138}
139
140const apr_array_header_t *
141svn_sysinfo__linked_libs(apr_pool_t *pool)
142{
143  svn_version_ext_linked_lib_t *lib;
144  apr_array_header_t *array = apr_array_make(pool, 7, sizeof(*lib));
145  int lz4_version = svn_lz4__runtime_version();
146
147  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
148  lib->name = "APR";
149  lib->compiled_version = APR_VERSION_STRING;
150  lib->runtime_version = apr_pstrdup(pool, apr_version_string());
151
152/* Don't list APR-Util if it isn't linked in, which it may not be if
153 * we're using APR 2.x+ which combined APR-Util into APR. */
154#ifdef APU_VERSION_STRING
155  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
156  lib->name = "APR-Util";
157  lib->compiled_version = APU_VERSION_STRING;
158  lib->runtime_version = apr_pstrdup(pool, apu_version_string());
159#endif
160
161  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
162  lib->name = "Expat";
163  lib->compiled_version = apr_pstrdup(pool, svn_xml__compiled_version());
164  lib->runtime_version = apr_pstrdup(pool, svn_xml__runtime_version());
165
166  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
167  lib->name = "SQLite";
168  lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
169#ifdef SVN_SQLITE_INLINE
170  lib->runtime_version = NULL;
171#else
172  lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
173#endif
174
175  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
176  lib->name = "Utf8proc";
177  lib->compiled_version = apr_pstrdup(pool, svn_utf__utf8proc_compiled_version());
178  lib->runtime_version = apr_pstrdup(pool, svn_utf__utf8proc_runtime_version());
179
180  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
181  lib->name = "ZLib";
182  lib->compiled_version = apr_pstrdup(pool, svn_zlib__compiled_version());
183  lib->runtime_version = apr_pstrdup(pool, svn_zlib__runtime_version());
184
185  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
186  lib->name = "LZ4";
187  lib->compiled_version = apr_pstrdup(pool, svn_lz4__compiled_version());
188
189  lib->runtime_version = apr_psprintf(pool, "%d.%d.%d",
190                                      lz4_version / 100 / 100,
191                                      (lz4_version / 100) % 100,
192                                      lz4_version % 100);
193
194  return array;
195}
196
197const apr_array_header_t *
198svn_sysinfo__loaded_libs(apr_pool_t *pool)
199{
200#ifdef WIN32
201  return win32_shared_libs(pool);
202#elif defined(SVN_HAVE_MACHO_ITERATE)
203  return macos_shared_libs(pool);
204#elif __linux__
205  return linux_shared_libs(pool);
206#else
207  return NULL;
208#endif
209}
210
211
212#if HAVE_UNAME
213static const char*
214canonical_host_from_uname(apr_pool_t *pool)
215{
216  const char *machine = "unknown";
217  const char *vendor = "unknown";
218  const char *sysname = "unknown";
219  const char *sysver = "";
220  struct utsname info;
221
222  if (0 <= uname(&info))
223    {
224      svn_error_t *err;
225      const char *tmp;
226
227      err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
228      if (err)
229        svn_error_clear(err);
230      else
231        machine = tmp;
232
233      err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
234      if (err)
235        svn_error_clear(err);
236      else
237        {
238          char *lwr = apr_pstrdup(pool, tmp);
239          char *it = lwr;
240          while (*it)
241            {
242              if (svn_ctype_isupper(*it))
243                *it = apr_tolower(*it);
244              ++it;
245            }
246          sysname = lwr;
247        }
248
249      if (0 == strcmp(sysname, "darwin"))
250        vendor = "apple";
251      if (0 == strcmp(sysname, "linux"))
252        sysver = "-gnu";
253      else
254        {
255          err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
256          if (err)
257            svn_error_clear(err);
258          else
259            {
260              apr_size_t n = strspn(tmp, ".0123456789");
261              if (n > 0)
262                {
263                  char *ver = apr_pstrdup(pool, tmp);
264                  ver[n] = 0;
265                  sysver = ver;
266                }
267              else
268                sysver = tmp;
269            }
270        }
271    }
272
273  return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
274}
275
276# ifndef SVN_HAVE_MACOS_PLIST
277/* Generate a release name from the uname(3) info, effectively
278   returning "`uname -s` `uname -r`". */
279static const char *
280release_name_from_uname(apr_pool_t *pool)
281{
282  struct utsname info;
283  if (0 <= uname(&info))
284    {
285      svn_error_t *err;
286      const char *sysname;
287      const char *sysver;
288
289      err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
290      if (err)
291        {
292          sysname = NULL;
293          svn_error_clear(err);
294        }
295
296
297      err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
298      if (err)
299        {
300          sysver = NULL;
301          svn_error_clear(err);
302        }
303
304      if (sysname || sysver)
305        {
306          return apr_psprintf(pool, "%s%s%s",
307                              (sysname ? sysname : ""),
308                              (sysver ? (sysname ? " " : "") : ""),
309                              (sysver ? sysver : ""));
310        }
311    }
312  return NULL;
313}
314# endif  /* !SVN_HAVE_MACOS_PLIST */
315#endif  /* HAVE_UNAME */
316
317
318#if __linux__
319/* Find the first whitespace character in a stringbuf.
320   Analogous to svn_stringbuf_first_non_whitespace. */
321static apr_size_t
322stringbuf_first_whitespace(const svn_stringbuf_t *str)
323{
324  apr_size_t i;
325  for (i = 0; i < str->len; ++i)
326    {
327      if (svn_ctype_isspace(str->data[i]))
328        return i;
329    }
330  return str->len;
331}
332
333/* Skip a whitespace-delimited field in a stringbuf. */
334static void
335stringbuf_skip_whitespace_field(svn_stringbuf_t *str)
336{
337  apr_size_t i;
338  i = stringbuf_first_whitespace(str);
339  svn_stringbuf_leftchop(str, i);
340  i = svn_stringbuf_first_non_whitespace(str);
341  svn_stringbuf_leftchop(str, i);
342}
343
344/* Split a stringbuf into a key/value pair.
345   Return the key, leaving the stripped value in the stringbuf. */
346static const char *
347stringbuf_split_key(svn_stringbuf_t *buffer, char delim)
348{
349  char *key;
350  char *end;
351
352  end = strchr(buffer->data, delim);
353  if (!end)
354    return NULL;
355
356  svn_stringbuf_strip_whitespace(buffer);
357
358  /* Now we split the currently allocated buffer in two parts:
359      - a const char * HEAD
360      - the remaining stringbuf_t. */
361
362  /* Create HEAD as '\0' terminated const char * */
363  key = buffer->data;
364  end = strchr(key, delim);
365  *end = '\0';
366
367  /* And update the TAIL to be a smaller, but still valid stringbuf */
368  buffer->data = end + 1;
369  buffer->len -= 1 + end - key;
370  buffer->blocksize -= 1 + end - key;
371
372  svn_stringbuf_strip_whitespace(buffer);
373
374  return key;
375}
376
377/* Parse `/usr/bin/lsb_rlease --all` */
378static const char *
379lsb_release(apr_pool_t *pool)
380{
381  static const char *const args[3] =
382    {
383      "/usr/bin/lsb_release",
384      "--all",
385      NULL
386    };
387
388  const char *distributor = NULL;
389  const char *description = NULL;
390  const char *release = NULL;
391  const char *codename = NULL;
392
393  apr_proc_t lsbproc;
394  svn_stream_t *lsbinfo;
395  svn_error_t *err;
396
397  /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
398  {
399    apr_file_t *stdin_handle;
400    apr_file_t *stdout_handle;
401
402    err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
403                           APR_READ, APR_OS_DEFAULT, pool);
404    if (!err)
405      err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
406                             APR_WRITE, APR_OS_DEFAULT, pool);
407    if (!err)
408      err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
409                              FALSE, stdin_handle,
410                              TRUE, NULL,
411                              FALSE, stdout_handle,
412                              pool);
413    if (err)
414      {
415        svn_error_clear(err);
416        return NULL;
417      }
418  }
419
420  /* Parse the output and try to populate the  */
421  lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
422  if (lsbinfo)
423    {
424      for (;;)
425        {
426          svn_boolean_t eof = FALSE;
427          svn_stringbuf_t *line;
428          const char *key;
429
430          err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
431          if (err || eof)
432            break;
433
434          key = stringbuf_split_key(line, ':');
435          if (!key)
436            continue;
437
438          if (0 == svn_cstring_casecmp(key, "Distributor ID"))
439            distributor = line->data;
440          else if (0 == svn_cstring_casecmp(key, "Description"))
441            description = line->data;
442          else if (0 == svn_cstring_casecmp(key, "Release"))
443            release = line->data;
444          else if (0 == svn_cstring_casecmp(key, "Codename"))
445            codename = line->data;
446        }
447      err = svn_error_compose_create(err,
448                                     svn_stream_close(lsbinfo));
449      if (err)
450        {
451          svn_error_clear(err);
452          apr_proc_kill(&lsbproc, SIGKILL);
453          return NULL;
454        }
455    }
456
457  /* Reap the child process */
458  err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
459  if (err)
460    {
461      svn_error_clear(err);
462      return NULL;
463    }
464
465  if (description)
466    return apr_psprintf(pool, "%s%s%s%s", description,
467                        (codename ? " (" : ""),
468                        (codename ? codename : ""),
469                        (codename ? ")" : ""));
470  if (distributor)
471    return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
472                        (release ? " " : ""),
473                        (release ? release : ""),
474                        (codename ? " (" : ""),
475                        (codename ? codename : ""),
476                        (codename ? ")" : ""));
477
478  return NULL;
479}
480
481/* Read /etc/os-release, as documented here:
482 * http://www.freedesktop.org/software/systemd/man/os-release.html
483 */
484static const char *
485systemd_release(apr_pool_t *pool)
486{
487  svn_error_t *err;
488  svn_stream_t *stream;
489
490  /* Open the file. */
491  err = svn_stream_open_readonly(&stream, "/etc/os-release", pool, pool);
492  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
493    {
494      svn_error_clear(err);
495      err = svn_stream_open_readonly(&stream, "/usr/lib/os-release", pool,
496                                     pool);
497    }
498  if (err)
499    {
500      svn_error_clear(err);
501      return NULL;
502    }
503
504  /* Look for the PRETTY_NAME line. */
505  while (TRUE)
506    {
507      svn_stringbuf_t *line;
508      svn_boolean_t eof;
509
510      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
511      if (err)
512        {
513          svn_error_clear(err);
514          return NULL;
515        }
516
517      if (!strncmp(line->data, "PRETTY_NAME=", 12))
518        {
519          svn_stringbuf_t *release_name;
520
521          /* The value may or may not be enclosed by double quotes.  We don't
522           * attempt to strip them. */
523          release_name = svn_stringbuf_create(line->data + 12, pool);
524          svn_error_clear(svn_stream_close(stream));
525          svn_stringbuf_strip_whitespace(release_name);
526          return release_name->data;
527        }
528
529      if (eof)
530        break;
531    }
532
533  /* The file did not contain a PRETTY_NAME line. */
534  svn_error_clear(svn_stream_close(stream));
535  return NULL;
536}
537
538/* Read the whole contents of a file. */
539static svn_stringbuf_t *
540read_file_contents(const char *filename, apr_pool_t *pool)
541{
542  svn_error_t *err;
543  svn_stringbuf_t *buffer;
544
545  err = svn_stringbuf_from_file2(&buffer, filename, pool);
546  if (err)
547    {
548      svn_error_clear(err);
549      return NULL;
550    }
551
552  return buffer;
553}
554
555/* Strip everything but the first line from a stringbuf. */
556static void
557stringbuf_first_line_only(svn_stringbuf_t *buffer)
558{
559  char *eol = strchr(buffer->data, '\n');
560  if (eol)
561    {
562      *eol = '\0';
563      buffer->len = 1 + eol - buffer->data;
564    }
565  svn_stringbuf_strip_whitespace(buffer);
566}
567
568/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
569static const char *
570redhat_release(apr_pool_t *pool)
571{
572  svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
573  if (buffer)
574    {
575      stringbuf_first_line_only(buffer);
576      return buffer->data;
577    }
578  return NULL;
579}
580
581/* Look at /etc/SuSE-release to detect non-LSB SuSE. */
582static const char *
583suse_release(apr_pool_t *pool)
584{
585  const char *release = NULL;
586  const char *codename = NULL;
587
588  svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
589  svn_stringbuf_t *line;
590  svn_stream_t *stream;
591  svn_boolean_t eof;
592  svn_error_t *err;
593  if (!buffer)
594      return NULL;
595
596  stream = svn_stream_from_stringbuf(buffer, pool);
597  err = svn_stream_readline(stream, &line, "\n", &eof, pool);
598  if (err || eof)
599    {
600      svn_error_clear(err);
601      return NULL;
602    }
603
604  svn_stringbuf_strip_whitespace(line);
605  release = line->data;
606
607  for (;;)
608    {
609      const char *key;
610
611      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
612      if (err || eof)
613        {
614          svn_error_clear(err);
615          break;
616        }
617
618      key = stringbuf_split_key(line, '=');
619      if (!key)
620        continue;
621
622      if (0 == strncmp(key, "CODENAME", 8))
623        codename = line->data;
624    }
625
626  return apr_psprintf(pool, "%s%s%s%s",
627                      release,
628                      (codename ? " (" : ""),
629                      (codename ? codename : ""),
630                      (codename ? ")" : ""));
631}
632
633/* Look at /etc/debian_version to detect non-LSB Debian. */
634static const char *
635debian_release(apr_pool_t *pool)
636{
637  svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
638  if (!buffer)
639      return NULL;
640
641  stringbuf_first_line_only(buffer);
642  return apr_pstrcat(pool, "Debian ", buffer->data, SVN_VA_NULL);
643}
644
645/* Try to find the Linux distribution name, or return info from uname. */
646static const char *
647linux_release_name(apr_pool_t *pool)
648{
649  const char *uname_release = release_name_from_uname(pool);
650
651  /* Try anything that has /usr/bin/lsb_release.
652     Covers, for example, Debian, Ubuntu and SuSE.  */
653  const char *release_name = lsb_release(pool);
654
655  /* Try the systemd way (covers Arch). */
656  if (!release_name)
657    release_name = systemd_release(pool);
658
659  /* Try RHEL/Fedora/CentOS */
660  if (!release_name)
661    release_name = redhat_release(pool);
662
663  /* Try Non-LSB SuSE */
664  if (!release_name)
665    release_name = suse_release(pool);
666
667  /* Try non-LSB Debian */
668  if (!release_name)
669    release_name = debian_release(pool);
670
671  if (!release_name)
672    return uname_release;
673
674  if (!uname_release)
675    return release_name;
676
677  return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
678}
679
680#if HAVE_ELF_H
681/* Parse a hexadecimal number as a pointer value. */
682static const unsigned char *
683parse_pointer_value(const char *start, const char *limit, char **end)
684{
685  const unsigned char *ptr;
686  const apr_uint64_t val = (apr_uint64_t)apr_strtoi64(start, end, 16);
687
688  if (errno                     /* overflow */
689      || *end == start          /* no valid digits */
690      || *end >= limit)         /* representation too long */
691    return NULL;
692
693  ptr = (const unsigned char*)val;
694  if (val != (apr_uint64_t)ptr)  /* truncated value */
695    return NULL;
696
697  return ptr;
698}
699
700/* Read the ELF header at the mapping position to check if this is a shared
701   library. We only look at the ELF identification and the type. The format is
702   described here:
703       http://www.skyfree.org/linux/references/ELF_Format.pdf
704*/
705static svn_boolean_t
706check_elf_header(const unsigned char *map_start,
707                 const unsigned char *map_end)
708{
709  /* A union of all known ELF header types, for size checks. */
710  union max_elf_header_size_t
711  {
712    Elf32_Ehdr header_32;
713    Elf64_Ehdr header_64;
714  };
715
716  /* Check the size of the mapping and the ELF magic tag. */
717  if (map_end < map_start
718      || map_end - map_start < sizeof(union max_elf_header_size_t)
719      || memcmp(map_start, ELFMAG, SELFMAG))
720    {
721      return FALSE;
722    }
723
724  /* Check that this is an ELF shared library or executable file. This also
725     implicitly checks that the data encoding of the current process is the
726     same as in the loaded library. */
727  if (map_start[EI_CLASS] == ELFCLASS32)
728    {
729      const Elf32_Ehdr *hdr = (void*)map_start;
730      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
731    }
732  else if (map_start[EI_CLASS] == ELFCLASS64)
733    {
734      const Elf64_Ehdr *hdr = (void*)map_start;
735      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
736    }
737
738  return FALSE;
739}
740#endif  /* HAVE_ELF_H */
741
742static const apr_array_header_t *
743linux_shared_libs(apr_pool_t *pool)
744{
745  /* Read the list of loaded modules from /proc/[pid]/maps
746    The format is described here:
747        http://man7.org/linux/man-pages/man5/proc.5.html
748  */
749
750  const char *maps = apr_psprintf(pool, "/proc/%ld/maps", (long)getpid());
751  apr_array_header_t *result = NULL;
752  svn_boolean_t eof = FALSE;
753  svn_stream_t *stream;
754  svn_error_t *err;
755
756  err = svn_stream_open_readonly(&stream, maps, pool, pool);
757  if (err)
758    {
759      svn_error_clear(err);
760      return NULL;
761    }
762
763  /* Each line in /proc/[pid]/maps consists of whitespace-delimited fields. */
764  while (!eof)
765    {
766      svn_stringbuf_t *line;
767
768#if HAVE_ELF_H
769      const unsigned char *map_start;
770      const unsigned char *map_end;
771#endif
772
773      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
774      if (err)
775        {
776          svn_error_clear(err);
777          return NULL;
778        }
779
780#if HAVE_ELF_H
781      /* Address: The mapped memory address range. */
782      {
783        const char *const limit = line->data + line->len;
784        char *end;
785
786        /* The start of the address range */
787        map_start = parse_pointer_value(line->data, limit, &end);
788        if (!map_start || *end != '-')
789          continue;
790
791        /* The end of the address range */
792        map_end = parse_pointer_value(end + 1, limit, &end);
793        if (!map_end || !svn_ctype_isspace(*end))
794          continue;
795      }
796#endif
797
798      stringbuf_skip_whitespace_field(line); /* skip address */
799
800      /* Permissions: The memory region must be readable and executable. */
801      if (line->len < 4 || line->data[0] != 'r' || line->data[2] != 'x')
802        continue;
803
804      stringbuf_skip_whitespace_field(line); /* skip perms */
805      stringbuf_skip_whitespace_field(line); /* skip offset */
806      stringbuf_skip_whitespace_field(line); /* skip device */
807
808      /* I-Node: If it is 0, there is no file associated with the region. */
809      if (line->len < 2
810          || (line->data[0] == '0' && svn_ctype_isspace(line->data[1])))
811        continue;
812
813      stringbuf_skip_whitespace_field(line); /* skip inode */
814
815      /* Consider only things that look like absolute paths.
816         Files that were removed since the process was created (due to an
817         upgrade, for example) are marked as '(deleted)'. */
818      if (line->data[0] == '/')
819        {
820          svn_version_ext_loaded_lib_t *lib;
821
822#if HAVE_ELF_H
823          if (!check_elf_header(map_start, map_end))
824            continue;
825#endif
826
827          /* We've done our best to find a mapped shared library. */
828          if (!result)
829            {
830              result = apr_array_make(pool, 32, sizeof(*lib));
831            }
832          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
833          lib->name = line->data;
834          lib->version = NULL;
835        }
836    }
837
838  svn_error_clear(svn_stream_close(stream));
839  return result;
840}
841#endif /* __linux__ */
842
843
844#ifdef WIN32
845typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
846typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);
847
848svn_boolean_t
849svn_sysinfo___fill_windows_version(OSVERSIONINFOEXW *version_info)
850{
851  memset(version_info, 0, sizeof(*version_info));
852
853  version_info->dwOSVersionInfoSize = sizeof(*version_info);
854
855  /* Kill warnings with the Windows 8 and later platform SDK */
856#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
857  /* Windows 8 deprecated the API to retrieve the Windows version to avoid
858     backwards compatibility problems... It might return a constant version
859     in future Windows versions... But let's kill the warning.
860
861     We can implementation this using a different function later. */
862#pragma warning(push)
863#pragma warning(disable: 4996)
864#endif
865
866  /* Prototype supports OSVERSIONINFO */
867  return GetVersionExW((LPVOID)version_info);
868#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
869#pragma warning(pop)
870#pragma warning(disable: 4996)
871#endif
872}
873
874/* Get system info, and try to tell the difference between the native
875   system type and the runtime environment of the current process.
876   Populate results in SYSINFO and LOCAL_SYSINFO (optional). */
877static BOOL
878system_info(SYSTEM_INFO *sysinfo,
879            SYSTEM_INFO *local_sysinfo)
880{
881  FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
882    GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetNativeSystemInfo");
883
884  memset(sysinfo, 0, sizeof *sysinfo);
885  if (local_sysinfo)
886    {
887      memset(local_sysinfo, 0, sizeof *local_sysinfo);
888      GetSystemInfo(local_sysinfo);
889      if (GetNativeSystemInfo_)
890        GetNativeSystemInfo_(sysinfo);
891      else
892        memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
893    }
894  else
895    GetSystemInfo(sysinfo);
896
897  return TRUE;
898}
899
900/* Map the proccessor type from SYSINFO to a string. */
901static const char *
902processor_name(SYSTEM_INFO *sysinfo)
903{
904  switch (sysinfo->wProcessorArchitecture)
905    {
906    case PROCESSOR_ARCHITECTURE_AMD64:         return "x86_64";
907    case PROCESSOR_ARCHITECTURE_IA64:          return "ia64";
908    case PROCESSOR_ARCHITECTURE_INTEL:         return "x86";
909    case PROCESSOR_ARCHITECTURE_MIPS:          return "mips";
910    case PROCESSOR_ARCHITECTURE_ALPHA:         return "alpha32";
911    case PROCESSOR_ARCHITECTURE_PPC:           return "powerpc";
912    case PROCESSOR_ARCHITECTURE_SHX:           return "shx";
913    case PROCESSOR_ARCHITECTURE_ARM:           return "arm";
914    case PROCESSOR_ARCHITECTURE_ALPHA64:       return "alpha";
915    case PROCESSOR_ARCHITECTURE_MSIL:          return "msil";
916    case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
917    default: return "unknown";
918    }
919}
920
921/* Return the Windows-specific canonical host name. */
922static const char *
923win32_canonical_host(apr_pool_t *pool)
924{
925  SYSTEM_INFO sysinfo;
926  SYSTEM_INFO local_sysinfo;
927  OSVERSIONINFOEXW osinfo;
928
929  if (system_info(&sysinfo, &local_sysinfo)
930      && svn_sysinfo___fill_windows_version(&osinfo))
931    {
932      const char *arch = processor_name(&local_sysinfo);
933      const char *machine = processor_name(&sysinfo);
934      const char *vendor = "microsoft";
935      const char *sysname = "windows";
936      const char *sysver = apr_psprintf(pool, "%u.%u.%u",
937                                        (unsigned int)osinfo.dwMajorVersion,
938                                        (unsigned int)osinfo.dwMinorVersion,
939                                        (unsigned int)osinfo.dwBuildNumber);
940
941      if (sysinfo.wProcessorArchitecture
942          == local_sysinfo.wProcessorArchitecture)
943        return apr_psprintf(pool, "%s-%s-%s%s",
944                            machine, vendor, sysname, sysver);
945      return apr_psprintf(pool, "%s/%s-%s-%s%s",
946                          arch, machine, vendor, sysname, sysver);
947    }
948
949  return "unknown-microsoft-windows";
950}
951
952/* Convert a Unicode string to UTF-8. */
953static char *
954wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
955{
956  const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
957                                          NULL, 0, NULL, NULL);
958  if (bufsize > 0)
959    {
960      char *const utf8 = apr_palloc(pool, bufsize + 1);
961      WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
962      return utf8;
963    }
964  return NULL;
965}
966
967/* Query the value called NAME of the registry key HKEY. */
968static char *
969registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
970{
971  DWORD size;
972  wchar_t *value;
973
974  if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
975    return NULL;
976
977  value = apr_palloc(pool, size + sizeof *value);
978  if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
979    return NULL;
980  value[size / sizeof *value] = 0;
981  return wcs_to_utf8(value, pool);
982}
983
984/* Try to glean the Windows release name and associated info from the
985   registry. Failing that, construct a release name from the version
986   info. */
987static const char *
988win32_release_name(apr_pool_t *pool)
989{
990  SYSTEM_INFO sysinfo;
991  OSVERSIONINFOEXW osinfo;
992  HKEY hkcv;
993
994  if (!system_info(&sysinfo, NULL)
995      || !svn_sysinfo___fill_windows_version(&osinfo))
996    return NULL;
997
998  if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
999                     L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
1000                     0, KEY_QUERY_VALUE, &hkcv))
1001    {
1002      const char *release = registry_value(hkcv, L"ProductName", pool);
1003      const char *spack = registry_value(hkcv, L"CSDVersion", pool);
1004      const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
1005      const char *curtype = registry_value(hkcv, L"CurrentType", pool);
1006      const char *install = registry_value(hkcv, L"InstallationType", pool);
1007      const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
1008
1009      if (!spack && *osinfo.szCSDVersion)
1010        spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1011
1012      if (!curbuild)
1013        curbuild = registry_value(hkcv, L"CurrentBuild", pool);
1014
1015      if (release || spack || curver || curtype || curbuild)
1016        {
1017          const char *bootinfo = "";
1018          if (curver || install || curtype)
1019            {
1020              bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
1021                                      (curver ? curver : ""),
1022                                      (install ? (curver ? " " : "") : ""),
1023                                      (install ? install : ""),
1024                                      (curtype
1025                                       ? (curver||install ? " " : "")
1026                                       : ""),
1027                                      (curtype ? curtype : ""));
1028            }
1029
1030          return apr_psprintf(pool, "%s%s%s%s%s%s%s",
1031                              (release ? release : ""),
1032                              (spack ? (release ? ", " : "") : ""),
1033                              (spack ? spack : ""),
1034                              (curbuild
1035                               ? (release||spack ? ", build " : "build ")
1036                               : ""),
1037                              (curbuild ? curbuild : ""),
1038                              (bootinfo
1039                               ? (release||spack||curbuild ? " " : "")
1040                               : ""),
1041                              (bootinfo ? bootinfo : ""));
1042        }
1043    }
1044
1045  if (*osinfo.szCSDVersion)
1046    {
1047      const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1048
1049      if (servicepack)
1050        return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
1051                            (unsigned int)osinfo.dwMajorVersion,
1052                            (unsigned int)osinfo.dwMinorVersion,
1053                            servicepack,
1054                            (unsigned int)osinfo.dwBuildNumber);
1055
1056      /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
1057      if (osinfo.wServicePackMinor)
1058        return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
1059                            (unsigned int)osinfo.dwMajorVersion,
1060                            (unsigned int)osinfo.dwMinorVersion,
1061                            (unsigned int)osinfo.wServicePackMajor,
1062                            (unsigned int)osinfo.wServicePackMinor,
1063                            (unsigned int)osinfo.dwBuildNumber);
1064
1065      return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
1066                          (unsigned int)osinfo.dwMajorVersion,
1067                          (unsigned int)osinfo.dwMinorVersion,
1068                          (unsigned int)osinfo.wServicePackMajor,
1069                          (unsigned int)osinfo.dwBuildNumber);
1070    }
1071
1072  return apr_psprintf(pool, "Windows NT %u.%u, build %u",
1073                      (unsigned int)osinfo.dwMajorVersion,
1074                      (unsigned int)osinfo.dwMinorVersion,
1075                      (unsigned int)osinfo.dwBuildNumber);
1076}
1077
1078
1079/* Get a list of handles of shared libs loaded by the current
1080   process. Returns a NULL-terminated array alocated from POOL. */
1081static HMODULE *
1082enum_loaded_modules(apr_pool_t *pool)
1083{
1084  HMODULE psapi_dll = 0;
1085  HANDLE current = GetCurrentProcess();
1086  HMODULE dummy[1];
1087  HMODULE *handles;
1088  DWORD size;
1089  FNENUMPROCESSMODULES EnumProcessModules_;
1090
1091  psapi_dll = GetModuleHandleW(L"psapi.dll");
1092
1093  if (!psapi_dll)
1094    {
1095      /* Load and never unload, just like static linking */
1096      psapi_dll = LoadLibraryW(L"psapi.dll");
1097    }
1098
1099  if (!psapi_dll)
1100      return NULL;
1101
1102  EnumProcessModules_ = (FNENUMPROCESSMODULES)
1103                              GetProcAddress(psapi_dll, "EnumProcessModules");
1104
1105  /* Before Windows XP psapi was an optional module */
1106  if (! EnumProcessModules_)
1107    return NULL;
1108
1109  if (!EnumProcessModules_(current, dummy, sizeof(dummy), &size))
1110    return NULL;
1111
1112  handles = apr_palloc(pool, size + sizeof *handles);
1113  if (! EnumProcessModules_(current, handles, size, &size))
1114    return NULL;
1115  handles[size / sizeof *handles] = NULL;
1116  return handles;
1117}
1118
1119/* Find the version number, if any, embedded in FILENAME. */
1120static const char *
1121file_version_number(const wchar_t *filename, apr_pool_t *pool)
1122{
1123  VS_FIXEDFILEINFO info;
1124  unsigned int major, minor, micro, nano;
1125  void *data;
1126  DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
1127  void *vinfo;
1128  UINT vinfo_size;
1129
1130  if (!data_size)
1131    return NULL;
1132
1133  data = apr_palloc(pool, data_size);
1134  if (!GetFileVersionInfoW(filename, 0, data_size, data))
1135    return NULL;
1136
1137  if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
1138    return NULL;
1139
1140  if (vinfo_size != sizeof info)
1141    return NULL;
1142
1143  memcpy(&info, vinfo, sizeof info);
1144  major = (info.dwFileVersionMS >> 16) & 0xFFFF;
1145  minor = info.dwFileVersionMS & 0xFFFF;
1146  micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
1147  nano = info.dwFileVersionLS & 0xFFFF;
1148
1149  if (!nano)
1150    {
1151      if (!micro)
1152        return apr_psprintf(pool, "%u.%u", major, minor);
1153      else
1154        return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
1155    }
1156  return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
1157}
1158
1159/* List the shared libraries loaded by the current process. */
1160static const apr_array_header_t *
1161win32_shared_libs(apr_pool_t *pool)
1162{
1163  apr_array_header_t *array = NULL;
1164  wchar_t buffer[MAX_PATH + 1];
1165  HMODULE *handles = enum_loaded_modules(pool);
1166  HMODULE *module;
1167
1168  for (module = handles; module && *module; ++module)
1169    {
1170      const char *filename;
1171      const char *version;
1172      if (GetModuleFileNameW(*module, buffer, MAX_PATH))
1173        {
1174          buffer[MAX_PATH] = 0;
1175
1176          version = file_version_number(buffer, pool);
1177          filename = wcs_to_utf8(buffer, pool);
1178          if (filename)
1179            {
1180              svn_version_ext_loaded_lib_t *lib;
1181
1182              if (!array)
1183                {
1184                  array = apr_array_make(pool, 32, sizeof(*lib));
1185                }
1186              lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
1187              lib->name = svn_dirent_local_style(filename, pool);
1188              lib->version = version;
1189            }
1190        }
1191    }
1192
1193  return array;
1194}
1195#endif /* WIN32 */
1196
1197
1198#ifdef SVN_HAVE_MACOS_PLIST
1199/* implements svn_write_fn_t to copy the data into a CFMutableDataRef that's
1200 * in the baton. */
1201static svn_error_t *
1202write_to_cfmutabledata(void *baton, const char *data, apr_size_t *len)
1203{
1204  CFMutableDataRef *resource = (CFMutableDataRef *) baton;
1205
1206  CFDataAppendBytes(*resource, (UInt8 *)data, *len);
1207
1208  return SVN_NO_ERROR;
1209}
1210
1211/* Load the SystemVersion.plist or ServerVersion.plist file into a
1212   property list. Set SERVER to TRUE if the file read was
1213   ServerVersion.plist. */
1214static CFDictionaryRef
1215system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
1216{
1217  static const char server_version[] =
1218    "/System/Library/CoreServices/ServerVersion.plist";
1219  static const char system_version[] =
1220    "/System/Library/CoreServices/SystemVersion.plist";
1221  svn_stream_t *read_stream, *write_stream;
1222  svn_error_t *err;
1223  CFPropertyListRef plist = NULL;
1224  CFMutableDataRef resource = CFDataCreateMutable(kCFAllocatorDefault, 0);
1225
1226  /* failed getting the CFMutableDataRef, shouldn't happen */
1227  if (!resource)
1228    return NULL;
1229
1230  /* Try to open the plist files to get the data */
1231  err = svn_stream_open_readonly(&read_stream, server_version, pool, pool);
1232  if (err)
1233    {
1234      if (!APR_STATUS_IS_ENOENT(err->apr_err))
1235        {
1236          svn_error_clear(err);
1237          CFRelease(resource);
1238          return NULL;
1239        }
1240      else
1241        {
1242          svn_error_clear(err);
1243          err = svn_stream_open_readonly(&read_stream, system_version,
1244                                         pool, pool);
1245          if (err)
1246            {
1247              svn_error_clear(err);
1248              CFRelease(resource);
1249              return NULL;
1250            }
1251
1252          *server = FALSE;
1253        }
1254    }
1255  else
1256    {
1257      *server = TRUE;
1258    }
1259
1260  /* copy the data onto the CFMutableDataRef to allow us to provide it to
1261   * the CoreFoundation functions that parse proprerty lists */
1262  write_stream = svn_stream_create(&resource, pool);
1263  svn_stream_set_write(write_stream, write_to_cfmutabledata);
1264  err = svn_stream_copy3(read_stream, write_stream, NULL, NULL, pool);
1265  if (err)
1266    {
1267      svn_error_clear(err);
1268      return NULL;
1269    }
1270
1271#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1272  /* This function is only available from Mac OS 10.6 onward. */
1273  plist = CFPropertyListCreateWithData(kCFAllocatorDefault, resource,
1274                                       kCFPropertyListImmutable,
1275                                       NULL, NULL);
1276#else  /* Mac OS 10.5 or earlier */
1277  /* This function obsolete and deprecated since Mac OS 10.10. */
1278  plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
1279                                          kCFPropertyListImmutable,
1280                                          NULL);
1281#endif /* MAC_OS_X_VERSION_10_6 */
1282
1283  if (resource)
1284    CFRelease(resource);
1285
1286  if (!plist)
1287    return NULL;
1288
1289  if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
1290    {
1291      /* Oops ... this really should be a dict. */
1292      CFRelease(plist);
1293      return NULL;
1294    }
1295
1296  return plist;
1297}
1298
1299/* Return the value for KEY from PLIST, or NULL if not available. */
1300static const char *
1301value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
1302{
1303  CFStringRef valref;
1304  CFIndex bufsize;
1305  const void *valptr;
1306  const char *value;
1307
1308  if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
1309    return NULL;
1310
1311  valref = valptr;
1312  if (CFStringGetTypeID() != CFGetTypeID(valref))
1313    return NULL;
1314
1315  value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
1316  if (value)
1317    return apr_pstrdup(pool, value);
1318
1319  bufsize =  5 * CFStringGetLength(valref) + 1;
1320  value = apr_palloc(pool, bufsize);
1321  if (!CFStringGetCString(valref, (char*)value, bufsize,
1322                          kCFStringEncodingUTF8))
1323    value = NULL;
1324
1325  return value;
1326}
1327
1328/* Return the minor version the operating system, given the number in
1329   a format that matches the regular expression /^10\.\d+(\..*)?$/ */
1330static int
1331macos_minor_version(const char *osver)
1332{
1333  char *end = NULL;
1334  unsigned long num = strtoul(osver, &end, 10);
1335
1336  if (!end || *end != '.' || num != 10)
1337    return -1;
1338
1339  osver = end + 1;
1340  end = NULL;
1341  num = strtoul(osver, &end, 10);
1342  if (!end || (*end && *end != '.'))
1343    return -1;
1344
1345  return (int)num;
1346}
1347
1348/* Return the product name of the operating system. */
1349static const char *
1350product_name_from_minor_version(int minor, const char* product_name)
1351{
1352  /* We can only do this if we know the official product name. */
1353  if (0 != strcmp(product_name, "Mac OS X"))
1354    return product_name;
1355
1356  if (minor <= 7)
1357    return product_name;
1358
1359  if (minor <= 11)
1360    return "OS X";
1361
1362  return "macOS";
1363}
1364
1365/* Return the commercial name of the operating system. */
1366static const char *
1367release_name_from_minor_version(int minor, const char* product_name)
1368{
1369  /* We can only do this if we know the official product name. */
1370  if (0 == strcmp(product_name, "Mac OS X"))
1371    {
1372      /* See https://en.wikipedia.org/wiki/MacOS_version_history#Releases */
1373      switch(minor)
1374        {
1375        case  0: return "Cheetah";
1376        case  1: return "Puma";
1377        case  2: return "Jaguar";
1378        case  3: return "Panther";
1379        case  4: return "Tiger";
1380        case  5: return "Leopard";
1381        case  6: return "Snow Leopard";
1382        case  7: return "Lion";
1383        case  8: return "Mountain Lion";
1384        case  9: return "Mavericks";
1385        case 10: return "Yosemite";
1386        case 11: return "El Capitan";
1387        case 12: return "Sierra";
1388        case 13: return "High Sierra";
1389        case 14: return "Mojave";
1390        case 15: return "Catalina";
1391        }
1392    }
1393  return NULL;
1394}
1395
1396/* Construct the release name from information stored in the Mac OS X
1397   "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
1398   Server. */
1399static const char *
1400macos_release_name(apr_pool_t *pool)
1401{
1402  svn_boolean_t server;
1403  CFDictionaryRef plist = system_version_plist(&server, pool);
1404
1405  if (plist)
1406    {
1407      const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
1408      const char *osver = value_from_dict(plist,
1409                                          CFSTR("ProductUserVisibleVersion"),
1410                                          pool);
1411      const char *build = value_from_dict(plist,
1412                                          CFSTR("ProductBuildVersion"),
1413                                          pool);
1414      const char *release;
1415      int minor_version;
1416
1417      if (!osver)
1418        osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
1419      minor_version = macos_minor_version(osver);
1420      release = release_name_from_minor_version(minor_version, osname);
1421      osname = product_name_from_minor_version(minor_version, osname);
1422
1423      CFRelease(plist);
1424      return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
1425                          (osname ? osname : ""),
1426                          (release ? (osname ? " " : "") : ""),
1427                          (release ? release : ""),
1428                          (osver ? (osname||release ? " " : "") : ""),
1429                          (osver ? osver : ""),
1430                          (build
1431                           ? (osname||release||osver ? ", " : "")
1432                           : ""),
1433                          (build
1434                           ? (server ? "server build " : "build ")
1435                           : ""),
1436                          (build ? build : ""));
1437    }
1438
1439  return NULL;
1440}
1441#endif  /* SVN_HAVE_MACOS_PLIST */
1442
1443#ifdef SVN_HAVE_MACHO_ITERATE
1444/* List the shared libraries loaded by the current process.
1445   Ignore frameworks and system libraries, they're just clutter. */
1446static const apr_array_header_t *
1447macos_shared_libs(apr_pool_t *pool)
1448{
1449  static const char slb_prefix[] = "/usr/lib/system/";
1450  static const char fwk_prefix[] = "/System/Library/Frameworks/";
1451  static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
1452
1453  const size_t slb_prefix_len = strlen(slb_prefix);
1454  const size_t fwk_prefix_len = strlen(fwk_prefix);
1455  const size_t pfk_prefix_len = strlen(pfk_prefix);
1456
1457  apr_array_header_t *result = NULL;
1458  apr_array_header_t *dylibs = NULL;
1459
1460  uint32_t i;
1461  for (i = 0;; ++i)
1462    {
1463      const struct mach_header *header = _dyld_get_image_header(i);
1464      const char *filename = _dyld_get_image_name(i);
1465      const char *version;
1466      char *truename;
1467      svn_version_ext_loaded_lib_t *lib;
1468
1469      if (!(header && filename))
1470        break;
1471
1472      switch (header->cputype)
1473        {
1474        case CPU_TYPE_I386:      version = _("Intel"); break;
1475        case CPU_TYPE_X86_64:    version = _("Intel 64-bit"); break;
1476        case CPU_TYPE_POWERPC:   version = _("PowerPC"); break;
1477        case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
1478        default:
1479          version = NULL;
1480        }
1481
1482      if (0 == apr_filepath_merge(&truename, "", filename,
1483                                  APR_FILEPATH_NATIVE
1484                                  | APR_FILEPATH_TRUENAME,
1485                                  pool))
1486        filename = truename;
1487      else
1488        filename = apr_pstrdup(pool, filename);
1489
1490      if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
1491          || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
1492          || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
1493        {
1494          /* Ignore frameworks and system libraries. */
1495          continue;
1496        }
1497
1498      if (header->filetype == MH_EXECUTE)
1499        {
1500          /* Make sure the program filename is first in the list */
1501          if (!result)
1502            {
1503              result = apr_array_make(pool, 32, sizeof(*lib));
1504            }
1505          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
1506        }
1507      else
1508        {
1509          if (!dylibs)
1510            {
1511              dylibs = apr_array_make(pool, 32, sizeof(*lib));
1512            }
1513          lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
1514        }
1515
1516      lib->name = filename;
1517      lib->version = version;
1518    }
1519
1520  /* Gather results into one array. */
1521  if (dylibs)
1522    {
1523      if (result)
1524        apr_array_cat(result, dylibs);
1525      else
1526        result = dylibs;
1527    }
1528
1529  return result;
1530}
1531#endif  /* SVN_HAVE_MACHO_ITERATE */
1532