マルチスレッドと環境変数

BBv2のjamファイルでテストしようと思ったら、環境変数をサポートしていなくて全然動きませんでした。
というわけでbjam::contextに環境変数を追加しました。


環境変数の一覧は、WindowsではGetEnvironmentStrings()で、POSIXではchar** environという大域変数で取得できます。
Windowsの場合は一つの関数呼び出しで全ての環境変数を取得できますが、POSIXの場合は大域変数なのでマルチスレッド環境では各要素を参照する間に環境変数が変更されないこと保証できなければいけません。
これはライブラリを実装する上では大きな障害となります。
上のコードではこれを無視していますが、一応対策を考えたので貼っておきます。
(動作未確認Cygwin on Windows Vistaではクラッシュ、SF.jpのコンパイルファーム(Linux)で動作確認済み)

#include <boost/noncopyable.hpp>
#include <boost/scoped_array.hpp>
#include <cstring>
#include <map>
#include <stdexcept>
#include <string>
#include <sys/wait.h>
#include <unistd.h>

namespace hamigaki { namespace detail { namespace posix {

namespace impl
{

// environ受け渡し用パイプ
class pipe_for_environ : private boost::noncopyable
{
public:
    pipe_for_environ()
    {
        if (::pipe(fds_) == -1)
            throw std::runtime_error("pipe() failed");
    }

    ~pipe_for_environ()
    {
        close_read_end();
        close_write_end();
    }

    void close_read_end()
    {
        if (fds_[0] != -1)
        {
            ::close(fds_[0]);
            fds_[0] = -1;
        }
    }

    void close_write_end()
    {
        if (fds_[1] != -1)
        {
            ::close(fds_[1]);
            fds_[1] = -1;
        }
    }

    std::size_t read_size_t()
    {
        char buf[sizeof(std::size_t)];
        std::size_t size;

        if (::read(fds_[0], buf, sizeof(buf)) != sizeof(buf))
            return 0;
        std::memcpy(&size, buf, sizeof(buf));

        return size;
    }

    void write_size_t(std::size_t n)
    {
        char buf[sizeof(std::size_t)];

        std::memcpy(buf, &n, sizeof(buf));
        ::write(fds_[1], buf, sizeof(buf));
    }

    void read_string(char* s, std::size_t n)
    {
        ::read(fds_[0], s, n);
    }

    void write_string(const char* s, std::size_t n)
    {
        ::write(fds_[1], s, n);
    }

private:
    int fds_[2];
};

// forkのラッパー
class fork_process : private boost::noncopyable
{
public:
    static const ::pid_t invalid_pid = static_cast< ::pid_t>(-1);

    fork_process() : pid_(::fork())
    {
        if (pid_ == invalid_pid)
            throw std::runtime_error("fork() failed");
    }

    ~fork_process()
    {
        if (pid_ == 0)
            ::_exit(0);
        else if (pid_ != invalid_pid)
        {
            ::kill(pid_, SIGKILL);
            ::waitpid(pid_, 0, 0);
        }
    }

    bool is_child() const
    {
        return pid_ == 0;
    }

    void wait_child()
    {
        if ((pid_ != 0) && (pid_ != invalid_pid))
        {
            ::waitpid(pid_, 0, 0);
            pid_ = invalid_pid;
        }
    }

private:
    ::pid_t pid_;
};

} // namespace impl

inline void get_environment_strings(std::map<std::string,std::string>& table)
{
    impl::pipe_for_environ pe;
    impl::fork_process fp;
    if (fp.is_child())
    {
        pe.close_read_end();
        for (char** p = environ; p; ++p)
        {
            const char* s = *p;
            std::size_t size = std::strlen(s);

            pe.write_size_t(size);
            pe.write_string(s, size);
        }
        pe.close_write_end();
    }
    else
    {
        pe.close_write_end();
        std::size_t size;
        while (size = pe.read_size_t())
        {
            boost::scoped_array<char> buf(new char[size+1]);
            pe.read_string(buf.get(), size);
            buf[size] = '\0';

            const char* s = buf.get();
            if (const char* delim = std::strchr(s, '='))
                table[std::string(s, delim-s)].assign(delim+1);
        }
        pe.close_read_end();
        fp.wait_child();
    }
}

} } } // End namespaces posix, detail, hamigaki.

要するにfork()すれば、大域変数にアクセスできるスレッドが一つであることを保障できるということです。無茶苦茶大げさですが。
かなり複雑ですが、これでもread/writeのエラー処理は端折っています。
あと、SIGPIPEの処理もいるので結局大域状態に依存するのかなぁ。