3 * sysinfo.c : information about the running system
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
27 #define APR_WANT_STRFUNC
31 #include <apr_pools.h>
32 #include <apr_file_info.h>
33 #include <apr_signal.h>
34 #include <apr_strings.h>
35 #include <apr_thread_proc.h>
36 #include <apr_version.h>
37 #include <apu_version.h>
39 #include "svn_pools.h"
40 #include "svn_ctype.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_error.h"
44 #include "svn_string.h"
46 #include "svn_version.h"
48 #include "private/svn_sqlite.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_utf_private.h"
53 #include "svn_private_config.h"
56 #include <sys/types.h>
59 #if HAVE_SYS_UTSNAME_H
60 #include <sys/utsname.h>
71 #ifdef SVN_HAVE_MACOS_PLIST
72 #include <CoreFoundation/CoreFoundation.h>
73 #include <AvailabilityMacros.h>
74 # ifndef MAC_OS_X_VERSION_10_6
75 # define MAC_OS_X_VERSION_10_6 1060
79 #ifdef SVN_HAVE_MACHO_ITERATE
80 #include <mach-o/dyld.h>
81 #include <mach-o/loader.h>
85 static const char *canonical_host_from_uname(apr_pool_t *pool);
86 # ifndef SVN_HAVE_MACOS_PLIST
87 static const char *release_name_from_uname(apr_pool_t *pool);
92 static const char *win32_canonical_host(apr_pool_t *pool);
93 static const char *win32_release_name(apr_pool_t *pool);
94 static const apr_array_header_t *win32_shared_libs(apr_pool_t *pool);
97 #ifdef SVN_HAVE_MACOS_PLIST
98 static const char *macos_release_name(apr_pool_t *pool);
101 #ifdef SVN_HAVE_MACHO_ITERATE
102 static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool);
107 static const char *linux_release_name(apr_pool_t *pool);
108 static const apr_array_header_t *linux_shared_libs(apr_pool_t *pool);
112 svn_sysinfo__canonical_host(apr_pool_t *pool)
115 return win32_canonical_host(pool);
117 return canonical_host_from_uname(pool);
119 return "unknown-unknown-unknown";
125 svn_sysinfo__release_name(apr_pool_t *pool)
128 return win32_release_name(pool);
129 #elif defined(SVN_HAVE_MACOS_PLIST)
130 return macos_release_name(pool);
132 return linux_release_name(pool);
134 return release_name_from_uname(pool);
140 const apr_array_header_t *
141 svn_sysinfo__linked_libs(apr_pool_t *pool)
143 svn_version_ext_linked_lib_t *lib;
144 apr_array_header_t *array = apr_array_make(pool, 7, sizeof(*lib));
145 int lz4_version = svn_lz4__runtime_version();
147 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
149 lib->compiled_version = APR_VERSION_STRING;
150 lib->runtime_version = apr_pstrdup(pool, apr_version_string());
152 /* Don't list APR-Util if it isn't linked in, which it may not be if
153 * we're using APR 2.x+ which combined APR-Util into APR. */
154 #ifdef APU_VERSION_STRING
155 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
156 lib->name = "APR-Util";
157 lib->compiled_version = APU_VERSION_STRING;
158 lib->runtime_version = apr_pstrdup(pool, apu_version_string());
161 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
163 lib->compiled_version = apr_pstrdup(pool, svn_xml__compiled_version());
164 lib->runtime_version = apr_pstrdup(pool, svn_xml__runtime_version());
166 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
167 lib->name = "SQLite";
168 lib->compiled_version = apr_pstrdup(pool, svn_sqlite__compiled_version());
169 #ifdef SVN_SQLITE_INLINE
170 lib->runtime_version = NULL;
172 lib->runtime_version = apr_pstrdup(pool, svn_sqlite__runtime_version());
175 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
176 lib->name = "Utf8proc";
177 lib->compiled_version = apr_pstrdup(pool, svn_utf__utf8proc_compiled_version());
178 lib->runtime_version = apr_pstrdup(pool, svn_utf__utf8proc_runtime_version());
180 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
182 lib->compiled_version = apr_pstrdup(pool, svn_zlib__compiled_version());
183 lib->runtime_version = apr_pstrdup(pool, svn_zlib__runtime_version());
185 lib = &APR_ARRAY_PUSH(array, svn_version_ext_linked_lib_t);
187 lib->compiled_version = apr_pstrdup(pool, svn_lz4__compiled_version());
189 lib->runtime_version = apr_psprintf(pool, "%d.%d.%d",
190 lz4_version / 100 / 100,
191 (lz4_version / 100) % 100,
197 const apr_array_header_t *
198 svn_sysinfo__loaded_libs(apr_pool_t *pool)
201 return win32_shared_libs(pool);
202 #elif defined(SVN_HAVE_MACHO_ITERATE)
203 return macos_shared_libs(pool);
205 return linux_shared_libs(pool);
214 canonical_host_from_uname(apr_pool_t *pool)
216 const char *machine = "unknown";
217 const char *vendor = "unknown";
218 const char *sysname = "unknown";
219 const char *sysver = "";
222 if (0 <= uname(&info))
227 err = svn_utf_cstring_to_utf8(&tmp, info.machine, pool);
229 svn_error_clear(err);
233 err = svn_utf_cstring_to_utf8(&tmp, info.sysname, pool);
235 svn_error_clear(err);
238 char *lwr = apr_pstrdup(pool, tmp);
242 if (svn_ctype_isupper(*it))
243 *it = apr_tolower(*it);
249 if (0 == strcmp(sysname, "darwin"))
251 if (0 == strcmp(sysname, "linux"))
255 err = svn_utf_cstring_to_utf8(&tmp, info.release, pool);
257 svn_error_clear(err);
260 apr_size_t n = strspn(tmp, ".0123456789");
263 char *ver = apr_pstrdup(pool, tmp);
273 return apr_psprintf(pool, "%s-%s-%s%s", machine, vendor, sysname, sysver);
276 # ifndef SVN_HAVE_MACOS_PLIST
277 /* Generate a release name from the uname(3) info, effectively
278 returning "`uname -s` `uname -r`". */
280 release_name_from_uname(apr_pool_t *pool)
283 if (0 <= uname(&info))
289 err = svn_utf_cstring_to_utf8(&sysname, info.sysname, pool);
293 svn_error_clear(err);
297 err = svn_utf_cstring_to_utf8(&sysver, info.release, pool);
301 svn_error_clear(err);
304 if (sysname || sysver)
306 return apr_psprintf(pool, "%s%s%s",
307 (sysname ? sysname : ""),
308 (sysver ? (sysname ? " " : "") : ""),
309 (sysver ? sysver : ""));
314 # endif /* !SVN_HAVE_MACOS_PLIST */
315 #endif /* HAVE_UNAME */
319 /* Find the first whitespace character in a stringbuf.
320 Analogous to svn_stringbuf_first_non_whitespace. */
322 stringbuf_first_whitespace(const svn_stringbuf_t *str)
325 for (i = 0; i < str->len; ++i)
327 if (svn_ctype_isspace(str->data[i]))
333 /* Skip a whitespace-delimited field in a stringbuf. */
335 stringbuf_skip_whitespace_field(svn_stringbuf_t *str)
338 i = stringbuf_first_whitespace(str);
339 svn_stringbuf_leftchop(str, i);
340 i = svn_stringbuf_first_non_whitespace(str);
341 svn_stringbuf_leftchop(str, i);
344 /* Split a stringbuf into a key/value pair.
345 Return the key, leaving the stripped value in the stringbuf. */
347 stringbuf_split_key(svn_stringbuf_t *buffer, char delim)
352 end = strchr(buffer->data, delim);
356 svn_stringbuf_strip_whitespace(buffer);
358 /* Now we split the currently allocated buffer in two parts:
359 - a const char * HEAD
360 - the remaining stringbuf_t. */
362 /* Create HEAD as '\0' terminated const char * */
364 end = strchr(key, delim);
367 /* And update the TAIL to be a smaller, but still valid stringbuf */
368 buffer->data = end + 1;
369 buffer->len -= 1 + end - key;
370 buffer->blocksize -= 1 + end - key;
372 svn_stringbuf_strip_whitespace(buffer);
377 /* Parse `/usr/bin/lsb_rlease --all` */
379 lsb_release(apr_pool_t *pool)
381 static const char *const args[3] =
383 "/usr/bin/lsb_release",
388 const char *distributor = NULL;
389 const char *description = NULL;
390 const char *release = NULL;
391 const char *codename = NULL;
394 svn_stream_t *lsbinfo;
397 /* Run /usr/bin/lsb_release --all < /dev/null 2>/dev/null */
399 apr_file_t *stdin_handle;
400 apr_file_t *stdout_handle;
402 err = svn_io_file_open(&stdin_handle, SVN_NULL_DEVICE_NAME,
403 APR_READ, APR_OS_DEFAULT, pool);
405 err = svn_io_file_open(&stdout_handle, SVN_NULL_DEVICE_NAME,
406 APR_WRITE, APR_OS_DEFAULT, pool);
408 err = svn_io_start_cmd3(&lsbproc, NULL, args[0], args, NULL, FALSE,
411 FALSE, stdout_handle,
415 svn_error_clear(err);
420 /* Parse the output and try to populate the */
421 lsbinfo = svn_stream_from_aprfile2(lsbproc.out, TRUE, pool);
426 svn_boolean_t eof = FALSE;
427 svn_stringbuf_t *line;
430 err = svn_stream_readline(lsbinfo, &line, "\n", &eof, pool);
434 key = stringbuf_split_key(line, ':');
438 if (0 == svn_cstring_casecmp(key, "Distributor ID"))
439 distributor = line->data;
440 else if (0 == svn_cstring_casecmp(key, "Description"))
441 description = line->data;
442 else if (0 == svn_cstring_casecmp(key, "Release"))
443 release = line->data;
444 else if (0 == svn_cstring_casecmp(key, "Codename"))
445 codename = line->data;
447 err = svn_error_compose_create(err,
448 svn_stream_close(lsbinfo));
451 svn_error_clear(err);
452 apr_proc_kill(&lsbproc, SIGKILL);
457 /* Reap the child process */
458 err = svn_io_wait_for_cmd(&lsbproc, "", NULL, NULL, pool);
461 svn_error_clear(err);
466 return apr_psprintf(pool, "%s%s%s%s", description,
467 (codename ? " (" : ""),
468 (codename ? codename : ""),
469 (codename ? ")" : ""));
471 return apr_psprintf(pool, "%s%s%s%s%s%s", distributor,
472 (release ? " " : ""),
473 (release ? release : ""),
474 (codename ? " (" : ""),
475 (codename ? codename : ""),
476 (codename ? ")" : ""));
481 /* Read /etc/os-release, as documented here:
482 * http://www.freedesktop.org/software/systemd/man/os-release.html
485 systemd_release(apr_pool_t *pool)
488 svn_stream_t *stream;
491 err = svn_stream_open_readonly(&stream, "/etc/os-release", pool, pool);
492 if (err && APR_STATUS_IS_ENOENT(err->apr_err))
494 svn_error_clear(err);
495 err = svn_stream_open_readonly(&stream, "/usr/lib/os-release", pool,
500 svn_error_clear(err);
504 /* Look for the PRETTY_NAME line. */
507 svn_stringbuf_t *line;
510 err = svn_stream_readline(stream, &line, "\n", &eof, pool);
513 svn_error_clear(err);
517 if (!strncmp(line->data, "PRETTY_NAME=", 12))
519 svn_stringbuf_t *release_name;
521 /* The value may or may not be enclosed by double quotes. We don't
522 * attempt to strip them. */
523 release_name = svn_stringbuf_create(line->data + 12, pool);
524 svn_error_clear(svn_stream_close(stream));
525 svn_stringbuf_strip_whitespace(release_name);
526 return release_name->data;
533 /* The file did not contain a PRETTY_NAME line. */
534 svn_error_clear(svn_stream_close(stream));
538 /* Read the whole contents of a file. */
539 static svn_stringbuf_t *
540 read_file_contents(const char *filename, apr_pool_t *pool)
543 svn_stringbuf_t *buffer;
545 err = svn_stringbuf_from_file2(&buffer, filename, pool);
548 svn_error_clear(err);
555 /* Strip everything but the first line from a stringbuf. */
557 stringbuf_first_line_only(svn_stringbuf_t *buffer)
559 char *eol = strchr(buffer->data, '\n');
563 buffer->len = 1 + eol - buffer->data;
565 svn_stringbuf_strip_whitespace(buffer);
568 /* Look at /etc/redhat_release to detect RHEL/Fedora/CentOS. */
570 redhat_release(apr_pool_t *pool)
572 svn_stringbuf_t *buffer = read_file_contents("/etc/redhat-release", pool);
575 stringbuf_first_line_only(buffer);
581 /* Look at /etc/SuSE-release to detect non-LSB SuSE. */
583 suse_release(apr_pool_t *pool)
585 const char *release = NULL;
586 const char *codename = NULL;
588 svn_stringbuf_t *buffer = read_file_contents("/etc/SuSE-release", pool);
589 svn_stringbuf_t *line;
590 svn_stream_t *stream;
596 stream = svn_stream_from_stringbuf(buffer, pool);
597 err = svn_stream_readline(stream, &line, "\n", &eof, pool);
600 svn_error_clear(err);
604 svn_stringbuf_strip_whitespace(line);
605 release = line->data;
611 err = svn_stream_readline(stream, &line, "\n", &eof, pool);
614 svn_error_clear(err);
618 key = stringbuf_split_key(line, '=');
622 if (0 == strncmp(key, "CODENAME", 8))
623 codename = line->data;
626 return apr_psprintf(pool, "%s%s%s%s",
628 (codename ? " (" : ""),
629 (codename ? codename : ""),
630 (codename ? ")" : ""));
633 /* Look at /etc/debian_version to detect non-LSB Debian. */
635 debian_release(apr_pool_t *pool)
637 svn_stringbuf_t *buffer = read_file_contents("/etc/debian_version", pool);
641 stringbuf_first_line_only(buffer);
642 return apr_pstrcat(pool, "Debian ", buffer->data, SVN_VA_NULL);
645 /* Try to find the Linux distribution name, or return info from uname. */
647 linux_release_name(apr_pool_t *pool)
649 const char *uname_release = release_name_from_uname(pool);
651 /* Try anything that has /usr/bin/lsb_release.
652 Covers, for example, Debian, Ubuntu and SuSE. */
653 const char *release_name = lsb_release(pool);
655 /* Try the systemd way (covers Arch). */
657 release_name = systemd_release(pool);
659 /* Try RHEL/Fedora/CentOS */
661 release_name = redhat_release(pool);
663 /* Try Non-LSB SuSE */
665 release_name = suse_release(pool);
667 /* Try non-LSB Debian */
669 release_name = debian_release(pool);
672 return uname_release;
677 return apr_psprintf(pool, "%s [%s]", release_name, uname_release);
681 /* Parse a hexadecimal number as a pointer value. */
682 static const unsigned char *
683 parse_pointer_value(const char *start, const char *limit, char **end)
685 const unsigned char *ptr;
686 const apr_uint64_t val = (apr_uint64_t)apr_strtoi64(start, end, 16);
688 if (errno /* overflow */
689 || *end == start /* no valid digits */
690 || *end >= limit) /* representation too long */
693 ptr = (const unsigned char*)val;
694 if (val != (apr_uint64_t)ptr) /* truncated value */
700 /* Read the ELF header at the mapping position to check if this is a shared
701 library. We only look at the ELF identification and the type. The format is
703 http://www.skyfree.org/linux/references/ELF_Format.pdf
706 check_elf_header(const unsigned char *map_start,
707 const unsigned char *map_end)
709 /* A union of all known ELF header types, for size checks. */
710 union max_elf_header_size_t
712 Elf32_Ehdr header_32;
713 Elf64_Ehdr header_64;
716 /* Check the size of the mapping and the ELF magic tag. */
717 if (map_end < map_start
718 || map_end - map_start < sizeof(union max_elf_header_size_t)
719 || memcmp(map_start, ELFMAG, SELFMAG))
724 /* Check that this is an ELF shared library or executable file. This also
725 implicitly checks that the data encoding of the current process is the
726 same as in the loaded library. */
727 if (map_start[EI_CLASS] == ELFCLASS32)
729 const Elf32_Ehdr *hdr = (void*)map_start;
730 return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
732 else if (map_start[EI_CLASS] == ELFCLASS64)
734 const Elf64_Ehdr *hdr = (void*)map_start;
735 return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC);
740 #endif /* HAVE_ELF_H */
742 static const apr_array_header_t *
743 linux_shared_libs(apr_pool_t *pool)
745 /* Read the list of loaded modules from /proc/[pid]/maps
746 The format is described here:
747 http://man7.org/linux/man-pages/man5/proc.5.html
750 const char *maps = apr_psprintf(pool, "/proc/%ld/maps", (long)getpid());
751 apr_array_header_t *result = NULL;
752 svn_boolean_t eof = FALSE;
753 svn_stream_t *stream;
756 err = svn_stream_open_readonly(&stream, maps, pool, pool);
759 svn_error_clear(err);
763 /* Each line in /proc/[pid]/maps consists of whitespace-delimited fields. */
766 svn_stringbuf_t *line;
769 const unsigned char *map_start;
770 const unsigned char *map_end;
773 err = svn_stream_readline(stream, &line, "\n", &eof, pool);
776 svn_error_clear(err);
781 /* Address: The mapped memory address range. */
783 const char *const limit = line->data + line->len;
786 /* The start of the address range */
787 map_start = parse_pointer_value(line->data, limit, &end);
788 if (!map_start || *end != '-')
791 /* The end of the address range */
792 map_end = parse_pointer_value(end + 1, limit, &end);
793 if (!map_end || !svn_ctype_isspace(*end))
798 stringbuf_skip_whitespace_field(line); /* skip address */
800 /* Permissions: The memory region must be readable and executable. */
801 if (line->len < 4 || line->data[0] != 'r' || line->data[2] != 'x')
804 stringbuf_skip_whitespace_field(line); /* skip perms */
805 stringbuf_skip_whitespace_field(line); /* skip offset */
806 stringbuf_skip_whitespace_field(line); /* skip device */
808 /* I-Node: If it is 0, there is no file associated with the region. */
810 || (line->data[0] == '0' && svn_ctype_isspace(line->data[1])))
813 stringbuf_skip_whitespace_field(line); /* skip inode */
815 /* Consider only things that look like absolute paths.
816 Files that were removed since the process was created (due to an
817 upgrade, for example) are marked as '(deleted)'. */
818 if (line->data[0] == '/')
820 svn_version_ext_loaded_lib_t *lib;
823 if (!check_elf_header(map_start, map_end))
827 /* We've done our best to find a mapped shared library. */
830 result = apr_array_make(pool, 32, sizeof(*lib));
832 lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
833 lib->name = line->data;
838 svn_error_clear(svn_stream_close(stream));
841 #endif /* __linux__ */
845 typedef DWORD (WINAPI *FNGETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
846 typedef BOOL (WINAPI *FNENUMPROCESSMODULES) (HANDLE, HMODULE*, DWORD, LPDWORD);
849 svn_sysinfo___fill_windows_version(OSVERSIONINFOEXW *version_info)
851 memset(version_info, 0, sizeof(*version_info));
853 version_info->dwOSVersionInfoSize = sizeof(*version_info);
855 /* Kill warnings with the Windows 8 and later platform SDK */
856 #if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
857 /* Windows 8 deprecated the API to retrieve the Windows version to avoid
858 backwards compatibility problems... It might return a constant version
859 in future Windows versions... But let's kill the warning.
861 We can implementation this using a different function later. */
862 #pragma warning(push)
863 #pragma warning(disable: 4996)
866 /* Prototype supports OSVERSIONINFO */
867 return GetVersionExW((LPVOID)version_info);
868 #if _MSC_VER > 1600 && NTDDI_VERSION >= _0x06020000
870 #pragma warning(disable: 4996)
874 /* Get system info, and try to tell the difference between the native
875 system type and the runtime environment of the current process.
876 Populate results in SYSINFO and LOCAL_SYSINFO (optional). */
878 system_info(SYSTEM_INFO *sysinfo,
879 SYSTEM_INFO *local_sysinfo)
881 FNGETNATIVESYSTEMINFO GetNativeSystemInfo_ = (FNGETNATIVESYSTEMINFO)
882 GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetNativeSystemInfo");
884 memset(sysinfo, 0, sizeof *sysinfo);
887 memset(local_sysinfo, 0, sizeof *local_sysinfo);
888 GetSystemInfo(local_sysinfo);
889 if (GetNativeSystemInfo_)
890 GetNativeSystemInfo_(sysinfo);
892 memcpy(sysinfo, local_sysinfo, sizeof *sysinfo);
895 GetSystemInfo(sysinfo);
900 /* Map the proccessor type from SYSINFO to a string. */
902 processor_name(SYSTEM_INFO *sysinfo)
904 switch (sysinfo->wProcessorArchitecture)
906 case PROCESSOR_ARCHITECTURE_AMD64: return "x86_64";
907 case PROCESSOR_ARCHITECTURE_IA64: return "ia64";
908 case PROCESSOR_ARCHITECTURE_INTEL: return "x86";
909 case PROCESSOR_ARCHITECTURE_MIPS: return "mips";
910 case PROCESSOR_ARCHITECTURE_ALPHA: return "alpha32";
911 case PROCESSOR_ARCHITECTURE_PPC: return "powerpc";
912 case PROCESSOR_ARCHITECTURE_SHX: return "shx";
913 case PROCESSOR_ARCHITECTURE_ARM: return "arm";
914 case PROCESSOR_ARCHITECTURE_ALPHA64: return "alpha";
915 case PROCESSOR_ARCHITECTURE_MSIL: return "msil";
916 case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64: return "x86_wow64";
917 default: return "unknown";
921 /* Return the Windows-specific canonical host name. */
923 win32_canonical_host(apr_pool_t *pool)
926 SYSTEM_INFO local_sysinfo;
927 OSVERSIONINFOEXW osinfo;
929 if (system_info(&sysinfo, &local_sysinfo)
930 && svn_sysinfo___fill_windows_version(&osinfo))
932 const char *arch = processor_name(&local_sysinfo);
933 const char *machine = processor_name(&sysinfo);
934 const char *vendor = "microsoft";
935 const char *sysname = "windows";
936 const char *sysver = apr_psprintf(pool, "%u.%u.%u",
937 (unsigned int)osinfo.dwMajorVersion,
938 (unsigned int)osinfo.dwMinorVersion,
939 (unsigned int)osinfo.dwBuildNumber);
941 if (sysinfo.wProcessorArchitecture
942 == local_sysinfo.wProcessorArchitecture)
943 return apr_psprintf(pool, "%s-%s-%s%s",
944 machine, vendor, sysname, sysver);
945 return apr_psprintf(pool, "%s/%s-%s-%s%s",
946 arch, machine, vendor, sysname, sysver);
949 return "unknown-microsoft-windows";
952 /* Convert a Unicode string to UTF-8. */
954 wcs_to_utf8(const wchar_t *wcs, apr_pool_t *pool)
956 const int bufsize = WideCharToMultiByte(CP_UTF8, 0, wcs, -1,
957 NULL, 0, NULL, NULL);
960 char *const utf8 = apr_palloc(pool, bufsize + 1);
961 WideCharToMultiByte(CP_UTF8, 0, wcs, -1, utf8, bufsize, NULL, NULL);
967 /* Query the value called NAME of the registry key HKEY. */
969 registry_value(HKEY hkey, wchar_t *name, apr_pool_t *pool)
974 if (RegQueryValueExW(hkey, name, NULL, NULL, NULL, &size))
977 value = apr_palloc(pool, size + sizeof *value);
978 if (RegQueryValueExW(hkey, name, NULL, NULL, (void*)value, &size))
980 value[size / sizeof *value] = 0;
981 return wcs_to_utf8(value, pool);
984 /* Try to glean the Windows release name and associated info from the
985 registry. Failing that, construct a release name from the version
988 win32_release_name(apr_pool_t *pool)
991 OSVERSIONINFOEXW osinfo;
994 if (!system_info(&sysinfo, NULL)
995 || !svn_sysinfo___fill_windows_version(&osinfo))
998 if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
999 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
1000 0, KEY_QUERY_VALUE, &hkcv))
1002 const char *release = registry_value(hkcv, L"ProductName", pool);
1003 const char *spack = registry_value(hkcv, L"CSDVersion", pool);
1004 const char *curver = registry_value(hkcv, L"CurrentVersion", pool);
1005 const char *curtype = registry_value(hkcv, L"CurrentType", pool);
1006 const char *install = registry_value(hkcv, L"InstallationType", pool);
1007 const char *curbuild = registry_value(hkcv, L"CurrentBuildNumber", pool);
1009 if (!spack && *osinfo.szCSDVersion)
1010 spack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1013 curbuild = registry_value(hkcv, L"CurrentBuild", pool);
1015 if (release || spack || curver || curtype || curbuild)
1017 const char *bootinfo = "";
1018 if (curver || install || curtype)
1020 bootinfo = apr_psprintf(pool, "[%s%s%s%s%s]",
1021 (curver ? curver : ""),
1022 (install ? (curver ? " " : "") : ""),
1023 (install ? install : ""),
1025 ? (curver||install ? " " : "")
1027 (curtype ? curtype : ""));
1030 return apr_psprintf(pool, "%s%s%s%s%s%s%s",
1031 (release ? release : ""),
1032 (spack ? (release ? ", " : "") : ""),
1033 (spack ? spack : ""),
1035 ? (release||spack ? ", build " : "build ")
1037 (curbuild ? curbuild : ""),
1039 ? (release||spack||curbuild ? " " : "")
1041 (bootinfo ? bootinfo : ""));
1045 if (*osinfo.szCSDVersion)
1047 const char *servicepack = wcs_to_utf8(osinfo.szCSDVersion, pool);
1050 return apr_psprintf(pool, "Windows NT %u.%u, %s, build %u",
1051 (unsigned int)osinfo.dwMajorVersion,
1052 (unsigned int)osinfo.dwMinorVersion,
1054 (unsigned int)osinfo.dwBuildNumber);
1056 /* Assume wServicePackMajor > 0 if szCSDVersion is not empty */
1057 if (osinfo.wServicePackMinor)
1058 return apr_psprintf(pool, "Windows NT %u.%u SP%u.%u, build %u",
1059 (unsigned int)osinfo.dwMajorVersion,
1060 (unsigned int)osinfo.dwMinorVersion,
1061 (unsigned int)osinfo.wServicePackMajor,
1062 (unsigned int)osinfo.wServicePackMinor,
1063 (unsigned int)osinfo.dwBuildNumber);
1065 return apr_psprintf(pool, "Windows NT %u.%u SP%u, build %u",
1066 (unsigned int)osinfo.dwMajorVersion,
1067 (unsigned int)osinfo.dwMinorVersion,
1068 (unsigned int)osinfo.wServicePackMajor,
1069 (unsigned int)osinfo.dwBuildNumber);
1072 return apr_psprintf(pool, "Windows NT %u.%u, build %u",
1073 (unsigned int)osinfo.dwMajorVersion,
1074 (unsigned int)osinfo.dwMinorVersion,
1075 (unsigned int)osinfo.dwBuildNumber);
1079 /* Get a list of handles of shared libs loaded by the current
1080 process. Returns a NULL-terminated array alocated from POOL. */
1082 enum_loaded_modules(apr_pool_t *pool)
1084 HMODULE psapi_dll = 0;
1085 HANDLE current = GetCurrentProcess();
1089 FNENUMPROCESSMODULES EnumProcessModules_;
1091 psapi_dll = GetModuleHandleW(L"psapi.dll");
1095 /* Load and never unload, just like static linking */
1096 psapi_dll = LoadLibraryW(L"psapi.dll");
1102 EnumProcessModules_ = (FNENUMPROCESSMODULES)
1103 GetProcAddress(psapi_dll, "EnumProcessModules");
1105 /* Before Windows XP psapi was an optional module */
1106 if (! EnumProcessModules_)
1109 if (!EnumProcessModules_(current, dummy, sizeof(dummy), &size))
1112 handles = apr_palloc(pool, size + sizeof *handles);
1113 if (! EnumProcessModules_(current, handles, size, &size))
1115 handles[size / sizeof *handles] = NULL;
1119 /* Find the version number, if any, embedded in FILENAME. */
1121 file_version_number(const wchar_t *filename, apr_pool_t *pool)
1123 VS_FIXEDFILEINFO info;
1124 unsigned int major, minor, micro, nano;
1126 DWORD data_size = GetFileVersionInfoSizeW(filename, NULL);
1133 data = apr_palloc(pool, data_size);
1134 if (!GetFileVersionInfoW(filename, 0, data_size, data))
1137 if (!VerQueryValueW(data, L"\\", &vinfo, &vinfo_size))
1140 if (vinfo_size != sizeof info)
1143 memcpy(&info, vinfo, sizeof info);
1144 major = (info.dwFileVersionMS >> 16) & 0xFFFF;
1145 minor = info.dwFileVersionMS & 0xFFFF;
1146 micro = (info.dwFileVersionLS >> 16) & 0xFFFF;
1147 nano = info.dwFileVersionLS & 0xFFFF;
1152 return apr_psprintf(pool, "%u.%u", major, minor);
1154 return apr_psprintf(pool, "%u.%u.%u", major, minor, micro);
1156 return apr_psprintf(pool, "%u.%u.%u.%u", major, minor, micro, nano);
1159 /* List the shared libraries loaded by the current process. */
1160 static const apr_array_header_t *
1161 win32_shared_libs(apr_pool_t *pool)
1163 apr_array_header_t *array = NULL;
1164 wchar_t buffer[MAX_PATH + 1];
1165 HMODULE *handles = enum_loaded_modules(pool);
1168 for (module = handles; module && *module; ++module)
1170 const char *filename;
1171 const char *version;
1172 if (GetModuleFileNameW(*module, buffer, MAX_PATH))
1174 buffer[MAX_PATH] = 0;
1176 version = file_version_number(buffer, pool);
1177 filename = wcs_to_utf8(buffer, pool);
1180 svn_version_ext_loaded_lib_t *lib;
1184 array = apr_array_make(pool, 32, sizeof(*lib));
1186 lib = &APR_ARRAY_PUSH(array, svn_version_ext_loaded_lib_t);
1187 lib->name = svn_dirent_local_style(filename, pool);
1188 lib->version = version;
1198 #ifdef SVN_HAVE_MACOS_PLIST
1199 /* implements svn_write_fn_t to copy the data into a CFMutableDataRef that's
1201 static svn_error_t *
1202 write_to_cfmutabledata(void *baton, const char *data, apr_size_t *len)
1204 CFMutableDataRef *resource = (CFMutableDataRef *) baton;
1206 CFDataAppendBytes(*resource, (UInt8 *)data, *len);
1208 return SVN_NO_ERROR;
1211 /* Load the SystemVersion.plist or ServerVersion.plist file into a
1212 property list. Set SERVER to TRUE if the file read was
1213 ServerVersion.plist. */
1214 static CFDictionaryRef
1215 system_version_plist(svn_boolean_t *server, apr_pool_t *pool)
1217 static const char server_version[] =
1218 "/System/Library/CoreServices/ServerVersion.plist";
1219 static const char system_version[] =
1220 "/System/Library/CoreServices/SystemVersion.plist";
1221 svn_stream_t *read_stream, *write_stream;
1223 CFPropertyListRef plist = NULL;
1224 CFMutableDataRef resource = CFDataCreateMutable(kCFAllocatorDefault, 0);
1226 /* failed getting the CFMutableDataRef, shouldn't happen */
1230 /* Try to open the plist files to get the data */
1231 err = svn_stream_open_readonly(&read_stream, server_version, pool, pool);
1234 if (!APR_STATUS_IS_ENOENT(err->apr_err))
1236 svn_error_clear(err);
1237 CFRelease(resource);
1242 svn_error_clear(err);
1243 err = svn_stream_open_readonly(&read_stream, system_version,
1247 svn_error_clear(err);
1248 CFRelease(resource);
1260 /* copy the data onto the CFMutableDataRef to allow us to provide it to
1261 * the CoreFoundation functions that parse proprerty lists */
1262 write_stream = svn_stream_create(&resource, pool);
1263 svn_stream_set_write(write_stream, write_to_cfmutabledata);
1264 err = svn_stream_copy3(read_stream, write_stream, NULL, NULL, pool);
1267 svn_error_clear(err);
1271 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
1272 /* This function is only available from Mac OS 10.6 onward. */
1273 plist = CFPropertyListCreateWithData(kCFAllocatorDefault, resource,
1274 kCFPropertyListImmutable,
1276 #else /* Mac OS 10.5 or earlier */
1277 /* This function obsolete and deprecated since Mac OS 10.10. */
1278 plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, resource,
1279 kCFPropertyListImmutable,
1281 #endif /* MAC_OS_X_VERSION_10_6 */
1284 CFRelease(resource);
1289 if (CFDictionaryGetTypeID() != CFGetTypeID(plist))
1291 /* Oops ... this really should be a dict. */
1299 /* Return the value for KEY from PLIST, or NULL if not available. */
1301 value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool)
1308 if (!CFDictionaryGetValueIfPresent(plist, key, &valptr))
1312 if (CFStringGetTypeID() != CFGetTypeID(valref))
1315 value = CFStringGetCStringPtr(valref, kCFStringEncodingUTF8);
1317 return apr_pstrdup(pool, value);
1319 bufsize = 5 * CFStringGetLength(valref) + 1;
1320 value = apr_palloc(pool, bufsize);
1321 if (!CFStringGetCString(valref, (char*)value, bufsize,
1322 kCFStringEncodingUTF8))
1328 /* Return the minor version the operating system, given the number in
1329 a format that matches the regular expression /^10\.\d+(\..*)?$/ */
1331 macos_minor_version(const char *osver)
1334 unsigned long num = strtoul(osver, &end, 10);
1336 if (!end || *end != '.' || num != 10)
1341 num = strtoul(osver, &end, 10);
1342 if (!end || (*end && *end != '.'))
1348 /* Return the product name of the operating system. */
1350 product_name_from_minor_version(int minor, const char* product_name)
1352 /* We can only do this if we know the official product name. */
1353 if (0 != strcmp(product_name, "Mac OS X"))
1354 return product_name;
1357 return product_name;
1365 /* Return the commercial name of the operating system. */
1367 release_name_from_minor_version(int minor, const char* product_name)
1369 /* We can only do this if we know the official product name. */
1370 if (0 == strcmp(product_name, "Mac OS X"))
1372 /* See https://en.wikipedia.org/wiki/MacOS_version_history#Releases */
1375 case 0: return "Cheetah";
1376 case 1: return "Puma";
1377 case 2: return "Jaguar";
1378 case 3: return "Panther";
1379 case 4: return "Tiger";
1380 case 5: return "Leopard";
1381 case 6: return "Snow Leopard";
1382 case 7: return "Lion";
1383 case 8: return "Mountain Lion";
1384 case 9: return "Mavericks";
1385 case 10: return "Yosemite";
1386 case 11: return "El Capitan";
1387 case 12: return "Sierra";
1388 case 13: return "High Sierra";
1389 case 14: return "Mojave";
1390 case 15: return "Catalina";
1396 /* Construct the release name from information stored in the Mac OS X
1397 "SystemVersion.plist" file (or ServerVersion.plist, for Mac Os
1400 macos_release_name(apr_pool_t *pool)
1402 svn_boolean_t server;
1403 CFDictionaryRef plist = system_version_plist(&server, pool);
1407 const char *osname = value_from_dict(plist, CFSTR("ProductName"), pool);
1408 const char *osver = value_from_dict(plist,
1409 CFSTR("ProductUserVisibleVersion"),
1411 const char *build = value_from_dict(plist,
1412 CFSTR("ProductBuildVersion"),
1414 const char *release;
1418 osver = value_from_dict(plist, CFSTR("ProductVersion"), pool);
1419 minor_version = macos_minor_version(osver);
1420 release = release_name_from_minor_version(minor_version, osname);
1421 osname = product_name_from_minor_version(minor_version, osname);
1424 return apr_psprintf(pool, "%s%s%s%s%s%s%s%s",
1425 (osname ? osname : ""),
1426 (release ? (osname ? " " : "") : ""),
1427 (release ? release : ""),
1428 (osver ? (osname||release ? " " : "") : ""),
1429 (osver ? osver : ""),
1431 ? (osname||release||osver ? ", " : "")
1434 ? (server ? "server build " : "build ")
1436 (build ? build : ""));
1441 #endif /* SVN_HAVE_MACOS_PLIST */
1443 #ifdef SVN_HAVE_MACHO_ITERATE
1444 /* List the shared libraries loaded by the current process.
1445 Ignore frameworks and system libraries, they're just clutter. */
1446 static const apr_array_header_t *
1447 macos_shared_libs(apr_pool_t *pool)
1449 static const char slb_prefix[] = "/usr/lib/system/";
1450 static const char fwk_prefix[] = "/System/Library/Frameworks/";
1451 static const char pfk_prefix[] = "/System/Library/PrivateFrameworks/";
1453 const size_t slb_prefix_len = strlen(slb_prefix);
1454 const size_t fwk_prefix_len = strlen(fwk_prefix);
1455 const size_t pfk_prefix_len = strlen(pfk_prefix);
1457 apr_array_header_t *result = NULL;
1458 apr_array_header_t *dylibs = NULL;
1463 const struct mach_header *header = _dyld_get_image_header(i);
1464 const char *filename = _dyld_get_image_name(i);
1465 const char *version;
1467 svn_version_ext_loaded_lib_t *lib;
1469 if (!(header && filename))
1472 switch (header->cputype)
1474 case CPU_TYPE_I386: version = _("Intel"); break;
1475 case CPU_TYPE_X86_64: version = _("Intel 64-bit"); break;
1476 case CPU_TYPE_POWERPC: version = _("PowerPC"); break;
1477 case CPU_TYPE_POWERPC64: version = _("PowerPC 64-bit"); break;
1482 if (0 == apr_filepath_merge(&truename, "", filename,
1484 | APR_FILEPATH_TRUENAME,
1486 filename = truename;
1488 filename = apr_pstrdup(pool, filename);
1490 if (0 == strncmp(filename, slb_prefix, slb_prefix_len)
1491 || 0 == strncmp(filename, fwk_prefix, fwk_prefix_len)
1492 || 0 == strncmp(filename, pfk_prefix, pfk_prefix_len))
1494 /* Ignore frameworks and system libraries. */
1498 if (header->filetype == MH_EXECUTE)
1500 /* Make sure the program filename is first in the list */
1503 result = apr_array_make(pool, 32, sizeof(*lib));
1505 lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t);
1511 dylibs = apr_array_make(pool, 32, sizeof(*lib));
1513 lib = &APR_ARRAY_PUSH(dylibs, svn_version_ext_loaded_lib_t);
1516 lib->name = filename;
1517 lib->version = version;
1520 /* Gather results into one array. */
1524 apr_array_cat(result, dylibs);
1531 #endif /* SVN_HAVE_MACHO_ITERATE */