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