стекаются с C ++ ifstream в Linux (GCC 4.6) - PullRequest
5 голосов
/ 29 декабря 2011

context

Я медленно пишу специализированное приложение веб-сервера на C ++ (используя библиотеку C onion http * и библиотеку JSONCPP для сериализации JSON,если это имеет значение). для системы Linux с компилятором GCC 4.6 (меня не волнует переносимость в системы, отличные от Linux, или в GCC до 4.5 или в Clang до 3.0).

Я решил оставитьпользовательская «база данных» (пользователей будет очень мало, возможно, один или два, поэтому производительность не имеет значения, а время доступа O (n) приемлемо) в формате JSON, вероятно, в виде небольшого массива объектов JSON, таких как

 { "_user" : "basile" ;
   "_crypasswd" : "XYZABC123" ; 
   "_email" : "basile@starynkevitch.net" ;
   "firstname" : "Basile" ;
   "lastname" : "Starynkevitch" ;
   "privileges" : "all" ;
 }

с соглашением (а-ля .htpasswd), что поле _crypasswd является crypt (3) «шифрованием» пароля пользователя, с добавлением имени _user;

Причина, по которой я хочу описывать пользователей объектами Json, заключается в том, что мое приложение может добавлять (а не заменять) некоторые поля JSON (например, privileges выше) в такие объекты Json, описывающие пользователей.Я использую JsonCpp в качестве библиотеки синтаксического анализа Json для C ++.Эта библиотека хочет, чтобы ifstream был проанализирован.

Итак, я читаю мой файл паролей с

extern char* iaca_passwd_path; // the path of the password file
std::ifstream jsinpass(iaca_passwd_path);
Json::Value jpassarr;
Json::Reader reader;
reader.parse(jsinpass,jpassarr,true);
jsinpass.close();
assert (jpassarr.isArray());
for (int ix=0; ix<nbu; ix++) {
  const Json::Value&jcuruser= jpassarr[ix];
  assert(jcuruser.isObject());
  if (jcuruser["_user"].compare(user) == 0) {
    std::string crypasswd = jcuruser["_crypasswd"].asString();
    if (crypasswd.compare(crypted_password(user,password)) == 0) {
         // good user
    }
  }
}

question

Очевидно, я хочу flock или lockf файл пароля, чтобы гарантировать, что только один процесс читает или записывает его.Чтобы вызвать эти функции, мне нужно получить дескриптор файла (на языке Unix) ifstream jsinpass.Но Google дает мне в основном файл Крекеля (который я считаю полным, но немного безумным), чтобы получить файловый дескриптор std::ifstream, и я не уверен, что конструктор не будет предварительно читать некоторые изЭто.Отсюда мой вопрос :

как мне заблокировать C ++ ifstream (Linux, GCC 4.6)?

(или вы нашли какой-то другой способ решения этой проблемы??)

Спасибо

Ответы [ 4 ]

1 голос
/ 09 декабря 2018

Мое решение этой проблемы вытекает из этого ответа: https://stackoverflow.com/a/19749019/5899976

Я тестировал его только с GCC 4.8.5.

#include <cstring>  // for strerror()
#include <iostream> // for std::cerr
#include <fstream>
#include <ext/stdio_filebuf.h>

extern "C" {
#include <errno.h>
#include <sys/file.h>  // for flock()
}

    // Atomically increments a persistent counter, stored in /tmp/counter.txt
int increment_counter()
{
    std::fstream file( "/tmp/counter.txt" );
    if (!file) file.open( "/tmp/counter.txt", std::fstream::out );

    int fd = static_cast< __gnu_cxx::stdio_filebuf< char > * const >( file.rdbuf() )->fd();
    if (flock( fd, LOCK_EX ))
    {
        std::cerr << "Failed to lock file: " << strerror( errno ) << "\n";
    }

    int value = 0;
    file >> value;
    file.clear();   // clear eof bit.
    file.seekp( 0 );
    file << ++value;

    return value;

    // When 'file' goes out of scope, it's closed.  Moreover, since flock() is
    //  tied to the file descriptor, it gets released when the file is closed.
}
1 голос
/ 05 августа 2017

Недостаток API файлового потока в том, что вы не можете (по крайней мере, не легко ) получить доступ к файловому дескриптору fstream (см. здесь и здесь , например). Это потому, что нет требования, чтобы fstream был реализован в терминах FILE * или файловых дескрипторов (хотя на практике это всегда так). Это также требуется для использования каналов в качестве потоков C ++.

Поэтому «канонический» ответ (как подразумевается в комментариях к вопросу):

создает потоковый буфер (производный от std :: basic_streambuf), который использует функции ввода / вывода Posix и C stdio (т.е. открытые и т. Д.) И, таким образом, предоставляет доступ к дескриптору файла.

Создайте свой собственный 'LockableFileStream' (производный от std :: basic_iostream), используя ваш потоковый буфер на основе stdio вместо std :: streambuf. * ​​1015 *

Теперь у вас может быть класс, похожий на fstream, из которого вы можете получить доступ к дескриптору файла и, таким образом, использовать fcntl (или lockf) в зависимости от ситуации.

Есть несколько библиотек, которые предоставляют это из коробки.

Я думал, что это частично решено теперь, когда мы достигли C ++ 17, но я не могу найти ссылку, поэтому, должно быть, мне это приснилось.

1 голос
/ 29 декабря 2011

Возможно, вы захотите использовать отдельный файл блокировки, а не пытаться получить дескриптор из ifstream. Это гораздо проще реализовать, и вы, возможно, могли бы обернуть ifstream в класс, который автоматизирует это.

Если вы хотите обеспечить атомарное открытие / блокировку, вы можете создать поток, используя метод, предложенный в этом SO-ответе , после open и flock

0 голосов
/ 29 декабря 2011

Является ли неприемлемым традиционное решение unix-y, основанное на атомарности rename ()?

Я имею в виду, если ваш формат сериализации JSON не поддерживает обновление на месте (с журналом транзакций или чем-то еще), то обновление базы данных паролей влечет за собой перезапись всего файла, не так ли? Таким образом, вы могли бы также записать его во временный файл, а затем переименовать его по реальному имени, таким образом гарантируя, что читатели прочитают последовательную запись? (Конечно, для того, чтобы это работало, каждый читатель должен открывать () файл каждый раз, когда он хочет получить доступ к записи в БД, а оставленный файл открытым не обрезает его)

...