植绒 C++ Linux 上的 ifstream (GCC 4.6)

发布于 2024-12-23 10:49:44 字数 2287 浏览 6 评论 0 原文

我正在慢慢地用 C++编写

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

我决定以 JSON 格式保留用户“数据库”(用户很少,可能是一两个,因此性能不是问题,O(n) 访问时间是可以接受的),可能是一个 JSON 的小数组对象类似于

 { "_user" : "basile" ;
   "_crypasswd" : "XYZABC123" ; 
   "_email" : "[email protected]" ;
   "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
    }
  }
}

问题

阅读我的密码文件显然,我想 flock< /a> 或 lockf 密码文件,以确保只有一个进程正在读取或写入它。要调用这些函数,我需要获取 ifstream jsinpass 的文件描述符(用 Unix 术语来说)。但谷歌给了我大部分Kreckel's fileno(我发现它很完整,但有点疯狂)获取 std::ifstream 的文件描述符,我不确定构造函数不会预先读取其中的一些。因此我的问题

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

(或者你找到其他方法来解决这个问题吗?)

谢谢

context

I'm slowly writing a specialized web server application in C++ (using the C onion http server library and the JSONCPP library for JSON serialization, if that matters)., for a Linux system with GCC 4.6 compiler (I don't care about portability to non Linux systems, or to GCC before 4.5 or to Clang before 3.0).

I decided to keep the user "database" (there will be very few users, probably one or two, so performance is not a concern, and O(n) access time is acceptable) in JSON format, probably as a small array of JSON objects like

 { "_user" : "basile" ;
   "_crypasswd" : "XYZABC123" ; 
   "_email" : "[email protected]" ;
   "firstname" : "Basile" ;
   "lastname" : "Starynkevitch" ;
   "privileges" : "all" ;
 }

with the convention (à la .htpasswd) that the _crypasswd field is the crypt(3) "encryption" of the user password, salted by the _user name;

The reason I want to describe users by Json objects is that my application might add (not replace) some JSON fields (like e.g. privileges above) in such Json objects describing users. I'm using JsonCpp as a Json parsing library for C++. This library wants an ifstream to be parsed.

So I am reading my password file with

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

Obviously, I want to flock or lockf the password file, to ensure that only one process is reading or writing it. To call these functions, I need to get the file descriptor (in Unix parlance) of the ifstream jsinpass. But Google gives me mostly Kreckel's fileno (which I find complete, but a bit insane) to get the file descriptor of an std::ifstream and I am not sure that the constructor won't pre-read some of it. Hence my question:

how can I lock a C++ ifstream (Linux, GCC 4.6) ?

(Or do you find some other way to tackle that issue?)

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

毅然前行 2024-12-30 10:49:44

我对这个问题的解决方案源自这个答案: 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.
}

My solution to this problem is derived from this answer: https://stackoverflow.com/a/19749019/5899976

I've only tested it with 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.
}
爱人如己 2024-12-30 10:49:44

您可能想要使用单独的锁定文件,而不是尝试从 ifstream 获取描述符。它更容易实现,您可以将 ifstream 包装在一个自动执行此操作的类中。

如果您想确保原子打开/锁定,您可能需要使用这个SO答案中建议的方法构造一个流,如下<代码>开放和<代码>聚集

You might want to use a separate lockfile rather than trying to get the descriptor from the ifstream. It's much easier to implement, and you could probably wrap the ifstream in a class that automates this.

If you want to ensure atomic open/lock, You might want to construct a stream using the method suggested in this SO answer, following open and flock

赏烟花じ飞满天 2024-12-30 10:49:44

文件流 API 的一个缺陷是您无法(至少不能轻松)访问fstream 的文件描述符(请参阅此处此处)。这是因为不要求 fstream 以 FILE* 或文件描述符的形式实现(尽管实际上总是如此)。
这对于使用管道作为 C++ 流也是必需的。

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

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

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

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

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

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

A deficiency with the filestream API is that you cannot (at least not easily) access the file descriptor of an fstream (see here and here, for example). This is because there is no requirement that fstream is implemented in terms of FILE* or file descriptors (though in practice it always is).
This is also required for using pipes as C++ streams.

Therefore the 'canonical' answer (as implied in the comments to the question) is:

create a stream buffer (derived from std::basic_streambuf) that uses Posix and C stdio I/O functions (i.e open etc) and thus gives access to the file descriptor.

Create your own 'LockableFileStream' (derived from std::basic_iostream) using your stdio based stream buffer instead of std::streambuf.

You may now have a fstream like class from which you may gain access to the file descriptor and thus use fcntl (or lockf) as appropriate.

There are a few libraries which provide this out of the box.

I had thought this was addressed partly now that we've reached C++17 but I can't find the link so I must have dreamed it.

不…忘初心 2024-12-30 10:49:44

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

我的意思是,除非您的 JSON 序列化格式支持就地更新(使用事务日志或其他内容),否则更新密码数据库需要重写整个文件,不是吗?那么您不妨将其写入临时文件,然后将其重命名为真实名称,从而确保读者读取到一致的条目? (当然,为了使其工作,每个读者每次想要访问数据库条目时都必须 open() 文件,保持文件打开并不会减少它)

Is the traditional unix-y solution of relying on the atomicity of rename() unacceptable?

I mean, unless your JSON serialization format supports in-place update (with a transaction log or whatever), then updating your password database entails rewriting the entire file, doesn't it? So you might as well write it to a temporary file, then rename it over the real name, thus ensuring that readers read a consistent entry? (Of course, in order for this to work each reader must open() the file each time it wants to access a DB entry, leaving the file open doesn't cut it)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文