]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/sysinfo.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 #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
72 static const char *canonical_host_from_uname(apr_pool_t *pool);
73 # ifndef SVN_HAVE_MACOS_PLIST
74 static const char *release_name_from_uname(apr_pool_t *pool);
75 # endif
76 #endif
77
78 #ifdef WIN32
79 static const char *win32_canonical_host(apr_pool_t *pool);
80 static const char *win32_release_name(apr_pool_t *pool);
81 static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
82 #endif /* WIN32 */
83
84 #ifdef SVN_HAVE_MACOS_PLIST
85 static const char *macos_release_name(apr_pool_t *pool);
86 #endif
87
88 #ifdef SVN_HAVE_MACHO_ITERATE
89 static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
90 #endif
91
92
93 #if __linux__
94 static const char *linux_release_name(apr_pool_t *pool);
95 #endif
96
97 const char *
98 svn_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
110 const char *
111 svn_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
126 const apr_array_header_t *
127 svn_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
173 const apr_array_header_t *
174 svn_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 \f
186 #if HAVE_UNAME
187 static const char*
188 canonical_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`". */
253 static const char *
254 release_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 \f
292 #if __linux__
293 /* Split a stringbuf into a key/value pair.
294    Return the key, leaving the stripped value in the stringbuf. */
295 static const char *
296 stringbuf_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` */
327 static const char *
328 lsb_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  */
433 static const char *
434 systemd_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. */
488 static svn_stringbuf_t *
489 read_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. */
505 static void
506 stringbuf_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. */
518 static const char *
519 redhat_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. */
531 static const char *
532 suse_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. */
583 static const char *
584 debian_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. */
595 static const char *
596 linux_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 \f
631 #ifdef WIN32
632 typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
633 typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);
634
635 svn_boolean_t
636 svn_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). */
664 static BOOL
665 system_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. */
688 static const char *
689 processor_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. */
709 static const char *
710 win32_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. */
740 static char *
741 wcs_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. */
755 static char *
756 registry_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. */
774 static const char *
775 win32_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. */
868 static HMODULE *
869 enum_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. */
907 static const char *
908 file_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. */
947 static const apr_array_header_t *
948 win32_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 \f
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. */
988 static svn_error_t *
989 write_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. */
1001 static CFDictionaryRef
1002 system_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. */
1087 static const char *
1088 value_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+(\..*)?$/ */
1117 static const char *
1118 release_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     }
1147
1148   return NULL;
1149 }
1150
1151 /* Construct the release name from information stored in the Mac OS X
1152    "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
1153    Server. */
1154 static const char *
1155 macos_release_name(apr_pool_t *pool)
1156 {
1157   svn_boolean_t server;
1158   CFDictionaryRef plist = system_version_plist(&server, pool);
1159
1160   if (plist)
1161     {
1162       const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
1163       const char *osver = value_from_dict(plist,
1164                                           CFSTR("ProductUserVisibleVersion"),
1165                                           pool);
1166       const char *build = value_from_dict(plist,
1167                                           CFSTR("ProductBuildVersion"),
1168                                           pool);
1169       const char *release;
1170
1171       if (!osver)
1172         osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
1173       release = release_name_from_version(osver);
1174
1175       CFRelease(plist);
1176       return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
1177                           (osname ? osname : ""),
1178                           (osver ? (osname ? " " : "") : ""),
1179                           (osver ? osver : ""),
1180                           (release ? (osname||osver ? " " : "") : ""),
1181                           (release ? release : ""),
1182                           (build
1183                            ? (osname||osver||release ? ", " : "")
1184                            : ""),
1185                           (build
1186                            ? (server ? "server build " : "build ")
1187                            : ""),
1188                           (build ? build : ""));
1189     }
1190
1191   return NULL;
1192 }
1193 #endif  /* SVN_HAVE_MACOS_PLIST */
1194
1195 #ifdef SVN_HAVE_MACHO_ITERATE
1196 /* List the shared libraries loaded by the current process.
1197    Ignore frameworks and system libraries, they're just clutter. */
1198 static const apr_array_header_t *
1199 macos_shared_libs(apr_pool_t *pool)
1200 {
1201   static const char slb_prefix[] = "/usr/lib/system/";
1202   static const char fwk_prefix[] = "/System/Library/Frameworks/";
1203   static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
1204
1205   const size_t slb_prefix_len = strlen(slb_prefix);
1206   const size_t fwk_prefix_len = strlen(fwk_prefix);
1207   const size_t pfk_prefix_len = strlen(pfk_prefix);
1208
1209   apr_array_header_t *result = NULL;
1210   apr_array_header_t *dylibs = NULL;
1211
1212   uint32_t i;
1213   for (i = 0;; ++i)
1214     {
1215       const struct mach_header *header = _dyld_get_image_header(i);
1216       const char *filename = _dyld_get_image_name(i);
1217       const char *version;
1218       char *truename;
1219       svn_version_ext_loaded_lib_t *lib;
1220
1221       if (!(header && filename))
1222         break;
1223
1224       switch (header->cputype)
1225         {
1226         case CPU_TYPE_I386:      version = _("Intel"); break;
1227         case CPU_TYPE_X86_64:    version = _("Intel 64-bit"); break;
1228         case CPU_TYPE_POWERPC:   version = _("PowerPC"); break;
1229         case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
1230         default:
1231           version = NULL;
1232         }
1233
1234       if (0 == apr_filepath_merge(&truename, "", filename,
1235                                   APR_FILEPATH_NATIVE
1236                                   | APR_FILEPATH_TRUENAME,
1237                                   pool))
1238         filename = truename;
1239       else
1240         filename = apr_pstrdup(pool, filename);
1241
1242       if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
1243           || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
1244           || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
1245         {
1246           /* Ignore frameworks and system libraries. */
1247           continue;
1248         }
1249
1250       if (header->filetype == MH_EXECUTE)
1251         {
1252           /* Make sure the program filename is first in the list */
1253           if (!result)
1254             {
1255               result = apr_array_make(pool, 32, sizeof(*lib));
1256             }
1257           lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
1258         }
1259       else
1260         {
1261           if (!dylibs)
1262             {
1263               dylibs = apr_array_make(pool, 32, sizeof(*lib));
1264             }
1265           lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
1266         }
1267
1268       lib->name = filename;
1269       lib->version = version;
1270     }
1271
1272   /* Gather results into one array. */
1273   if (dylibs)
1274     {
1275       if (result)
1276         apr_array_cat(result, dylibs);
1277       else
1278         result = dylibs;
1279     }
1280
1281   return result;
1282 }
1283 #endif  /* SVN_HAVE_MACHO_ITERATE */