Потокобезопасный, реентерабельный, асинхронно-сигнальный сейф
Я заранее прошу прощения за то, что будет немного дамп кода, я обрезал как можно больше неважного кода:
// Global vars / mutex stuff
extern char **environ;
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;
int
putenv_r(char *string)
{
int len;
int key_len = 0;
int i;
sigset_t block;
sigset_t old;
sigfillset(&block);
pthread_sigmask(SIG_BLOCK, &block, &old);
// This function is thread-safe
len = strlen(string);
for (int i=0; i < len; i++) {
if (string[i] == '=') {
key_len = i; // Thanks Klas for pointing this out.
break;
}
}
// Need a string like key=value
if (key_len == 0) {
errno = EINVAL; // putenv doesn't normally return this err code
return -1;
}
// We're moving into environ territory so start locking stuff up.
pthread_mutex_lock(&env_mutex);
for (i = 0; environ[i] != NULL; i++) {
if (strncmp(string, environ[i], key_len) == 0) {
// Pointer assignment, so if string changes so does the env.
// This behaviour is POSIX conformant, instead of making a copy.
environ[i] = string;
pthread_mutex_unlock(&env_mutex);
return(0);
}
}
// If we get here, the env var didn't already exist, so we add it.
// Note that malloc isn't async-signal safe. This is why we block signals.
environ[i] = malloc(sizeof(char *));
environ[i] = string;
environ[i+1] = NULL;
// This ^ is possibly incorrect, do I need to grow environ some how?
pthread_mutex_unlock(&env_mutex);
pthread_sigmask(SIG_SETMASK, &old, NULL);
return(0);
}
Как видно из названия, я пытаюсь закодировать потокобезопасную, безопасную асинхронную сигнальную реентерабельную версию putenv
, Код работает в том, что он устанавливает переменную окружения как putenv
будет, но у меня есть несколько проблем:
- Мой метод обеспечения безопасности при работе с асинхронными сигналами кажется немного сложным, просто блокируя все сигналы (кроме, конечно, SIGKILL/SIGSTOP). Или это самый подходящий способ сделать это?
- Расположение моего сигнала слишком консервативно? я знаю
strlen
не гарантируется безопасность асинхронного сигнала, это означает, что моя блокировка сигнала должна произойти заранее, но, возможно, я ошибаюсь. - Я вполне уверен, что это потокобезопасный, учитывая, что все функции потокобезопасны и что я блокирую взаимодействия с
environ
, но я бы с радостью доказал обратное. - Я действительно не слишком уверен, является ли это реентерабельным или нет. Хотя это и не гарантировано, но я полагаю, что, если я поставлю галочку в двух других полях, это, скорее всего, будет повторным вводом?
Я нашел другое решение этого вопроса здесь, в котором они просто устанавливают соответствующую блокировку сигнала и блокировку мьютекса (больные рифмы), а затем вызывают putenv
обычно. Это действительно? Если так, то это, очевидно, намного проще, чем мой подход.
Извините за большой блок кода, я надеюсь, что установил MCVE. Я пропускаю немного проверки ошибок в моем коде для краткости. Спасибо!
Вот остальная часть кода, включая основной, если вы хотите протестировать код самостоятельно:
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
// Prototypes
static void thread_init(void);
int putenv_r(char *string);
int
main(int argc, char *argv[]) {
int ret = putenv_r("mykey=myval");
printf("%d: mykey = %s\n", ret, getenv("mykey"));
return 0;
}
1 ответ
Этот код является проблемой:
// If we get here, the env var didn't already exist, so we add it.
// Note that malloc isn't async-signal safe. This is why we block signals.
environ[i] = malloc(sizeof(char *));
environ[i] = string;
Это создает char *
в куче, назначает адрес того char *
в environ[i]
затем перезаписывает это значение адресом, содержащимся в string
, Это не сработает. Это не гарантирует, что environ
NULL-завершается впоследствии.
Так как char **environ
это указатель на массив указателей. Последний указатель в массиве NULL
- так код может сказать, что он достиг конца списка переменных среды.
Примерно так должно работать лучше:
unsigned int envCount;
for ( envCount = 0; environ[ envCount ]; envCount++ )
{
/* empty loop */;
}
/* since environ[ envCount ] is NULL, the environ array
of pointers has envCount + 1 elements in it */
envCount++;
/* grow the environ array by one pointer */
char ** newEnviron = realloc( environ, ( envCount + 1 ) * sizeof( char * ) );
/* add the new envval */
newEnviron[ envCount - 1 ] = newEnvval;
/* NULL-terminate the array of pointers */
newEnviron[ envCount ] = NULL;
environ = newEnviron;
Обратите внимание, что нет проверки ошибок, и она предполагает оригинал environ
массив был получен с помощью вызова malloc()
или похожие. Если это предположение неверно, поведение не определено.