diff options
Diffstat (limited to 'subversion/libsvn_subr/sysinfo.c')
-rw-r--r-- | subversion/libsvn_subr/sysinfo.c | 289 |
1 files changed, 262 insertions, 27 deletions
diff --git a/subversion/libsvn_subr/sysinfo.c b/subversion/libsvn_subr/sysinfo.c index a5c302d1e2b7..1f826e9e54b3 100644 --- a/subversion/libsvn_subr/sysinfo.c +++ b/subversion/libsvn_subr/sysinfo.c @@ -1,3 +1,4 @@ + /* * sysinfo.c : information about the running system * @@ -51,10 +52,22 @@ #include "sysinfo.h" #include "svn_private_config.h" +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + #if HAVE_SYS_UTSNAME_H #include <sys/utsname.h> #endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if HAVE_ELF_H +#include <elf.h> +#endif + #ifdef SVN_HAVE_MACOS_PLIST #include <CoreFoundation/CoreFoundation.h> #include <AvailabilityMacros.h> @@ -92,6 +105,7 @@ static const apr_array_header_t *macos_shared_libs(apr_pool_t *pool); #if __linux__ static const char *linux_release_name(apr_pool_t *pool); +static const apr_array_header_t *linux_shared_libs(apr_pool_t *pool); #endif const char * @@ -187,6 +201,8 @@ svn_sysinfo__loaded_libs(apr_pool_t *pool) return win32_shared_libs(pool); #elif defined(SVN_HAVE_MACHO_ITERATE) return macos_shared_libs(pool); +#elif __linux__ + return linux_shared_libs(pool); #else return NULL; #endif @@ -300,6 +316,31 @@ release_name_from_uname(apr_pool_t *pool) #if __linux__ +/* Find the first whitespace character in a stringbuf. + Analogous to svn_stringbuf_first_non_whitespace. */ +static apr_size_t +stringbuf_first_whitespace(const svn_stringbuf_t *str) +{ + apr_size_t i; + for (i = 0; i < str->len; ++i) + { + if (svn_ctype_isspace(str->data[i])) + return i; + } + return str->len; +} + +/* Skip a whitespace-delimited field in a stringbuf. */ +static void +stringbuf_skip_whitespace_field(svn_stringbuf_t *str) +{ + apr_size_t i; + i = stringbuf_first_whitespace(str); + svn_stringbuf_leftchop(str, i); + i = svn_stringbuf_first_non_whitespace(str); + svn_stringbuf_leftchop(str, i); +} + /* Split a stringbuf into a key/value pair. Return the key, leaving the stripped value in the stringbuf. */ static const char * @@ -635,6 +676,168 @@ linux_release_name(apr_pool_t *pool) return apr_psprintf(pool, "%s [%s]", release_name, uname_release); } + +#if HAVE_ELF_H +/* Parse a hexadecimal number as a pointer value. */ +static const unsigned char * +parse_pointer_value(const char *start, const char *limit, char **end) +{ + const unsigned char *ptr; + const apr_uint64_t val = (apr_uint64_t)apr_strtoi64(start, end, 16); + + if (errno /* overflow */ + || *end == start /* no valid digits */ + || *end >= limit) /* representation too long */ + return NULL; + + ptr = (const unsigned char*)val; + if (val != (apr_uint64_t)ptr) /* truncated value */ + return NULL; + + return ptr; +} + +/* Read the ELF header at the mapping position to check if this is a shared + library. We only look at the ELF identification and the type. The format is + described here: + http://www.skyfree.org/linux/references/ELF_Format.pdf +*/ +static svn_boolean_t +check_elf_header(const unsigned char *map_start, + const unsigned char *map_end) +{ + /* A union of all known ELF header types, for size checks. */ + union max_elf_header_size_t + { + Elf32_Ehdr header_32; + Elf64_Ehdr header_64; + }; + + /* Check the size of the mapping and the ELF magic tag. */ + if (map_end < map_start + || map_end - map_start < sizeof(union max_elf_header_size_t) + || memcmp(map_start, ELFMAG, SELFMAG)) + { + return FALSE; + } + + /* Check that this is an ELF shared library or executable file. This also + implicitly checks that the data encoding of the current process is the + same as in the loaded library. */ + if (map_start[EI_CLASS] == ELFCLASS32) + { + const Elf32_Ehdr *hdr = (void*)map_start; + return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC); + } + else if (map_start[EI_CLASS] == ELFCLASS64) + { + const Elf64_Ehdr *hdr = (void*)map_start; + return (hdr->e_type == ET_DYN || hdr->e_type == ET_EXEC); + } + + return FALSE; +} +#endif /* HAVE_ELF_H */ + +static const apr_array_header_t * +linux_shared_libs(apr_pool_t *pool) +{ + /* Read the list of loaded modules from /proc/[pid]/maps + The format is described here: + http://man7.org/linux/man-pages/man5/proc.5.html + */ + + const char *maps = apr_psprintf(pool, "/proc/%ld/maps", (long)getpid()); + apr_array_header_t *result = NULL; + svn_boolean_t eof = FALSE; + svn_stream_t *stream; + svn_error_t *err; + + err = svn_stream_open_readonly(&stream, maps, pool, pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + + /* Each line in /proc/[pid]/maps consists of whitespace-delimited fields. */ + while (!eof) + { + svn_stringbuf_t *line; + +#if HAVE_ELF_H + const unsigned char *map_start; + const unsigned char *map_end; +#endif + + err = svn_stream_readline(stream, &line, "\n", &eof, pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + +#if HAVE_ELF_H + /* Address: The mapped memory address range. */ + { + const char *const limit = line->data + line->len; + char *end; + + /* The start of the address range */ + map_start = parse_pointer_value(line->data, limit, &end); + if (!map_start || *end != '-') + continue; + + /* The end of the address range */ + map_end = parse_pointer_value(end + 1, limit, &end); + if (!map_end || !svn_ctype_isspace(*end)) + continue; + } +#endif + + stringbuf_skip_whitespace_field(line); /* skip address */ + + /* Permissions: The memory region must be readable and executable. */ + if (line->len < 4 || line->data[0] != 'r' || line->data[2] != 'x') + continue; + + stringbuf_skip_whitespace_field(line); /* skip perms */ + stringbuf_skip_whitespace_field(line); /* skip offset */ + stringbuf_skip_whitespace_field(line); /* skip device */ + + /* I-Node: If it is 0, there is no file associated with the region. */ + if (line->len < 2 + || (line->data[0] == '0' && svn_ctype_isspace(line->data[1]))) + continue; + + stringbuf_skip_whitespace_field(line); /* skip inode */ + + /* Consider only things that look like absolute paths. + Files that were removed since the process was created (due to an + upgrade, for example) are marked as '(deleted)'. */ + if (line->data[0] == '/') + { + svn_version_ext_loaded_lib_t *lib; + +#if HAVE_ELF_H + if (!check_elf_header(map_start, map_end)) + continue; +#endif + + /* We've done our best to find a mapped shared library. */ + if (!result) + { + result = apr_array_make(pool, 32, sizeof(*lib)); + } + lib = &APR_ARRAY_PUSH(result, svn_version_ext_loaded_lib_t); + lib->name = line->data; + lib->version = NULL; + } + } + + svn_error_clear(svn_stream_close(stream)); + return result; +} #endif /* __linux__ */ @@ -1122,42 +1325,71 @@ value_from_dict(CFDictionaryRef plist, CFStringRef key, apr_pool_t *pool) return value; } -/* Return the commercial name of the OS, given the version number in +/* Return the minor version the operating system, given the number in a format that matches the regular expression /^10\.\d+(\..*)?$/ */ -static const char * -release_name_from_version(const char *osver) +static int +macos_minor_version(const char *osver) { char *end = NULL; unsigned long num = strtoul(osver, &end, 10); if (!end || *end != '.' || num != 10) - return NULL; + return -1; osver = end + 1; end = NULL; num = strtoul(osver, &end, 10); if (!end || (*end && *end != '.')) - return NULL; + return -1; + + return (int)num; +} - /* See http://en.wikipedia.org/wiki/History_of_OS_X#Release_timeline */ - switch(num) +/* Return the product name of the operating system. */ +static const char * +product_name_from_minor_version(int minor, const char* product_name) +{ + /* We can only do this if we know the official product name. */ + if (0 != strcmp(product_name, "Mac OS X")) + return product_name; + + if (minor <= 7) + return product_name; + + if (minor <= 11) + return "OS X"; + + return "macOS"; +} + +/* Return the commercial name of the operating system. */ +static const char * +release_name_from_minor_version(int minor, const char* product_name) +{ + /* We can only do this if we know the official product name. */ + if (0 == strcmp(product_name, "Mac OS X")) { - case 0: return "Cheetah"; - case 1: return "Puma"; - case 2: return "Jaguar"; - case 3: return "Panther"; - case 4: return "Tiger"; - case 5: return "Leopard"; - case 6: return "Snow Leopard"; - case 7: return "Lion"; - case 8: return "Mountain Lion"; - case 9: return "Mavericks"; - case 10: return "Yosemite"; - case 11: return "El Capitan"; - case 12: return "Sierra"; - case 13: return "High Sierra"; + /* See https://en.wikipedia.org/wiki/MacOS_version_history#Releases */ + switch(minor) + { + case 0: return "Cheetah"; + case 1: return "Puma"; + case 2: return "Jaguar"; + case 3: return "Panther"; + case 4: return "Tiger"; + case 5: return "Leopard"; + case 6: return "Snow Leopard"; + case 7: return "Lion"; + case 8: return "Mountain Lion"; + case 9: return "Mavericks"; + case 10: return "Yosemite"; + case 11: return "El Capitan"; + case 12: return "Sierra"; + case 13: return "High Sierra"; + case 14: return "Mojave"; + case 15: return "Catalina"; + } } - return NULL; } @@ -1180,20 +1412,23 @@ macos_release_name(apr_pool_t *pool) CFSTR("ProductBuildVersion"), pool); const char *release; + int minor_version; if (!osver) osver = value_from_dict(plist, CFSTR("ProductVersion"), pool); - release = release_name_from_version(osver); + minor_version = macos_minor_version(osver); + release = release_name_from_minor_version(minor_version, osname); + osname = product_name_from_minor_version(minor_version, osname); CFRelease(plist); return apr_psprintf(pool, "%s%s%s%s%s%s%s%s", (osname ? osname : ""), - (osver ? (osname ? " " : "") : ""), - (osver ? osver : ""), - (release ? (osname||osver ? " " : "") : ""), + (release ? (osname ? " " : "") : ""), (release ? release : ""), + (osver ? (osname||release ? " " : "") : ""), + (osver ? osver : ""), (build - ? (osname||osver||release ? ", " : "") + ? (osname||release||osver ? ", " : "") : ""), (build ? (server ? "server build " : "build ") |