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