1362181Sdim
2251881Speter/*
3251881Speter * sysinfo.c :  information about the running system
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter
26251881Speter
27251881Speter#define APR_WANT_STRFUNC
28251881Speter#include <apr_want.h>
29251881Speter
30251881Speter#include <apr_lib.h>
31251881Speter#include <apr_pools.h>
32251881Speter#include <apr_file_info.h>
33251881Speter#include <apr_signal.h>
34251881Speter#include <apr_strings.h>
35251881Speter#include <apr_thread_proc.h>
36251881Speter#include <apr_version.h>
37251881Speter#include <apu_version.h>
38251881Speter
39251881Speter#include "svn_pools.h"
40251881Speter#include "svn_ctype.h"
41251881Speter#include "svn_dirent_uri.h"
42251881Speter#include "svn_error.h"
43251881Speter#include "svn_io.h"
44251881Speter#include "svn_string.h"
45251881Speter#include "svn_utf.h"
46251881Speter#include "svn_version.h"
47251881Speter
48251881Speter#include "private/svn_sqlite.h"
49289180Speter#include "private/svn_subr_private.h"
50289180Speter#include "private/svn_utf_private.h"
51251881Speter
52251881Speter#include "sysinfo.h"
53251881Speter#include "svn_private_config.h"
54251881Speter
55362181Sdim#if HAVE_SYS_TYPES_H
56362181Sdim#include <sys/types.h>
57362181Sdim#endif
58362181Sdim
59251881Speter#if HAVE_SYS_UTSNAME_H
60251881Speter#include <sys/utsname.h>
61251881Speter#endif
62251881Speter
63362181Sdim#if HAVE_UNISTD_H
64362181Sdim#include <unistd.h>
65362181Sdim#endif
66362181Sdim
67362181Sdim#if HAVE_ELF_H
68362181Sdim#include <elf.h>
69362181Sdim#endif
70362181Sdim
71251881Speter#ifdef SVN_HAVE_MACOS_PLIST
72251881Speter#include <CoreFoundation/CoreFoundation.h>
73289180Speter#include <AvailabilityMacros.h>
74289180Speter# ifndef MAC_OS_X_VERSION_10_6
75289180Speter#  define MAC_OS_X_VERSION_10_6  1060
76289180Speter# endif
77251881Speter#endif
78251881Speter
79251881Speter#ifdef SVN_HAVE_MACHO_ITERATE
80251881Speter#include <mach-o/dyld.h>
81251881Speter#include <mach-o/loader.h>
82251881Speter#endif
83251881Speter
84251881Speter#if HAVE_UNAME
85251881Speterstatic const char *canonical_host_from_uname(apr_pool_t *pool);
86251881Speter# ifndef SVN_HAVE_MACOS_PLIST
87251881Speterstatic const char *release_name_from_uname(apr_pool_t *pool);
88251881Speter# endif
89251881Speter#endif
90251881Speter
91251881Speter#ifdef WIN32
92251881Speterstatic const char *win32_canonical_host(apr_pool_t *pool);
93251881Speterstatic const char *win32_release_name(apr_pool_t *pool);
94251881Speterstatic const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
95251881Speter#endif /* WIN32 */
96251881Speter
97251881Speter#ifdef SVN_HAVE_MACOS_PLIST
98251881Speterstatic const char *macos_release_name(apr_pool_t *pool);
99251881Speter#endif
100251881Speter
101251881Speter#ifdef SVN_HAVE_MACHO_ITERATE
102251881Speterstatic const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
103251881Speter#endif
104251881Speter
105251881Speter
106251881Speter#if __linux__
107251881Speterstatic const char *linux_release_name(apr_pool_t *pool);
108362181Sdimstatic const apr_array_header_t *linux_shared_libs(apr_pool_t *pool);
109251881Speter#endif
110251881Speter
111251881Speterconst char *
112251881Spetersvn_sysinfo__canonical_host(apr_pool_t *pool)
113251881Speter{
114251881Speter#ifdef WIN32
115251881Speter  return win32_canonical_host(pool);
116251881Speter#elif HAVE_UNAME
117251881Speter  return canonical_host_from_uname(pool);
118251881Speter#else
119251881Speter  return "unknown-unknown-unknown";
120251881Speter#endif
121251881Speter}
122251881Speter
123251881Speter
124251881Speterconst char *
125251881Spetersvn_sysinfo__release_name(apr_pool_t *pool)
126251881Speter{
127251881Speter#ifdef WIN32
128251881Speter  return win32_release_name(pool);
129251881Speter#elif defined(SVN_HAVE_MACOS_PLIST)
130251881Speter  return macos_release_name(pool);
131251881Speter#elif __linux__
132251881Speter  return linux_release_name(pool);
133251881Speter#elif HAVE_UNAME
134251881Speter  return release_name_from_uname(pool);
135251881Speter#else
136251881Speter  return NULL;
137251881Speter#endif
138251881Speter}
139251881Speter
140251881Speterconst apr_array_header_t *
141251881Spetersvn_sysinfo__linked_libs(apr_pool_t *pool)
142251881Speter{
143251881Speter  svn_version_ext_linked_lib_t *lib;
144362181Sdim  apr_array_header_t *array = apr_array_make(pool, 7, sizeof(*lib));
145362181Sdim  int lz4_version = svn_lz4__runtime_version();
146251881Speter
147251881Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
148251881Speter  lib->name = "APR";
149251881Speter  lib->compiled_version = APR_VERSION_STRING;
150251881Speter  lib->runtime_version = apr_pstrdup(pool, apr_version_string());
151251881Speter
152251881Speter/* Don't list APR-Util if it isn't linked in, which it may not be if
153251881Speter * we're using APR 2.x+ which combined APR-Util into APR. */
154251881Speter#ifdef APU_VERSION_STRING
155251881Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
156251881Speter  lib->name = "APR-Util";
157251881Speter  lib->compiled_version = APU_VERSION_STRING;
158251881Speter  lib->runtime_version = apr_pstrdup(pool, apu_version_string());
159251881Speter#endif
160251881Speter
161251881Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
162289180Speter  lib->name = "Expat";
163289180Speter  lib->compiled_version = apr_pstrdup(pool, svn_xml__compiled_version());
164289180Speter  lib->runtime_version = apr_pstrdup(pool, svn_xml__runtime_version());
165289180Speter
166289180Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
167251881Speter  lib->name = "SQLite";
168251881Speter  lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
169251881Speter#ifdef SVN_SQLITE_INLINE
170251881Speter  lib->runtime_version = NULL;
171251881Speter#else
172251881Speter  lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
173251881Speter#endif
174251881Speter
175289180Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
176289180Speter  lib->name = "Utf8proc";
177289180Speter  lib->compiled_version = apr_pstrdup(pool, svn_utf__utf8proc_compiled_version());
178289180Speter  lib->runtime_version = apr_pstrdup(pool, svn_utf__utf8proc_runtime_version());
179289180Speter
180289180Speter  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
181289180Speter  lib->name = "ZLib";
182289180Speter  lib->compiled_version = apr_pstrdup(pool, svn_zlib__compiled_version());
183289180Speter  lib->runtime_version = apr_pstrdup(pool, svn_zlib__runtime_version());
184289180Speter
185362181Sdim  lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
186362181Sdim  lib->name = "LZ4";
187362181Sdim  lib->compiled_version = apr_pstrdup(pool, svn_lz4__compiled_version());
188362181Sdim
189362181Sdim  lib->runtime_version = apr_psprintf(pool, "%d.%d.%d",
190362181Sdim                                      lz4_version / 100 / 100,
191362181Sdim                                      (lz4_version / 100) % 100,
192362181Sdim                                      lz4_version % 100);
193362181Sdim
194251881Speter  return array;
195251881Speter}
196251881Speter
197251881Speterconst apr_array_header_t *
198251881Spetersvn_sysinfo__loaded_libs(apr_pool_t *pool)
199251881Speter{
200251881Speter#ifdef WIN32
201251881Speter  return win32_shared_libs(pool);
202251881Speter#elif defined(SVN_HAVE_MACHO_ITERATE)
203251881Speter  return macos_shared_libs(pool);
204362181Sdim#elif __linux__
205362181Sdim  return linux_shared_libs(pool);
206251881Speter#else
207251881Speter  return NULL;
208251881Speter#endif
209251881Speter}
210251881Speter
211251881Speter
212251881Speter#if HAVE_UNAME
213251881Speterstatic const char*
214251881Spetercanonical_host_from_uname(apr_pool_t *pool)
215251881Speter{
216251881Speter  const char *machine = "unknown";
217251881Speter  const char *vendor = "unknown";
218251881Speter  const char *sysname = "unknown";
219251881Speter  const char *sysver = "";
220251881Speter  struct utsname info;
221251881Speter
222251881Speter  if (0 <= uname(&info))
223251881Speter    {
224251881Speter      svn_error_t *err;
225251881Speter      const char *tmp;
226251881Speter
227251881Speter      err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
228251881Speter      if (err)
229251881Speter        svn_error_clear(err);
230251881Speter      else
231251881Speter        machine = tmp;
232251881Speter
233251881Speter      err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
234251881Speter      if (err)
235251881Speter        svn_error_clear(err);
236251881Speter      else
237251881Speter        {
238251881Speter          char *lwr = apr_pstrdup(pool, tmp);
239251881Speter          char *it = lwr;
240251881Speter          while (*it)
241251881Speter            {
242251881Speter              if (svn_ctype_isupper(*it))
243251881Speter                *it = apr_tolower(*it);
244251881Speter              ++it;
245251881Speter            }
246251881Speter          sysname = lwr;
247251881Speter        }
248251881Speter
249251881Speter      if (0 == strcmp(sysname, "darwin"))
250251881Speter        vendor = "apple";
251251881Speter      if (0 == strcmp(sysname, "linux"))
252251881Speter        sysver = "-gnu";
253251881Speter      else
254251881Speter        {
255251881Speter          err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
256251881Speter          if (err)
257251881Speter            svn_error_clear(err);
258251881Speter          else
259251881Speter            {
260251881Speter              apr_size_t n = strspn(tmp, ".0123456789");
261251881Speter              if (n > 0)
262251881Speter                {
263251881Speter                  char *ver = apr_pstrdup(pool, tmp);
264251881Speter                  ver[n] = 0;
265251881Speter                  sysver = ver;
266251881Speter                }
267251881Speter              else
268251881Speter                sysver = tmp;
269251881Speter            }
270251881Speter        }
271251881Speter    }
272251881Speter
273251881Speter  return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
274251881Speter}
275251881Speter
276251881Speter# ifndef SVN_HAVE_MACOS_PLIST
277251881Speter/* Generate a release name from the uname(3) info, effectively
278251881Speter   returning "`uname -s` `uname -r`". */
279251881Speterstatic const char *
280251881Speterrelease_name_from_uname(apr_pool_t *pool)
281251881Speter{
282251881Speter  struct utsname info;
283251881Speter  if (0 <= uname(&info))
284251881Speter    {
285251881Speter      svn_error_t *err;
286251881Speter      const char *sysname;
287251881Speter      const char *sysver;
288251881Speter
289251881Speter      err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
290251881Speter      if (err)
291251881Speter        {
292251881Speter          sysname = NULL;
293251881Speter          svn_error_clear(err);
294251881Speter        }
295251881Speter
296251881Speter
297251881Speter      err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
298251881Speter      if (err)
299251881Speter        {
300251881Speter          sysver = NULL;
301251881Speter          svn_error_clear(err);
302251881Speter        }
303251881Speter
304251881Speter      if (sysname || sysver)
305251881Speter        {
306251881Speter          return apr_psprintf(pool, "%s%s%s",
307251881Speter                              (sysname ? sysname : ""),
308251881Speter                              (sysver ? (sysname ? " " : "") : ""),
309251881Speter                              (sysver ? sysver : ""));
310251881Speter        }
311251881Speter    }
312251881Speter  return NULL;
313251881Speter}
314251881Speter# endif  /* !SVN_HAVE_MACOS_PLIST */
315251881Speter#endif  /* HAVE_UNAME */
316251881Speter
317251881Speter
318251881Speter#if __linux__
319362181Sdim/* Find the first whitespace character in a stringbuf.
320362181Sdim   Analogous to svn_stringbuf_first_non_whitespace. */
321362181Sdimstatic apr_size_t
322362181Sdimstringbuf_first_whitespace(const svn_stringbuf_t *str)
323362181Sdim{
324362181Sdim  apr_size_t i;
325362181Sdim  for (i = 0; i < str->len; ++i)
326362181Sdim    {
327362181Sdim      if (svn_ctype_isspace(str->data[i]))
328362181Sdim        return i;
329362181Sdim    }
330362181Sdim  return str->len;
331362181Sdim}
332362181Sdim
333362181Sdim/* Skip a whitespace-delimited field in a stringbuf. */
334362181Sdimstatic void
335362181Sdimstringbuf_skip_whitespace_field(svn_stringbuf_t *str)
336362181Sdim{
337362181Sdim  apr_size_t i;
338362181Sdim  i = stringbuf_first_whitespace(str);
339362181Sdim  svn_stringbuf_leftchop(str, i);
340362181Sdim  i = svn_stringbuf_first_non_whitespace(str);
341362181Sdim  svn_stringbuf_leftchop(str, i);
342362181Sdim}
343362181Sdim
344251881Speter/* Split a stringbuf into a key/value pair.
345289180Speter   Return the key, leaving the stripped value in the stringbuf. */
346251881Speterstatic const char *
347251881Speterstringbuf_split_key(svn_stringbuf_t *buffer, char delim)
348251881Speter{
349251881Speter  char *key;
350251881Speter  char *end;
351251881Speter
352251881Speter  end = strchr(buffer->data, delim);
353251881Speter  if (!end)
354251881Speter    return NULL;
355251881Speter
356251881Speter  svn_stringbuf_strip_whitespace(buffer);
357262250Speter
358262250Speter  /* Now we split the currently allocated buffer in two parts:
359262250Speter      - a const char * HEAD
360262250Speter      - the remaining stringbuf_t. */
361262250Speter
362262250Speter  /* Create HEAD as '\0' terminated const char * */
363251881Speter  key = buffer->data;
364251881Speter  end = strchr(key, delim);
365251881Speter  *end = '\0';
366262250Speter
367262250Speter  /* And update the TAIL to be a smaller, but still valid stringbuf */
368251881Speter  buffer->data = end + 1;
369262250Speter  buffer->len -= 1 + end - key;
370262250Speter  buffer->blocksize -= 1 + end - key;
371262250Speter
372251881Speter  svn_stringbuf_strip_whitespace(buffer);
373251881Speter
374251881Speter  return key;
375251881Speter}
376251881Speter
377251881Speter/* Parse `/usr/bin/lsb_rlease --all` */
378251881Speterstatic const char *
379251881Speterlsb_release(apr_pool_t *pool)
380251881Speter{
381251881Speter  static const char *const args[3] =
382251881Speter    {
383251881Speter      "/usr/bin/lsb_release",
384251881Speter      "--all",
385251881Speter      NULL
386251881Speter    };
387251881Speter
388251881Speter  const char *distributor = NULL;
389251881Speter  const char *description = NULL;
390251881Speter  const char *release = NULL;
391251881Speter  const char *codename = NULL;
392251881Speter
393251881Speter  apr_proc_t lsbproc;
394251881Speter  svn_stream_t *lsbinfo;
395251881Speter  svn_error_t *err;
396251881Speter
397251881Speter  /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
398251881Speter  {
399251881Speter    apr_file_t *stdin_handle;
400251881Speter    apr_file_t *stdout_handle;
401251881Speter
402251881Speter    err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
403251881Speter                           APR_READ, APR_OS_DEFAULT, pool);
404251881Speter    if (!err)
405251881Speter      err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
406251881Speter                             APR_WRITE, APR_OS_DEFAULT, pool);
407251881Speter    if (!err)
408251881Speter      err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
409251881Speter                              FALSE, stdin_handle,
410251881Speter                              TRUE, NULL,
411251881Speter                              FALSE, stdout_handle,
412251881Speter                              pool);
413251881Speter    if (err)
414251881Speter      {
415251881Speter        svn_error_clear(err);
416251881Speter        return NULL;
417251881Speter      }
418251881Speter  }
419251881Speter
420251881Speter  /* Parse the output and try to populate the  */
421251881Speter  lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
422251881Speter  if (lsbinfo)
423251881Speter    {
424251881Speter      for (;;)
425251881Speter        {
426251881Speter          svn_boolean_t eof = FALSE;
427251881Speter          svn_stringbuf_t *line;
428251881Speter          const char *key;
429251881Speter
430251881Speter          err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
431251881Speter          if (err || eof)
432251881Speter            break;
433251881Speter
434251881Speter          key = stringbuf_split_key(line, ':');
435251881Speter          if (!key)
436251881Speter            continue;
437251881Speter
438251881Speter          if (0 == svn_cstring_casecmp(key, "Distributor ID"))
439251881Speter            distributor = line->data;
440251881Speter          else if (0 == svn_cstring_casecmp(key, "Description"))
441251881Speter            description = line->data;
442251881Speter          else if (0 == svn_cstring_casecmp(key, "Release"))
443251881Speter            release = line->data;
444251881Speter          else if (0 == svn_cstring_casecmp(key, "Codename"))
445251881Speter            codename = line->data;
446251881Speter        }
447251881Speter      err = svn_error_compose_create(err,
448251881Speter                                     svn_stream_close(lsbinfo));
449251881Speter      if (err)
450251881Speter        {
451251881Speter          svn_error_clear(err);
452251881Speter          apr_proc_kill(&lsbproc, SIGKILL);
453251881Speter          return NULL;
454251881Speter        }
455251881Speter    }
456251881Speter
457251881Speter  /* Reap the child process */
458251881Speter  err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
459251881Speter  if (err)
460251881Speter    {
461251881Speter      svn_error_clear(err);
462251881Speter      return NULL;
463251881Speter    }
464251881Speter
465251881Speter  if (description)
466251881Speter    return apr_psprintf(pool, "%s%s%s%s", description,
467251881Speter                        (codename ? " (" : ""),
468251881Speter                        (codename ? codename : ""),
469251881Speter                        (codename ? ")" : ""));
470251881Speter  if (distributor)
471251881Speter    return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
472251881Speter                        (release ? " " : ""),
473251881Speter                        (release ? release : ""),
474251881Speter                        (codename ? " (" : ""),
475251881Speter                        (codename ? codename : ""),
476251881Speter                        (codename ? ")" : ""));
477251881Speter
478251881Speter  return NULL;
479251881Speter}
480251881Speter
481289180Speter/* Read /etc/os-release, as documented here:
482289180Speter * http://www.freedesktop.org/software/systemd/man/os-release.html
483289180Speter */
484289180Speterstatic const char *
485289180Spetersystemd_release(apr_pool_t *pool)
486289180Speter{
487289180Speter  svn_error_t *err;
488289180Speter  svn_stream_t *stream;
489289180Speter
490289180Speter  /* Open the file. */
491289180Speter  err = svn_stream_open_readonly(&stream, "/etc/os-release", pool, pool);
492289180Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
493289180Speter    {
494289180Speter      svn_error_clear(err);
495289180Speter      err = svn_stream_open_readonly(&stream, "/usr/lib/os-release", pool,
496289180Speter                                     pool);
497289180Speter    }
498289180Speter  if (err)
499289180Speter    {
500289180Speter      svn_error_clear(err);
501289180Speter      return NULL;
502289180Speter    }
503289180Speter
504289180Speter  /* Look for the PRETTY_NAME line. */
505289180Speter  while (TRUE)
506289180Speter    {
507289180Speter      svn_stringbuf_t *line;
508289180Speter      svn_boolean_t eof;
509289180Speter
510289180Speter      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
511289180Speter      if (err)
512289180Speter        {
513289180Speter          svn_error_clear(err);
514289180Speter          return NULL;
515289180Speter        }
516289180Speter
517289180Speter      if (!strncmp(line->data, "PRETTY_NAME=", 12))
518289180Speter        {
519289180Speter          svn_stringbuf_t *release_name;
520289180Speter
521289180Speter          /* The value may or may not be enclosed by double quotes.  We don't
522289180Speter           * attempt to strip them. */
523289180Speter          release_name = svn_stringbuf_create(line->data + 12, pool);
524289180Speter          svn_error_clear(svn_stream_close(stream));
525289180Speter          svn_stringbuf_strip_whitespace(release_name);
526289180Speter          return release_name->data;
527289180Speter        }
528289180Speter
529289180Speter      if (eof)
530289180Speter        break;
531289180Speter    }
532289180Speter
533289180Speter  /* The file did not contain a PRETTY_NAME line. */
534289180Speter  svn_error_clear(svn_stream_close(stream));
535289180Speter  return NULL;
536289180Speter}
537289180Speter
538251881Speter/* Read the whole contents of a file. */
539251881Speterstatic svn_stringbuf_t *
540251881Speterread_file_contents(const char *filename, apr_pool_t *pool)
541251881Speter{
542251881Speter  svn_error_t *err;
543251881Speter  svn_stringbuf_t *buffer;
544251881Speter
545251881Speter  err = svn_stringbuf_from_file2(&buffer, filename, pool);
546251881Speter  if (err)
547251881Speter    {
548251881Speter      svn_error_clear(err);
549251881Speter      return NULL;
550251881Speter    }
551251881Speter
552251881Speter  return buffer;
553251881Speter}
554251881Speter
555251881Speter/* Strip everything but the first line from a stringbuf. */
556251881Speterstatic void
557251881Speterstringbuf_first_line_only(svn_stringbuf_t *buffer)
558251881Speter{
559251881Speter  char *eol = strchr(buffer->data, '\n');
560251881Speter  if (eol)
561251881Speter    {
562251881Speter      *eol = '\0';
563251881Speter      buffer->len = 1 + eol - buffer->data;
564251881Speter    }
565251881Speter  svn_stringbuf_strip_whitespace(buffer);
566251881Speter}
567251881Speter
568251881Speter/* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
569251881Speterstatic const char *
570251881Speterredhat_release(apr_pool_t *pool)
571251881Speter{
572251881Speter  svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
573251881Speter  if (buffer)
574251881Speter    {
575251881Speter      stringbuf_first_line_only(buffer);
576251881Speter      return buffer->data;
577251881Speter    }
578251881Speter  return NULL;
579251881Speter}
580251881Speter
581251881Speter/* Look at /etc/SuSE-release to detect non-LSB SuSE. */
582251881Speterstatic const char *
583251881Spetersuse_release(apr_pool_t *pool)
584251881Speter{
585251881Speter  const char *release = NULL;
586251881Speter  const char *codename = NULL;
587251881Speter
588251881Speter  svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
589251881Speter  svn_stringbuf_t *line;
590251881Speter  svn_stream_t *stream;
591251881Speter  svn_boolean_t eof;
592251881Speter  svn_error_t *err;
593251881Speter  if (!buffer)
594251881Speter      return NULL;
595251881Speter
596251881Speter  stream = svn_stream_from_stringbuf(buffer, pool);
597251881Speter  err = svn_stream_readline(stream, &line, "\n", &eof, pool);
598251881Speter  if (err || eof)
599251881Speter    {
600251881Speter      svn_error_clear(err);
601251881Speter      return NULL;
602251881Speter    }
603251881Speter
604251881Speter  svn_stringbuf_strip_whitespace(line);
605251881Speter  release = line->data;
606251881Speter
607251881Speter  for (;;)
608251881Speter    {
609251881Speter      const char *key;
610251881Speter
611251881Speter      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
612251881Speter      if (err || eof)
613251881Speter        {
614251881Speter          svn_error_clear(err);
615251881Speter          break;
616251881Speter        }
617251881Speter
618251881Speter      key = stringbuf_split_key(line, '=');
619251881Speter      if (!key)
620251881Speter        continue;
621251881Speter
622251881Speter      if (0 == strncmp(key, "CODENAME", 8))
623251881Speter        codename = line->data;
624251881Speter    }
625251881Speter
626251881Speter  return apr_psprintf(pool, "%s%s%s%s",
627251881Speter                      release,
628251881Speter                      (codename ? " (" : ""),
629251881Speter                      (codename ? codename : ""),
630251881Speter                      (codename ? ")" : ""));
631251881Speter}
632251881Speter
633251881Speter/* Look at /etc/debian_version to detect non-LSB Debian. */
634251881Speterstatic const char *
635251881Speterdebian_release(apr_pool_t *pool)
636251881Speter{
637251881Speter  svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
638251881Speter  if (!buffer)
639251881Speter      return NULL;
640251881Speter
641251881Speter  stringbuf_first_line_only(buffer);
642289180Speter  return apr_pstrcat(pool, "Debian ", buffer->data, SVN_VA_NULL);
643251881Speter}
644251881Speter
645251881Speter/* Try to find the Linux distribution name, or return info from uname. */
646251881Speterstatic const char *
647251881Speterlinux_release_name(apr_pool_t *pool)
648251881Speter{
649251881Speter  const char *uname_release = release_name_from_uname(pool);
650251881Speter
651251881Speter  /* Try anything that has /usr/bin/lsb_release.
652251881Speter     Covers, for example, Debian, Ubuntu and SuSE.  */
653251881Speter  const char *release_name = lsb_release(pool);
654251881Speter
655289180Speter  /* Try the systemd way (covers Arch). */
656289180Speter  if (!release_name)
657289180Speter    release_name = systemd_release(pool);
658289180Speter
659251881Speter  /* Try RHEL/Fedora/CentOS */
660251881Speter  if (!release_name)
661251881Speter    release_name = redhat_release(pool);
662251881Speter
663251881Speter  /* Try Non-LSB SuSE */
664251881Speter  if (!release_name)
665251881Speter    release_name = suse_release(pool);
666251881Speter
667251881Speter  /* Try non-LSB Debian */
668251881Speter  if (!release_name)
669251881Speter    release_name = debian_release(pool);
670251881Speter
671251881Speter  if (!release_name)
672251881Speter    return uname_release;
673251881Speter
674251881Speter  if (!uname_release)
675251881Speter    return release_name;
676251881Speter
677251881Speter  return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
678251881Speter}
679362181Sdim
680362181Sdim#if HAVE_ELF_H
681362181Sdim/* Parse a hexadecimal number as a pointer value. */
682362181Sdimstatic const unsigned char *
683362181Sdimparse_pointer_value(const char *start, const char *limit, char **end)
684362181Sdim{
685362181Sdim  const unsigned char *ptr;
686362181Sdim  const apr_uint64_t val = (apr_uint64_t)apr_strtoi64(start, end, 16);
687362181Sdim
688362181Sdim  if (errno                     /* overflow */
689362181Sdim      || *end == start          /* no valid digits */
690362181Sdim      || *end >= limit)         /* representation too long */
691362181Sdim    return NULL;
692362181Sdim
693362181Sdim  ptr = (const unsigned char*)val;
694362181Sdim  if (val != (apr_uint64_t)ptr)  /* truncated value */
695362181Sdim    return NULL;
696362181Sdim
697362181Sdim  return ptr;
698362181Sdim}
699362181Sdim
700362181Sdim/* Read the ELF header at the mapping position to check if this is a shared
701362181Sdim   library. We only look at the ELF identification and the type. The format is
702362181Sdim   described here:
703362181Sdim       http://www.skyfree.org/linux/references/ELF_Format.pdf
704362181Sdim*/
705362181Sdimstatic svn_boolean_t
706362181Sdimcheck_elf_header(const unsigned char *map_start,
707362181Sdim                 const unsigned char *map_end)
708362181Sdim{
709362181Sdim  /* A union of all known ELF header types, for size checks. */
710362181Sdim  union max_elf_header_size_t
711362181Sdim  {
712362181Sdim    Elf32_Ehdr header_32;
713362181Sdim    Elf64_Ehdr header_64;
714362181Sdim  };
715362181Sdim
716362181Sdim  /* Check the size of the mapping and the ELF magic tag. */
717362181Sdim  if (map_end < map_start
718362181Sdim      || map_end - map_start < sizeof(union max_elf_header_size_t)
719362181Sdim      || memcmp(map_start, ELFMAG, SELFMAG))
720362181Sdim    {
721362181Sdim      return FALSE;
722362181Sdim    }
723362181Sdim
724362181Sdim  /* Check that this is an ELF shared library or executable file. This also
725362181Sdim     implicitly checks that the data encoding of the current process is the
726362181Sdim     same as in the loaded library. */
727362181Sdim  if (map_start[EI_CLASS] == ELFCLASS32)
728362181Sdim    {
729362181Sdim      const Elf32_Ehdr *hdr = (void*)map_start;
730362181Sdim      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
731362181Sdim    }
732362181Sdim  else if (map_start[EI_CLASS] == ELFCLASS64)
733362181Sdim    {
734362181Sdim      const Elf64_Ehdr *hdr = (void*)map_start;
735362181Sdim      return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
736362181Sdim    }
737362181Sdim
738362181Sdim  return FALSE;
739362181Sdim}
740362181Sdim#endif  /* HAVE_ELF_H */
741362181Sdim
742362181Sdimstatic const apr_array_header_t *
743362181Sdimlinux_shared_libs(apr_pool_t *pool)
744362181Sdim{
745362181Sdim  /* Read the list of loaded modules from /proc/[pid]/maps
746362181Sdim    The format is described here:
747362181Sdim        http://man7.org/linux/man-pages/man5/proc.5.html
748362181Sdim  */
749362181Sdim
750362181Sdim  const char *maps = apr_psprintf(pool, "/proc/%ld/maps", (long)getpid());
751362181Sdim  apr_array_header_t *result = NULL;
752362181Sdim  svn_boolean_t eof = FALSE;
753362181Sdim  svn_stream_t *stream;
754362181Sdim  svn_error_t *err;
755362181Sdim
756362181Sdim  err = svn_stream_open_readonly(&stream, maps, pool, pool);
757362181Sdim  if (err)
758362181Sdim    {
759362181Sdim      svn_error_clear(err);
760362181Sdim      return NULL;
761362181Sdim    }
762362181Sdim
763362181Sdim  /* Each line in /proc/[pid]/maps consists of whitespace-delimited fields. */
764362181Sdim  while (!eof)
765362181Sdim    {
766362181Sdim      svn_stringbuf_t *line;
767362181Sdim
768362181Sdim#if HAVE_ELF_H
769362181Sdim      const unsigned char *map_start;
770362181Sdim      const unsigned char *map_end;
771362181Sdim#endif
772362181Sdim
773362181Sdim      err = svn_stream_readline(stream, &line, "\n", &eof, pool);
774362181Sdim      if (err)
775362181Sdim        {
776362181Sdim          svn_error_clear(err);
777362181Sdim          return NULL;
778362181Sdim        }
779362181Sdim
780362181Sdim#if HAVE_ELF_H
781362181Sdim      /* Address: The mapped memory address range. */
782362181Sdim      {
783362181Sdim        const char *const limit = line->data + line->len;
784362181Sdim        char *end;
785362181Sdim
786362181Sdim        /* The start of the address range */
787362181Sdim        map_start = parse_pointer_value(line->data, limit, &end);
788362181Sdim        if (!map_start || *end != '-')
789362181Sdim          continue;
790362181Sdim
791362181Sdim        /* The end of the address range */
792362181Sdim        map_end = parse_pointer_value(end + 1, limit, &end);
793362181Sdim        if (!map_end || !svn_ctype_isspace(*end))
794362181Sdim          continue;
795362181Sdim      }
796362181Sdim#endif
797362181Sdim
798362181Sdim      stringbuf_skip_whitespace_field(line); /* skip address */
799362181Sdim
800362181Sdim      /* Permissions: The memory region must be readable and executable. */
801362181Sdim      if (line->len < 4 || line->data[0] != 'r' || line->data[2] != 'x')
802362181Sdim        continue;
803362181Sdim
804362181Sdim      stringbuf_skip_whitespace_field(line); /* skip perms */
805362181Sdim      stringbuf_skip_whitespace_field(line); /* skip offset */
806362181Sdim      stringbuf_skip_whitespace_field(line); /* skip device */
807362181Sdim
808362181Sdim      /* I-Node: If it is 0, there is no file associated with the region. */
809362181Sdim      if (line->len < 2
810362181Sdim          || (line->data[0] == '0' && svn_ctype_isspace(line->data[1])))
811362181Sdim        continue;
812362181Sdim
813362181Sdim      stringbuf_skip_whitespace_field(line); /* skip inode */
814362181Sdim
815362181Sdim      /* Consider only things that look like absolute paths.
816362181Sdim         Files that were removed since the process was created (due to an
817362181Sdim         upgrade, for example) are marked as '(deleted)'. */
818362181Sdim      if (line->data[0] == '/')
819362181Sdim        {
820362181Sdim          svn_version_ext_loaded_lib_t *lib;
821362181Sdim
822362181Sdim#if HAVE_ELF_H
823362181Sdim          if (!check_elf_header(map_start, map_end))
824362181Sdim            continue;
825362181Sdim#endif
826362181Sdim
827362181Sdim          /* We've done our best to find a mapped shared library. */
828362181Sdim          if (!result)
829362181Sdim            {
830362181Sdim              result = apr_array_make(pool, 32, sizeof(*lib));
831362181Sdim            }
832362181Sdim          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
833362181Sdim          lib->name = line->data;
834362181Sdim          lib->version = NULL;
835362181Sdim        }
836362181Sdim    }
837362181Sdim
838362181Sdim  svn_error_clear(svn_stream_close(stream));
839362181Sdim  return result;
840362181Sdim}
841251881Speter#endif /* __linux__ */
842251881Speter
843251881Speter
844251881Speter#ifdef WIN32
845251881Spetertypedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
846257936Spetertypedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);
847251881Speter
848289180Spetersvn_boolean_t
849289180Spetersvn_sysinfo___fill_windows_version(OSVERSIONINFOEXW *version_info)
850289180Speter{
851289180Speter  memset(version_info, 0, sizeof(*version_info));
852289180Speter
853289180Speter  version_info->dwOSVersionInfoSize = sizeof(*version_info);
854289180Speter
855289180Speter  /* Kill warnings with the Windows 8 and later platform SDK */
856289180Speter#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
857289180Speter  /* Windows 8 deprecated the API to retrieve the Windows version to avoid
858289180Speter     backwards compatibility problems... It might return a constant version
859289180Speter     in future Windows versions... But let's kill the warning.
860289180Speter
861289180Speter     We can implementation this using a different function later. */
862289180Speter#pragma warning(push)
863289180Speter#pragma warning(disable: 4996)
864289180Speter#endif
865289180Speter
866289180Speter  /* Prototype supports OSVERSIONINFO */
867289180Speter  return GetVersionExW((LPVOID)version_info);
868289180Speter#if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
869289180Speter#pragma warning(pop)
870289180Speter#pragma warning(disable: 4996)
871289180Speter#endif
872289180Speter}
873289180Speter
874289180Speter/* Get system info, and try to tell the difference between the native
875289180Speter   system type and the runtime environment of the current process.
876289180Speter   Populate results in SYSINFO and LOCAL_SYSINFO (optional). */
877251881Speterstatic BOOL
878251881Spetersystem_info(SYSTEM_INFO *sysinfo,
879289180Speter            SYSTEM_INFO *local_sysinfo)
880251881Speter{
881251881Speter  FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
882362181Sdim    GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetNativeSystemInfo");
883251881Speter
884289180Speter  memset(sysinfo, 0, sizeof *sysinfo);
885251881Speter  if (local_sysinfo)
886251881Speter    {
887289180Speter      memset(local_sysinfo, 0, sizeof *local_sysinfo);
888251881Speter      GetSystemInfo(local_sysinfo);
889251881Speter      if (GetNativeSystemInfo_)
890251881Speter        GetNativeSystemInfo_(sysinfo);
891251881Speter      else
892251881Speter        memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
893251881Speter    }
894251881Speter  else
895251881Speter    GetSystemInfo(sysinfo);
896251881Speter
897251881Speter  return TRUE;
898251881Speter}
899251881Speter
900251881Speter/* Map the proccessor type from SYSINFO to a string. */
901251881Speterstatic const char *
902251881Speterprocessor_name(SYSTEM_INFO *sysinfo)
903251881Speter{
904251881Speter  switch (sysinfo->wProcessorArchitecture)
905251881Speter    {
906251881Speter    case PROCESSOR_ARCHITECTURE_AMD64:         return "x86_64";
907251881Speter    case PROCESSOR_ARCHITECTURE_IA64:          return "ia64";
908251881Speter    case PROCESSOR_ARCHITECTURE_INTEL:         return "x86";
909251881Speter    case PROCESSOR_ARCHITECTURE_MIPS:          return "mips";
910251881Speter    case PROCESSOR_ARCHITECTURE_ALPHA:         return "alpha32";
911251881Speter    case PROCESSOR_ARCHITECTURE_PPC:           return "powerpc";
912251881Speter    case PROCESSOR_ARCHITECTURE_SHX:           return "shx";
913251881Speter    case PROCESSOR_ARCHITECTURE_ARM:           return "arm";
914251881Speter    case PROCESSOR_ARCHITECTURE_ALPHA64:       return "alpha";
915251881Speter    case PROCESSOR_ARCHITECTURE_MSIL:          return "msil";
916251881Speter    case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
917251881Speter    default: return "unknown";
918251881Speter    }
919251881Speter}
920251881Speter
921251881Speter/* Return the Windows-specific canonical host name. */
922251881Speterstatic const char *
923251881Speterwin32_canonical_host(apr_pool_t *pool)
924251881Speter{
925251881Speter  SYSTEM_INFO sysinfo;
926251881Speter  SYSTEM_INFO local_sysinfo;
927251881Speter  OSVERSIONINFOEXW osinfo;
928251881Speter
929289180Speter  if (system_info(&sysinfo, &local_sysinfo)
930289180Speter      && svn_sysinfo___fill_windows_version(&osinfo))
931251881Speter    {
932251881Speter      const char *arch = processor_name(&local_sysinfo);
933251881Speter      const char *machine = processor_name(&sysinfo);
934251881Speter      const char *vendor = "microsoft";
935251881Speter      const char *sysname = "windows";
936251881Speter      const char *sysver = apr_psprintf(pool, "%u.%u.%u",
937251881Speter                                        (unsigned int)osinfo.dwMajorVersion,
938251881Speter                                        (unsigned int)osinfo.dwMinorVersion,
939251881Speter                                        (unsigned int)osinfo.dwBuildNumber);
940251881Speter
941251881Speter      if (sysinfo.wProcessorArchitecture
942251881Speter          == local_sysinfo.wProcessorArchitecture)
943251881Speter        return apr_psprintf(pool, "%s-%s-%s%s",
944251881Speter                            machine, vendor, sysname, sysver);
945251881Speter      return apr_psprintf(pool, "%s/%s-%s-%s%s",
946251881Speter                          arch, machine, vendor, sysname, sysver);
947251881Speter    }
948251881Speter
949251881Speter  return "unknown-microsoft-windows";
950251881Speter}
951251881Speter
952251881Speter/* Convert a Unicode string to UTF-8. */
953251881Speterstatic char *
954251881Speterwcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
955251881Speter{
956251881Speter  const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
957251881Speter                                          NULL, 0, NULL, NULL);
958251881Speter  if (bufsize > 0)
959251881Speter    {
960251881Speter      char *const utf8 = apr_palloc(pool, bufsize + 1);
961251881Speter      WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
962251881Speter      return utf8;
963251881Speter    }
964251881Speter  return NULL;
965251881Speter}
966251881Speter
967251881Speter/* Query the value called NAME of the registry key HKEY. */
968251881Speterstatic char *
969251881Speterregistry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
970251881Speter{
971251881Speter  DWORD size;
972251881Speter  wchar_t *value;
973251881Speter
974251881Speter  if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
975251881Speter    return NULL;
976251881Speter
977251881Speter  value = apr_palloc(pool, size + sizeof *value);
978251881Speter  if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
979251881Speter    return NULL;
980251881Speter  value[size / sizeof *value] = 0;
981251881Speter  return wcs_to_utf8(value, pool);
982251881Speter}
983251881Speter
984251881Speter/* Try to glean the Windows release name and associated info from the
985251881Speter   registry. Failing that, construct a release name from the version
986251881Speter   info. */
987251881Speterstatic const char *
988251881Speterwin32_release_name(apr_pool_t *pool)
989251881Speter{
990251881Speter  SYSTEM_INFO sysinfo;
991251881Speter  OSVERSIONINFOEXW osinfo;
992251881Speter  HKEY hkcv;
993251881Speter
994289180Speter  if (!system_info(&sysinfo, NULL)
995289180Speter      || !svn_sysinfo___fill_windows_version(&osinfo))
996251881Speter    return NULL;
997251881Speter
998251881Speter  if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
999251881Speter                     L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
1000251881Speter                     0, KEY_QUERY_VALUE, &hkcv))
1001251881Speter    {
1002251881Speter      const char *release = registry_value(hkcv, L"ProductName", pool);
1003251881Speter      const char *spack = registry_value(hkcv, L"CSDVersion", pool);
1004251881Speter      const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
1005251881Speter      const char *curtype = registry_value(hkcv, L"CurrentType", pool);
1006251881Speter      const char *install = registry_value(hkcv, L"InstallationType", pool);
1007251881Speter      const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
1008251881Speter
1009251881Speter      if (!spack && *osinfo.szCSDVersion)
1010251881Speter        spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1011251881Speter
1012251881Speter      if (!curbuild)
1013251881Speter        curbuild = registry_value(hkcv, L"CurrentBuild", pool);
1014251881Speter
1015251881Speter      if (release || spack || curver || curtype || curbuild)
1016251881Speter        {
1017251881Speter          const char *bootinfo = "";
1018251881Speter          if (curver || install || curtype)
1019251881Speter            {
1020251881Speter              bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
1021251881Speter                                      (curver ? curver : ""),
1022251881Speter                                      (install ? (curver ? " " : "") : ""),
1023251881Speter                                      (install ? install : ""),
1024251881Speter                                      (curtype
1025251881Speter                                       ? (curver||install ? " " : "")
1026251881Speter                                       : ""),
1027251881Speter                                      (curtype ? curtype : ""));
1028251881Speter            }
1029251881Speter
1030251881Speter          return apr_psprintf(pool, "%s%s%s%s%s%s%s",
1031251881Speter                              (release ? release : ""),
1032251881Speter                              (spack ? (release ? ", " : "") : ""),
1033251881Speter                              (spack ? spack : ""),
1034251881Speter                              (curbuild
1035251881Speter                               ? (release||spack ? ", build " : "build ")
1036251881Speter                               : ""),
1037251881Speter                              (curbuild ? curbuild : ""),
1038251881Speter                              (bootinfo
1039251881Speter                               ? (release||spack||curbuild ? " " : "")
1040251881Speter                               : ""),
1041251881Speter                              (bootinfo ? bootinfo : ""));
1042251881Speter        }
1043251881Speter    }
1044251881Speter
1045251881Speter  if (*osinfo.szCSDVersion)
1046251881Speter    {
1047251881Speter      const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1048251881Speter
1049251881Speter      if (servicepack)
1050251881Speter        return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
1051251881Speter                            (unsigned int)osinfo.dwMajorVersion,
1052251881Speter                            (unsigned int)osinfo.dwMinorVersion,
1053251881Speter                            servicepack,
1054251881Speter                            (unsigned int)osinfo.dwBuildNumber);
1055251881Speter
1056251881Speter      /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
1057251881Speter      if (osinfo.wServicePackMinor)
1058251881Speter        return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
1059251881Speter                            (unsigned int)osinfo.dwMajorVersion,
1060251881Speter                            (unsigned int)osinfo.dwMinorVersion,
1061251881Speter                            (unsigned int)osinfo.wServicePackMajor,
1062251881Speter                            (unsigned int)osinfo.wServicePackMinor,
1063251881Speter                            (unsigned int)osinfo.dwBuildNumber);
1064251881Speter
1065251881Speter      return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
1066251881Speter                          (unsigned int)osinfo.dwMajorVersion,
1067251881Speter                          (unsigned int)osinfo.dwMinorVersion,
1068251881Speter                          (unsigned int)osinfo.wServicePackMajor,
1069251881Speter                          (unsigned int)osinfo.dwBuildNumber);
1070251881Speter    }
1071251881Speter
1072251881Speter  return apr_psprintf(pool, "Windows NT %u.%u, build %u",
1073251881Speter                      (unsigned int)osinfo.dwMajorVersion,
1074251881Speter                      (unsigned int)osinfo.dwMinorVersion,
1075251881Speter                      (unsigned int)osinfo.dwBuildNumber);
1076251881Speter}
1077251881Speter
1078251881Speter
1079251881Speter/* Get a list of handles of shared libs loaded by the current
1080251881Speter   process. Returns a NULL-terminated array alocated from POOL. */
1081251881Speterstatic HMODULE *
1082251881Speterenum_loaded_modules(apr_pool_t *pool)
1083251881Speter{
1084257936Speter  HMODULE psapi_dll = 0;
1085251881Speter  HANDLE current = GetCurrentProcess();
1086251881Speter  HMODULE dummy[1];
1087251881Speter  HMODULE *handles;
1088251881Speter  DWORD size;
1089257936Speter  FNENUMPROCESSMODULES EnumProcessModules_;
1090251881Speter
1091362181Sdim  psapi_dll = GetModuleHandleW(L"psapi.dll");
1092257936Speter
1093257936Speter  if (!psapi_dll)
1094257936Speter    {
1095257936Speter      /* Load and never unload, just like static linking */
1096362181Sdim      psapi_dll = LoadLibraryW(L"psapi.dll");
1097257936Speter    }
1098257936Speter
1099257936Speter  if (!psapi_dll)
1100257936Speter      return NULL;
1101257936Speter
1102257936Speter  EnumProcessModules_ = (FNENUMPROCESSMODULES)
1103257936Speter                              GetProcAddress(psapi_dll, "EnumProcessModules");
1104257936Speter
1105257936Speter  /* Before Windows XP psapi was an optional module */
1106257936Speter  if (! EnumProcessModules_)
1107251881Speter    return NULL;
1108251881Speter
1109257936Speter  if (!EnumProcessModules_(current, dummy, sizeof(dummy), &size))
1110257936Speter    return NULL;
1111257936Speter
1112251881Speter  handles = apr_palloc(pool, size + sizeof *handles);
1113257936Speter  if (! EnumProcessModules_(current, handles, size, &size))
1114251881Speter    return NULL;
1115251881Speter  handles[size / sizeof *handles] = NULL;
1116251881Speter  return handles;
1117251881Speter}
1118251881Speter
1119251881Speter/* Find the version number, if any, embedded in FILENAME. */
1120251881Speterstatic const char *
1121251881Speterfile_version_number(const wchar_t *filename, apr_pool_t *pool)
1122251881Speter{
1123251881Speter  VS_FIXEDFILEINFO info;
1124251881Speter  unsigned int major, minor, micro, nano;
1125251881Speter  void *data;
1126251881Speter  DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
1127251881Speter  void *vinfo;
1128251881Speter  UINT vinfo_size;
1129251881Speter
1130251881Speter  if (!data_size)
1131251881Speter    return NULL;
1132251881Speter
1133251881Speter  data = apr_palloc(pool, data_size);
1134251881Speter  if (!GetFileVersionInfoW(filename, 0, data_size, data))
1135251881Speter    return NULL;
1136251881Speter
1137251881Speter  if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
1138251881Speter    return NULL;
1139251881Speter
1140251881Speter  if (vinfo_size != sizeof info)
1141251881Speter    return NULL;
1142251881Speter
1143251881Speter  memcpy(&info, vinfo, sizeof info);
1144251881Speter  major = (info.dwFileVersionMS >> 16) & 0xFFFF;
1145251881Speter  minor = info.dwFileVersionMS & 0xFFFF;
1146251881Speter  micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
1147251881Speter  nano = info.dwFileVersionLS & 0xFFFF;
1148251881Speter
1149251881Speter  if (!nano)
1150251881Speter    {
1151251881Speter      if (!micro)
1152251881Speter        return apr_psprintf(pool, "%u.%u", major, minor);
1153251881Speter      else
1154251881Speter        return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
1155251881Speter    }
1156251881Speter  return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
1157251881Speter}
1158251881Speter
1159251881Speter/* List the shared libraries loaded by the current process. */
1160251881Speterstatic const apr_array_header_t *
1161251881Speterwin32_shared_libs(apr_pool_t *pool)
1162251881Speter{
1163251881Speter  apr_array_header_t *array = NULL;
1164251881Speter  wchar_t buffer[MAX_PATH + 1];
1165251881Speter  HMODULE *handles = enum_loaded_modules(pool);
1166251881Speter  HMODULE *module;
1167251881Speter
1168251881Speter  for (module = handles; module && *module; ++module)
1169251881Speter    {
1170251881Speter      const char *filename;
1171251881Speter      const char *version;
1172251881Speter      if (GetModuleFileNameW(*module, buffer, MAX_PATH))
1173251881Speter        {
1174251881Speter          buffer[MAX_PATH] = 0;
1175251881Speter
1176251881Speter          version = file_version_number(buffer, pool);
1177251881Speter          filename = wcs_to_utf8(buffer, pool);
1178251881Speter          if (filename)
1179251881Speter            {
1180251881Speter              svn_version_ext_loaded_lib_t *lib;
1181251881Speter
1182251881Speter              if (!array)
1183251881Speter                {
1184251881Speter                  array = apr_array_make(pool, 32, sizeof(*lib));
1185251881Speter                }
1186251881Speter              lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
1187251881Speter              lib->name = svn_dirent_local_style(filename, pool);
1188251881Speter              lib->version = version;
1189251881Speter            }
1190251881Speter        }
1191251881Speter    }
1192251881Speter
1193251881Speter  return array;
1194251881Speter}
1195251881Speter#endif /* WIN32 */
1196251881Speter
1197251881Speter
1198251881Speter#ifdef SVN_HAVE_MACOS_PLIST
1199289180Speter/* implements svn_write_fn_t to copy the data into a CFMutableDataRef that's
1200289180Speter * in the baton. */
1201289180Speterstatic svn_error_t *
1202289180Speterwrite_to_cfmutabledata(void *baton, const char *data, apr_size_t *len)
1203289180Speter{
1204289180Speter  CFMutableDataRef *resource = (CFMutableDataRef *) baton;
1205289180Speter
1206289180Speter  CFDataAppendBytes(*resource, (UInt8 *)data, *len);
1207289180Speter
1208289180Speter  return SVN_NO_ERROR;
1209289180Speter}
1210289180Speter
1211251881Speter/* Load the SystemVersion.plist or ServerVersion.plist file into a
1212251881Speter   property list. Set SERVER to TRUE if the file read was
1213251881Speter   ServerVersion.plist. */
1214251881Speterstatic CFDictionaryRef
1215251881Spetersystem_version_plist(svn_boolean_t *server, apr_pool_t *pool)
1216251881Speter{
1217289180Speter  static const char server_version[] =
1218251881Speter    "/System/Library/CoreServices/ServerVersion.plist";
1219289180Speter  static const char system_version[] =
1220251881Speter    "/System/Library/CoreServices/SystemVersion.plist";
1221289180Speter  svn_stream_t *read_stream, *write_stream;
1222289180Speter  svn_error_t *err;
1223251881Speter  CFPropertyListRef plist = NULL;
1224289180Speter  CFMutableDataRef resource = CFDataCreateMutable(kCFAllocatorDefault, 0);
1225251881Speter
1226289180Speter  /* failed getting the CFMutableDataRef, shouldn't happen */
1227289180Speter  if (!resource)
1228251881Speter    return NULL;
1229251881Speter
1230289180Speter  /* Try to open the plist files to get the data */
1231289180Speter  err = svn_stream_open_readonly(&read_stream, server_version, pool, pool);
1232289180Speter  if (err)
1233251881Speter    {
1234289180Speter      if (!APR_STATUS_IS_ENOENT(err->apr_err))
1235251881Speter        {
1236289180Speter          svn_error_clear(err);
1237289180Speter          CFRelease(resource);
1238251881Speter          return NULL;
1239251881Speter        }
1240251881Speter      else
1241251881Speter        {
1242289180Speter          svn_error_clear(err);
1243289180Speter          err = svn_stream_open_readonly(&read_stream, system_version,
1244289180Speter                                         pool, pool);
1245289180Speter          if (err)
1246289180Speter            {
1247289180Speter              svn_error_clear(err);
1248289180Speter              CFRelease(resource);
1249289180Speter              return NULL;
1250289180Speter            }
1251289180Speter
1252251881Speter          *server = FALSE;
1253251881Speter        }
1254251881Speter    }
1255251881Speter  else
1256251881Speter    {
1257251881Speter      *server = TRUE;
1258251881Speter    }
1259251881Speter
1260289180Speter  /* copy the data onto the CFMutableDataRef to allow us to provide it to
1261289180Speter   * the CoreFoundation functions that parse proprerty lists */
1262289180Speter  write_stream = svn_stream_create(&resource, pool);
1263289180Speter  svn_stream_set_write(write_stream, write_to_cfmutabledata);
1264289180Speter  err = svn_stream_copy3(read_stream, write_stream, NULL, NULL, pool);
1265289180Speter  if (err)
1266289180Speter    {
1267289180Speter      svn_error_clear(err);
1268289180Speter      return NULL;
1269289180Speter    }
1270289180Speter
1271289180Speter#if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1272289180Speter  /* This function is only available from Mac OS 10.6 onward. */
1273289180Speter  plist = CFPropertyListCreateWithData(kCFAllocatorDefault, resource,
1274289180Speter                                       kCFPropertyListImmutable,
1275289180Speter                                       NULL, NULL);
1276289180Speter#else  /* Mac OS 10.5 or earlier */
1277289180Speter  /* This function obsolete and deprecated since Mac OS 10.10. */
1278251881Speter  plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
1279251881Speter                                          kCFPropertyListImmutable,
1280289180Speter                                          NULL);
1281289180Speter#endif /* MAC_OS_X_VERSION_10_6 */
1282289180Speter
1283251881Speter  if (resource)
1284251881Speter    CFRelease(resource);
1285251881Speter
1286289180Speter  if (!plist)
1287289180Speter    return NULL;
1288289180Speter
1289251881Speter  if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
1290251881Speter    {
1291251881Speter      /* Oops ... this really should be a dict. */
1292251881Speter      CFRelease(plist);
1293251881Speter      return NULL;
1294251881Speter    }
1295251881Speter
1296251881Speter  return plist;
1297251881Speter}
1298251881Speter
1299251881Speter/* Return the value for KEY from PLIST, or NULL if not available. */
1300251881Speterstatic const char *
1301251881Spetervalue_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
1302251881Speter{
1303251881Speter  CFStringRef valref;
1304251881Speter  CFIndex bufsize;
1305251881Speter  const void *valptr;
1306251881Speter  const char *value;
1307251881Speter
1308251881Speter  if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
1309251881Speter    return NULL;
1310251881Speter
1311251881Speter  valref = valptr;
1312251881Speter  if (CFStringGetTypeID() != CFGetTypeID(valref))
1313251881Speter    return NULL;
1314251881Speter
1315251881Speter  value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
1316251881Speter  if (value)
1317251881Speter    return apr_pstrdup(pool, value);
1318251881Speter
1319251881Speter  bufsize =  5 * CFStringGetLength(valref) + 1;
1320251881Speter  value = apr_palloc(pool, bufsize);
1321251881Speter  if (!CFStringGetCString(valref, (char*)value, bufsize,
1322251881Speter                          kCFStringEncodingUTF8))
1323251881Speter    value = NULL;
1324251881Speter
1325251881Speter  return value;
1326251881Speter}
1327251881Speter
1328362181Sdim/* Return the minor version the operating system, given the number in
1329251881Speter   a format that matches the regular expression /^10\.\d+(\..*)?$/ */
1330362181Sdimstatic int
1331362181Sdimmacos_minor_version(const char *osver)
1332251881Speter{
1333251881Speter  char *end = NULL;
1334251881Speter  unsigned long num = strtoul(osver, &end, 10);
1335251881Speter
1336251881Speter  if (!end || *end != '.' || num != 10)
1337362181Sdim    return -1;
1338251881Speter
1339251881Speter  osver = end + 1;
1340251881Speter  end = NULL;
1341251881Speter  num = strtoul(osver, &end, 10);
1342251881Speter  if (!end || (*end && *end != '.'))
1343362181Sdim    return -1;
1344251881Speter
1345362181Sdim  return (int)num;
1346362181Sdim}
1347362181Sdim
1348362181Sdim/* Return the product name of the operating system. */
1349362181Sdimstatic const char *
1350362181Sdimproduct_name_from_minor_version(int minor, const char* product_name)
1351362181Sdim{
1352362181Sdim  /* We can only do this if we know the official product name. */
1353362181Sdim  if (0 != strcmp(product_name, "Mac OS X"))
1354362181Sdim    return product_name;
1355362181Sdim
1356362181Sdim  if (minor <= 7)
1357362181Sdim    return product_name;
1358362181Sdim
1359362181Sdim  if (minor <= 11)
1360362181Sdim    return "OS X";
1361362181Sdim
1362362181Sdim  return "macOS";
1363362181Sdim}
1364362181Sdim
1365362181Sdim/* Return the commercial name of the operating system. */
1366362181Sdimstatic const char *
1367362181Sdimrelease_name_from_minor_version(int minor, const char* product_name)
1368362181Sdim{
1369362181Sdim  /* We can only do this if we know the official product name. */
1370362181Sdim  if (0 == strcmp(product_name, "Mac OS X"))
1371251881Speter    {
1372362181Sdim      /* See https://en.wikipedia.org/wiki/MacOS_version_history#Releases */
1373362181Sdim      switch(minor)
1374362181Sdim        {
1375362181Sdim        case  0: return "Cheetah";
1376362181Sdim        case  1: return "Puma";
1377362181Sdim        case  2: return "Jaguar";
1378362181Sdim        case  3: return "Panther";
1379362181Sdim        case  4: return "Tiger";
1380362181Sdim        case  5: return "Leopard";
1381362181Sdim        case  6: return "Snow Leopard";
1382362181Sdim        case  7: return "Lion";
1383362181Sdim        case  8: return "Mountain Lion";
1384362181Sdim        case  9: return "Mavericks";
1385362181Sdim        case 10: return "Yosemite";
1386362181Sdim        case 11: return "El Capitan";
1387362181Sdim        case 12: return "Sierra";
1388362181Sdim        case 13: return "High Sierra";
1389362181Sdim        case 14: return "Mojave";
1390362181Sdim        case 15: return "Catalina";
1391362181Sdim        }
1392251881Speter    }
1393251881Speter  return NULL;
1394251881Speter}
1395251881Speter
1396251881Speter/* Construct the release name from information stored in the Mac OS X
1397251881Speter   "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
1398251881Speter   Server. */
1399251881Speterstatic const char *
1400251881Spetermacos_release_name(apr_pool_t *pool)
1401251881Speter{
1402251881Speter  svn_boolean_t server;
1403251881Speter  CFDictionaryRef plist = system_version_plist(&server, pool);
1404251881Speter
1405251881Speter  if (plist)
1406251881Speter    {
1407251881Speter      const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
1408251881Speter      const char *osver = value_from_dict(plist,
1409251881Speter                                          CFSTR("ProductUserVisibleVersion"),
1410251881Speter                                          pool);
1411251881Speter      const char *build = value_from_dict(plist,
1412251881Speter                                          CFSTR("ProductBuildVersion"),
1413251881Speter                                          pool);
1414251881Speter      const char *release;
1415362181Sdim      int minor_version;
1416251881Speter
1417251881Speter      if (!osver)
1418251881Speter        osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
1419362181Sdim      minor_version = macos_minor_version(osver);
1420362181Sdim      release = release_name_from_minor_version(minor_version, osname);
1421362181Sdim      osname = product_name_from_minor_version(minor_version, osname);
1422251881Speter
1423251881Speter      CFRelease(plist);
1424251881Speter      return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
1425251881Speter                          (osname ? osname : ""),
1426362181Sdim                          (release ? (osname ? " " : "") : ""),
1427362181Sdim                          (release ? release : ""),
1428362181Sdim                          (osver ? (osname||release ? " " : "") : ""),
1429251881Speter                          (osver ? osver : ""),
1430251881Speter                          (build
1431362181Sdim                           ? (osname||release||osver ? ", " : "")
1432251881Speter                           : ""),
1433251881Speter                          (build
1434251881Speter                           ? (server ? "server build " : "build ")
1435251881Speter                           : ""),
1436251881Speter                          (build ? build : ""));
1437251881Speter    }
1438251881Speter
1439251881Speter  return NULL;
1440251881Speter}
1441251881Speter#endif  /* SVN_HAVE_MACOS_PLIST */
1442251881Speter
1443251881Speter#ifdef SVN_HAVE_MACHO_ITERATE
1444251881Speter/* List the shared libraries loaded by the current process.
1445251881Speter   Ignore frameworks and system libraries, they're just clutter. */
1446251881Speterstatic const apr_array_header_t *
1447251881Spetermacos_shared_libs(apr_pool_t *pool)
1448251881Speter{
1449251881Speter  static const char slb_prefix[] = "/usr/lib/system/";
1450251881Speter  static const char fwk_prefix[] = "/System/Library/Frameworks/";
1451251881Speter  static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
1452251881Speter
1453251881Speter  const size_t slb_prefix_len = strlen(slb_prefix);
1454251881Speter  const size_t fwk_prefix_len = strlen(fwk_prefix);
1455251881Speter  const size_t pfk_prefix_len = strlen(pfk_prefix);
1456251881Speter
1457251881Speter  apr_array_header_t *result = NULL;
1458251881Speter  apr_array_header_t *dylibs = NULL;
1459251881Speter
1460251881Speter  uint32_t i;
1461251881Speter  for (i = 0;; ++i)
1462251881Speter    {
1463251881Speter      const struct mach_header *header = _dyld_get_image_header(i);
1464251881Speter      const char *filename = _dyld_get_image_name(i);
1465251881Speter      const char *version;
1466251881Speter      char *truename;
1467251881Speter      svn_version_ext_loaded_lib_t *lib;
1468251881Speter
1469251881Speter      if (!(header && filename))
1470251881Speter        break;
1471251881Speter
1472251881Speter      switch (header->cputype)
1473251881Speter        {
1474251881Speter        case CPU_TYPE_I386:      version = _("Intel"); break;
1475251881Speter        case CPU_TYPE_X86_64:    version = _("Intel 64-bit"); break;
1476251881Speter        case CPU_TYPE_POWERPC:   version = _("PowerPC"); break;
1477251881Speter        case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
1478251881Speter        default:
1479251881Speter          version = NULL;
1480251881Speter        }
1481251881Speter
1482251881Speter      if (0 == apr_filepath_merge(&truename, "", filename,
1483251881Speter                                  APR_FILEPATH_NATIVE
1484251881Speter                                  | APR_FILEPATH_TRUENAME,
1485251881Speter                                  pool))
1486251881Speter        filename = truename;
1487251881Speter      else
1488251881Speter        filename = apr_pstrdup(pool, filename);
1489251881Speter
1490251881Speter      if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
1491251881Speter          || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
1492251881Speter          || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
1493251881Speter        {
1494251881Speter          /* Ignore frameworks and system libraries. */
1495251881Speter          continue;
1496251881Speter        }
1497251881Speter
1498251881Speter      if (header->filetype == MH_EXECUTE)
1499251881Speter        {
1500251881Speter          /* Make sure the program filename is first in the list */
1501251881Speter          if (!result)
1502251881Speter            {
1503251881Speter              result = apr_array_make(pool, 32, sizeof(*lib));
1504251881Speter            }
1505251881Speter          lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
1506251881Speter        }
1507251881Speter      else
1508251881Speter        {
1509251881Speter          if (!dylibs)
1510251881Speter            {
1511251881Speter              dylibs = apr_array_make(pool, 32, sizeof(*lib));
1512251881Speter            }
1513251881Speter          lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
1514251881Speter        }
1515251881Speter
1516251881Speter      lib->name = filename;
1517251881Speter      lib->version = version;
1518251881Speter    }
1519251881Speter
1520251881Speter  /* Gather results into one array. */
1521251881Speter  if (dylibs)
1522251881Speter    {
1523251881Speter      if (result)
1524251881Speter        apr_array_cat(result, dylibs);
1525251881Speter      else
1526251881Speter        result = dylibs;
1527251881Speter    }
1528251881Speter
1529251881Speter  return result;
1530251881Speter}
1531251881Speter#endif  /* SVN_HAVE_MACHO_ITERATE */
1532