]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_subr/sysinfo.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_subr / sysinfo.c
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 \f
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
74 static const char *canonical_host_from_uname(apr_pool_t *pool);
75 # ifndef SVN_HAVE_MACOS_PLIST
76 static const char *release_name_from_uname(apr_pool_t *pool);
77 # endif
78 #endif
79
80 #ifdef WIN32
81 static const char *win32_canonical_host(apr_pool_t *pool);
82 static const char *win32_release_name(apr_pool_t *pool);
83 static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
84 #endif /* WIN32 */
85
86 #ifdef SVN_HAVE_MACOS_PLIST
87 static const char *macos_release_name(apr_pool_t *pool);
88 #endif
89
90 #ifdef SVN_HAVE_MACHO_ITERATE
91 static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
92 #endif
93
94
95 #if __linux__
96 static const char *linux_release_name(apr_pool_t *pool);
97 #endif
98
99 const char *
100 svn_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
112 const char *
113 svn_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
128 const apr_array_header_t *
129 svn_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
160 const apr_array_header_t *
161 svn_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 \f
173 #if HAVE_UNAME
174 static const char*
175 canonical_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`". */
240 static const char *
241 release_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 \f
279 #if __linux__
280 /* Split a stringbuf into a key/value pair.
281    Return the key, leaving the striped value in the stringbuf. */
282 static const char *
283 stringbuf_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` */
314 static const char *
315 lsb_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. */
418 static svn_stringbuf_t *
419 read_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. */
435 static void
436 stringbuf_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. */
448 static const char *
449 redhat_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. */
461 static const char *
462 suse_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. */
513 static const char *
514 debian_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. */
525 static const char *
526 linux_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 \f
557 #ifdef WIN32
558 typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
559 typedef 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. */
565 static BOOL
566 system_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. */
595 static const char *
596 processor_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. */
616 static const char *
617 win32_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. */
646 static char *
647 wcs_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. */
661 static char *
662 registry_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. */
680 static const char *
681 win32_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. */
773 static HMODULE *
774 enum_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. */
812 static const char *
813 file_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. */
852 static const apr_array_header_t *
853 win32_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 \f
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. */
894 static CFDictionaryRef
895 system_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. */
968 static const char *
969 value_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+(\..*)?$/ */
998 static const char *
999 release_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. */
1034 static const char *
1035 macos_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. */
1078 static const apr_array_header_t *
1079 macos_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 */