diff options
author | Arjun Shankar <arjun@redhat.com> | 2018-10-17 17:47:29 +0200 |
---|---|---|
committer | Arjun Shankar <arjun@redhat.com> | 2018-10-17 17:47:29 +0200 |
commit | c5288d378ad258d2e8e8cb174be3b9f233a312eb (patch) | |
tree | a987e28461c55a9d7011f046f382beaaa48cc93a /iconv/gconv_conf.c | |
parent | 729f34028a7f494b599a29889df825cf826b6de0 (diff) | |
download | glibc-c5288d378ad258d2e8e8cb174be3b9f233a312eb.tar.gz glibc-c5288d378ad258d2e8e8cb174be3b9f233a312eb.tar.xz glibc-c5288d378ad258d2e8e8cb174be3b9f233a312eb.zip |
Remove unnecessary locking when reading iconv configuration [BZ #22062]
In iconv/gconv_conf.c, __gconv_get_path unnecessarily obtains a lock when populating the array pointed to by __gconv_path_elem. The locking is not necessary because all calls to __gconv_read_conf (which in turn calls __gconv_get_path) are serialized using __libc_once. This patch: - removes all locking in __gconv_get_path; - replaces all explicitly serialized __gconv_read_conf calls with calls to __gconv_load_conf, a new wrapper that is serialized internally; - adds a new test, iconv/tst-iconv_mt.c, to exercise iconv initialization, usage, and cleanup in a multi-threaded program; - indents __gconv_get_path correctly, removing tab characters (which makes the patch look a little bigger than it really is). After removing the unnecessary locking, it was confirmed that the test case fails if the relevant __libc_once is removed. Additionally, four localedata and iconvdata tests also fail. This gives confidence that the testsuite sufficiently guards against some regressions relating to multi-threading with iconv. Tested on x86_64 and i686.
Diffstat (limited to 'iconv/gconv_conf.c')
-rw-r--r-- | iconv/gconv_conf.c | 208 |
1 files changed, 109 insertions, 99 deletions
diff --git a/iconv/gconv_conf.c b/iconv/gconv_conf.c index ce9f10f3af..78010491e6 100644 --- a/iconv/gconv_conf.c +++ b/iconv/gconv_conf.c @@ -426,119 +426,115 @@ read_conf_file (const char *filename, const char *directory, size_t dir_len, } -/* Determine the directories we are looking for data in. */ +/* Determine the directories we are looking for data in. This function should + only be called from __gconv_read_conf. */ static void __gconv_get_path (void) { struct path_elem *result; - __libc_lock_define_initialized (static, lock); - __libc_lock_lock (lock); - - /* Make sure there wasn't a second thread doing it already. */ - result = (struct path_elem *) __gconv_path_elem; - if (result == NULL) + /* This function is only ever called when __gconv_path_elem is NULL. */ + result = __gconv_path_elem; + assert (result == NULL); + + /* Determine the complete path first. */ + char *gconv_path; + size_t gconv_path_len; + char *elem; + char *oldp; + char *cp; + int nelems; + char *cwd; + size_t cwdlen; + + if (__gconv_path_envvar == NULL) { - /* Determine the complete path first. */ - char *gconv_path; - size_t gconv_path_len; - char *elem; - char *oldp; - char *cp; - int nelems; - char *cwd; - size_t cwdlen; - - if (__gconv_path_envvar == NULL) - { - /* No user-defined path. Make a modifiable copy of the - default path. */ - gconv_path = strdupa (default_gconv_path); - gconv_path_len = sizeof (default_gconv_path); - cwd = NULL; - cwdlen = 0; - } - else - { - /* Append the default path to the user-defined path. */ - size_t user_len = strlen (__gconv_path_envvar); - - gconv_path_len = user_len + 1 + sizeof (default_gconv_path); - gconv_path = alloca (gconv_path_len); - __mempcpy (__mempcpy (__mempcpy (gconv_path, __gconv_path_envvar, - user_len), - ":", 1), - default_gconv_path, sizeof (default_gconv_path)); - cwd = __getcwd (NULL, 0); - cwdlen = __glibc_unlikely (cwd == NULL) ? 0 : strlen (cwd); - } - assert (default_gconv_path[0] == '/'); - - /* In a first pass we calculate the number of elements. */ - oldp = NULL; - cp = strchr (gconv_path, ':'); - nelems = 1; - while (cp != NULL) - { - if (cp != oldp + 1) - ++nelems; - oldp = cp; - cp = strchr (cp + 1, ':'); - } - - /* Allocate the memory for the result. */ - result = (struct path_elem *) malloc ((nelems + 1) - * sizeof (struct path_elem) - + gconv_path_len + nelems - + (nelems - 1) * (cwdlen + 1)); - if (result != NULL) - { - char *strspace = (char *) &result[nelems + 1]; - int n = 0; - - /* Separate the individual parts. */ - __gconv_max_path_elem_len = 0; - elem = __strtok_r (gconv_path, ":", &gconv_path); - assert (elem != NULL); - do - { - result[n].name = strspace; - if (elem[0] != '/') - { - assert (cwd != NULL); - strspace = __mempcpy (strspace, cwd, cwdlen); - *strspace++ = '/'; - } - strspace = __stpcpy (strspace, elem); - if (strspace[-1] != '/') - *strspace++ = '/'; - - result[n].len = strspace - result[n].name; - if (result[n].len > __gconv_max_path_elem_len) - __gconv_max_path_elem_len = result[n].len; - - *strspace++ = '\0'; - ++n; - } - while ((elem = __strtok_r (NULL, ":", &gconv_path)) != NULL); - - result[n].name = NULL; - result[n].len = 0; - } + /* No user-defined path. Make a modifiable copy of the + default path. */ + gconv_path = strdupa (default_gconv_path); + gconv_path_len = sizeof (default_gconv_path); + cwd = NULL; + cwdlen = 0; + } + else + { + /* Append the default path to the user-defined path. */ + size_t user_len = strlen (__gconv_path_envvar); + + gconv_path_len = user_len + 1 + sizeof (default_gconv_path); + gconv_path = alloca (gconv_path_len); + __mempcpy (__mempcpy (__mempcpy (gconv_path, __gconv_path_envvar, + user_len), + ":", 1), + default_gconv_path, sizeof (default_gconv_path)); + cwd = __getcwd (NULL, 0); + cwdlen = __glibc_unlikely (cwd == NULL) ? 0 : strlen (cwd); + } + assert (default_gconv_path[0] == '/'); - __gconv_path_elem = result ?: (struct path_elem *) &empty_path_elem; + /* In a first pass we calculate the number of elements. */ + oldp = NULL; + cp = strchr (gconv_path, ':'); + nelems = 1; + while (cp != NULL) + { + if (cp != oldp + 1) + ++nelems; + oldp = cp; + cp = strchr (cp + 1, ':'); + } - free (cwd); + /* Allocate the memory for the result. */ + result = malloc ((nelems + 1) + * sizeof (struct path_elem) + + gconv_path_len + nelems + + (nelems - 1) * (cwdlen + 1)); + if (result != NULL) + { + char *strspace = (char *) &result[nelems + 1]; + int n = 0; + + /* Separate the individual parts. */ + __gconv_max_path_elem_len = 0; + elem = __strtok_r (gconv_path, ":", &gconv_path); + assert (elem != NULL); + do + { + result[n].name = strspace; + if (elem[0] != '/') + { + assert (cwd != NULL); + strspace = __mempcpy (strspace, cwd, cwdlen); + *strspace++ = '/'; + } + strspace = __stpcpy (strspace, elem); + if (strspace[-1] != '/') + *strspace++ = '/'; + + result[n].len = strspace - result[n].name; + if (result[n].len > __gconv_max_path_elem_len) + __gconv_max_path_elem_len = result[n].len; + + *strspace++ = '\0'; + ++n; + } + while ((elem = __strtok_r (NULL, ":", &gconv_path)) != NULL); + + result[n].name = NULL; + result[n].len = 0; } - __libc_lock_unlock (lock); + __gconv_path_elem = result ?: (struct path_elem *) &empty_path_elem; + + free (cwd); } /* Read all configuration files found in the user-specified and the default - path. */ -void -attribute_hidden + path. This function should only be called once during the program's + lifetime. It disregards locking and synchronization because its only + caller, __gconv_load_conf, handles this. */ +static void __gconv_read_conf (void) { void *modules = NULL; @@ -609,6 +605,20 @@ __gconv_read_conf (void) } +/* This "once" variable is used to do a one-time load of the configuration. */ +__libc_once_define (static, once); + + +/* Read all configuration files found in the user-specified and the default + path, but do it only "once" using __gconv_read_conf to do the actual + work. This is the function that must be called when reading iconv + configuration. */ +void +__gconv_load_conf (void) +{ + __libc_once (once, __gconv_read_conf); +} + /* Free all resources if necessary. */ libc_freeres_fn (free_mem) |