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
85 static const char *canonical_host_from_uname(apr_pool_t *pool);
86 # ifndef SVN_HAVE_MACOS_PLIST
87 static const char *release_name_from_uname(apr_pool_t *pool);
88 # endif
89 #endif
90 
91 #ifdef WIN32
92 static const char *win32_canonical_host(apr_pool_t *pool);
93 static const char *win32_release_name(apr_pool_t *pool);
94 static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
95 #endif /* WIN32 */
96 
97 #ifdef SVN_HAVE_MACOS_PLIST
98 static const char *macos_release_name(apr_pool_t *pool);
99 #endif
100 
101 #ifdef SVN_HAVE_MACHO_ITERATE
102 static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
103 #endif
104 
105 
106 #if __linux__
107 static const char *linux_release_name(apr_pool_t *pool);
108 static const apr_array_header_t *linux_shared_libs(apr_pool_t *pool);
109 #endif
110 
111 const char *
svn_sysinfo__canonical_host(apr_pool_t * pool)112 svn_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 
124 const char *
svn_sysinfo__release_name(apr_pool_t * pool)125 svn_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 
140 const apr_array_header_t *
svn_sysinfo__linked_libs(apr_pool_t * pool)141 svn_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 
197 const apr_array_header_t *
svn_sysinfo__loaded_libs(apr_pool_t * pool)198 svn_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
213 static const char*
canonical_host_from_uname(apr_pool_t * pool)214 canonical_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`". */
279 static const char *
release_name_from_uname(apr_pool_t * pool)280 release_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. */
321 static apr_size_t
stringbuf_first_whitespace(const svn_stringbuf_t * str)322 stringbuf_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. */
334 static void
stringbuf_skip_whitespace_field(svn_stringbuf_t * str)335 stringbuf_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. */
346 static const char *
stringbuf_split_key(svn_stringbuf_t * buffer,char delim)347 stringbuf_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` */
378 static const char *
lsb_release(apr_pool_t * pool)379 lsb_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  */
484 static const char *
systemd_release(apr_pool_t * pool)485 systemd_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. */
539 static svn_stringbuf_t *
read_file_contents(const char * filename,apr_pool_t * pool)540 read_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. */
556 static void
stringbuf_first_line_only(svn_stringbuf_t * buffer)557 stringbuf_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. */
569 static const char *
redhat_release(apr_pool_t * pool)570 redhat_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. */
582 static const char *
suse_release(apr_pool_t * pool)583 suse_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. */
634 static const char *
debian_release(apr_pool_t * pool)635 debian_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. */
646 static const char *
linux_release_name(apr_pool_t * pool)647 linux_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. */
682 static const unsigned char *
parse_pointer_value(const char * start,const char * limit,char ** end)683 parse_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 */
705 static svn_boolean_t
check_elf_header(const unsigned char * map_start,const unsigned char * map_end)706 check_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 
742 static const apr_array_header_t *
linux_shared_libs(apr_pool_t * pool)743 linux_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
845 typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
846 typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);
847 
848 svn_boolean_t
svn_sysinfo___fill_windows_version(OSVERSIONINFOEXW * version_info)849 svn_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). */
877 static BOOL
system_info(SYSTEM_INFO * sysinfo,SYSTEM_INFO * local_sysinfo)878 system_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. */
901 static const char *
processor_name(SYSTEM_INFO * sysinfo)902 processor_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. */
922 static const char *
win32_canonical_host(apr_pool_t * pool)923 win32_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. */
953 static char *
wcs_to_utf8(const wchar_t * wcs,apr_pool_t * pool)954 wcs_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. */
968 static char *
registry_value(HKEY hkey,wchar_t * name,apr_pool_t * pool)969 registry_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. */
987 static const char *
win32_release_name(apr_pool_t * pool)988 win32_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. */
1081 static HMODULE *
enum_loaded_modules(apr_pool_t * pool)1082 enum_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. */
1120 static const char *
file_version_number(const wchar_t * filename,apr_pool_t * pool)1121 file_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. */
1160 static const apr_array_header_t *
win32_shared_libs(apr_pool_t * pool)1161 win32_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. */
1201 static svn_error_t *
write_to_cfmutabledata(void * baton,const char * data,apr_size_t * len)1202 write_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. */
1214 static CFDictionaryRef
system_version_plist(svn_boolean_t * server,apr_pool_t * pool)1215 system_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. */
1300 static const char *
value_from_dict(CFDictionaryRef plist,CFStringRef key,apr_pool_t * pool)1301 value_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+(\..*)?$/ */
1330 static int
macos_minor_version(const char * osver)1331 macos_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. */
1349 static const char *
product_name_from_minor_version(int minor,const char * product_name)1350 product_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. */
1366 static const char *
release_name_from_minor_version(int minor,const char * product_name)1367 release_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. */
1399 static const char *
macos_release_name(apr_pool_t * pool)1400 macos_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. */
1446 static const apr_array_header_t *
macos_shared_libs(apr_pool_t * pool)1447 macos_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