6

语境

我正在慢慢地用 C++ 编写一个专门的 Web 服务器应用程序(使用 C onion http 服务器库JSONCPP 库进行 JSON 序列化,如果这很重要的话)。对于带有 GCC 4.6 编译器的 Linux 系统(我不关心可移植性到非 Linux 系统,或 4.5 之前的 GCC 或 3.0 之前的 Clang)。

我决定将用户“数据库”(用户很少,可能只有一两个,所以性能不是问题,并且 O(n) 访问时间是可以接受的)以 JSON 格式保存,可能是 JSON 的小数组像这样的物体

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

按照惯例(à la .htpasswd),该_crypasswd字段是用户密码的crypt(3) “加密”,按_user名称加盐;

我想通过 Json 对象来描述用户的原因是我的应用程序可能会在描述用户的此类 Json 对象中添加(而不是替换)一些 JSON 字段(例如privileges上面的)。我使用JsonCpp作为 C++ 的 Json 解析库。这个库想要一个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
    }
  }
}

问题

显然,我想flocklockf密码文件,以确保只有一个进程正在读取或写入它。要调用这些函数,我需要获取ifstream jsinpass. 但是谷歌主要给了我Kreckel 的 fileno(我觉得它很完整,但有点疯狂)来获取 an 的文件描述符,std::ifstream我不确定构造函数不会预先读取其中的一些。因此我的问题是

如何锁定 C++ ifstream(Linux、GCC 4.6)?

(或者您是否找到其他方法来解决该问题?)

谢谢

4

4 回答 4

2

我对这个问题的解决方案来自这个答案: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.
}
于 2018-12-09T03:20:52.687 回答
1

您可能希望使用单独的锁定文件,而不是尝试从ifstream. 它更容易实现,您可以将其包装ifstream在一个自动化的类中。

如果您想确保原子打开/锁定,您可能希望使用此 SO 答案中建议的方法构造一个流,如下openflock

于 2011-12-29T12:32:46.927 回答
1

filestream API 的一个缺陷是您不能(至少不容易)访问 fstream 的文件描述符(例如,请参见此处此处)。这是因为不要求 fstream 是根据 FILE* 或文件描述符实现的(尽管实际上它总是如此)。这也是将管道用作 C++ 流所必需的。

因此,“规范”的答案(正如对问题的评论所暗示的那样)是:

创建一个使用 Posix 和 C stdio I/O 函数(即 open 等)的流缓冲区(从 std::basic_streambuf 派生),从而可以访问文件描述符。

使用基于 stdio 的流缓冲区而不是 std::streambuf 创建自己的“LockableFileStream”(源自 std::basic_iostream)。

您现在可能有一个类似 fstream 的类,您可以从中获得对文件描述符的访问权限,从而酌情使用 fcntl(或 lockf)。

有一些库提供了开箱即用的功能。

我原以为现在我们已经达到 C++17 已经部分解决了这个问题,但我找不到链接,所以我一定是在做梦。

于 2017-08-05T18:09:54.617 回答
0

依赖 rename() 的原子性的传统 unix-y 解决方案是不能接受的吗?

我的意思是,除非您的 JSON 序列化格式支持就地更新(使用事务日志或其他),否则更新您的密码数据库需要重写整个文件,不是吗?所以不如先写到一个临时文件里,然后改名改成真名,从而保证读者读到的条目一致?(当然,为了使这个工作,每个读者必须在每次想要访问数据库条目时打开()文件,让文件保持打开状态并不会切断它)

于 2011-12-29T12:55:12.553 回答