Потокобезопасный, реентерабельный, асинхронно-сигнальный сейф

Я заранее прошу прощения за то, что будет немного дамп кода, я обрезал как можно больше неважного кода:

// 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 будет, но у меня есть несколько проблем:

  1. Мой метод обеспечения безопасности при работе с асинхронными сигналами кажется немного сложным, просто блокируя все сигналы (кроме, конечно, SIGKILL/SIGSTOP). Или это самый подходящий способ сделать это?
  2. Расположение моего сигнала слишком консервативно? я знаю strlen не гарантируется безопасность асинхронного сигнала, это означает, что моя блокировка сигнала должна произойти заранее, но, возможно, я ошибаюсь.
  3. Я вполне уверен, что это потокобезопасный, учитывая, что все функции потокобезопасны и что я блокирую взаимодействия с environ, но я бы с радостью доказал обратное.
  4. Я действительно не слишком уверен, является ли это реентерабельным или нет. Хотя это и не гарантировано, но я полагаю, что, если я поставлю галочку в двух других полях, это, скорее всего, будет повторным вводом?

Я нашел другое решение этого вопроса здесь, в котором они просто устанавливают соответствующую блокировку сигнала и блокировку мьютекса (больные рифмы), а затем вызывают 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() или похожие. Если это предположение неверно, поведение не определено.

Другие вопросы по тегам