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