首页 > 解决方案 > 使用 fcntl 和 C++ 锁定 Linux 文件

问题描述

我在网上搜索了两个月,以寻找在 C++ 程序中使用的适当文件锁定机制。

我在“C and fnctl”上发现了很多我可以证明可以工作的东西。但是,我可以证明在 Linux 中工作的所有真正正确的工作锁定机制都仅基于文件描述符。

由于这似乎是一种非常过时的东西,并且在实际的 C++17 风格中使用文件和 ip 流编写 C++ 代码而不使用该机制,所以我只想出了一些可以使用这里介绍的东西的东西:

无法使用 __gnu_cxx::stdio_filebuf 进行流式传输

我的问题是,这真的是唯一有效的机制吗?连接两个世界?

我查看了所有这些书籍以找到有关 fcntl 和 C++ 的任何信息,但没有成功:

我在这里向 C++ 大师提出的问题是,如果我遗漏了什么,或者下面的代码是,今天,2021 年开始,我们能做的最好的事情。

代码证明的简短说明:

我们有一个 C++ 代码,它将用户名及其 LSF 进程添加到一个 conf 文件中,SSH 服务器读取该文件以允许用户访问该机器。由于此代码的两个或多个运行进程可能导致同时尝试从该文件添加或删除用户,因此我们必须证明适当的文件锁定可以防止这种情况发生。不使用额外的“访问”文件,这也可能是一个解决方案。

这是我测试的一些示例代码:


#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <fcntl.h>
#include <unistd.h>
#include <ext/stdio_filebuf.h>

using namespace std::this_thread;  // for sleep_for

int main( ) {
  // set unbuffered concole output
  std::cout.setf(std::ios::unitbuf);

  const char* filename {"testfile.txt"};

  // get input from input_from_user

  std::string input_from_user_string;
  std::cout << "Please give input to change in the file: ";
  std::cin >> input_from_user_string;

  int add_1_del_2 = 0;
  std::cout << "Please give 1 if you want to add to the file or 2 if you want to delete from file: ";
  std::cin >> add_1_del_2;

  int input_from_user_time;
  std::cout << "Please give seconds to wait: ";
  std::cin >> input_from_user_time;


  // opening file

  std::cout << "Opening File" << std::endl;

  mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH; //664

  int fd;
  fd = open(filename, O_RDWR | O_CREAT, mode);

  // printing out information about file descriptor
  std::cout << " Dexc:" << fd << std::endl;

  // generating C++-streams on filedescriptor
  __gnu_cxx::stdio_filebuf<char> sourcebufin(fd, std::ios::in);
  __gnu_cxx::stdio_filebuf<char> sourcebufout(fd, std::ios::out);
  std::istream myfilein(&sourcebufin);
  std::ostream myfileout(&sourcebufout);


  // -----------
  // check for file Locking or exit
  // -----------

  // creating structure for file locking
  struct flock fl;
  fl.l_type = F_RDLCK;
  fl.l_whence = SEEK_SET;
  fl.l_start = 0;
  fl.l_len = 0;

  // set file locking for read
  fl.l_type = F_RDLCK;


  std::cout << "Checking for Lock on file" << std::endl;

  // check for file locking on file for read only once

  (void) fcntl(fd, F_GETLK, &fl);

  if (fl.l_type != F_UNLCK) {
    std::cout << "File is locked for reading by process "
         << fl.l_pid
         << ", in status"
         << ((fl.l_type == F_WRLCK) ? 'W' : 'R')
         << ", start="
         << fl.l_start
         << ", end="
         << fl.l_len
         << std::endl;
  }
  else {
    (void) printf("File is unlocked for reading\n");
  }

  // set file locking for write
  fl.l_type = F_WRLCK;

  // check for file locking on file for write in a loop
  for (int i = 1; i < 11; i++) {
    //printf("Checking for lock %d of 10 times...\n", i);
    std::cout << "Checking for lock "
         << i
         << " of 10 times..."
         << std::endl;
    (void) fcntl(fd, F_GETLK, &fl);

    if (fl.l_type != F_UNLCK) {
      //(void) printf("File is locked by process %d, in status %c, start=%8ld, end=%8ld\n", fl.l_pid,
      //      , fl.l_start, fl.l_len);
      std::cout << "File is locked by process "
           << fl.l_pid
           << ", in status"
           << ((fl.l_type == F_WRLCK) ? 'W' : 'R')
           << ", start="
           << fl.l_start
           << ", end="
           << fl.l_len
           << std::endl;
      sleep(10);
    }
    else {
      (void) printf("File is unlocked\n");
      break;
    }
  }



  // -----------
  // apply lock for write on file
  // -----------

  // locking file

  std::cout << "Locking file for write" << std::endl;

  // set file locking for write again, as checking on lock resets it
  fl.l_type = F_WRLCK;

  if (fcntl(fd, F_SETLKW, &fl) == -1) {
    perror("fcntl");
    abort();
  }



  // -----------
  // wait some time
  // -----------

  std::cout << "Now waiting for " << input_from_user_time << " seconds, keeping the file locked..." << std::endl;
  std::this_thread::sleep_for(std::chrono::seconds(input_from_user_time));



  // -----------
  // read from file
  // -----------

  std::cout << "Reading from file... " << std::endl;
  myfilein.seekg(0, std::ios::end);
  size_t size_before = myfilein.tellg();
  myfilein.seekg(0);

  std::string filecontent{""};
  filecontent.reserve(size_before);

  std::cout << "Length of file is: " << size_before << std::endl;

  // read full content of file in string "filecontent"
  filecontent.assign((std::istreambuf_iterator<char>(myfilein)),
            std::istreambuf_iterator<char>());



  // -----------
  // print output about read data
  // -----------

  std::cout << "Length of filecontent-string: " << filecontent.size() << std::endl;

  std::cout << "Content of File begin" << std::endl;
  std::cout << "----------" << std::endl;
  std::cout << filecontent << std::endl;
  std::cout << "----------" << std::endl;



  // -----------
  // Apply changes on read in data depending on given input
  // -----------

  if (add_1_del_2 == 2) {
    std::cout << "Runmode: Del" << std::endl;
    std::string string_to_delete = input_from_user_string+"\n";
    std::string::size_type pos_of_found_substring = filecontent.find(string_to_delete);

    if (pos_of_found_substring != std::string::npos) {
      filecontent.erase(pos_of_found_substring, string_to_delete.length());
    }
    else {

    }

  }

  if (add_1_del_2 == 1) {
    std::cout << "Runmode: Append" << std::endl;
    filecontent.append(input_from_user_string);
  }

  std::cout << "Content of String after change" << std::endl;
  std::cout << "----------" << std::endl;
  std::cout << filecontent << std::endl;
  std::cout << "----------" << std::endl;



  // -----------
  // write out to file, truncate before to length of new string
  // -----------

  std::cout << "Now starting the write out..." << std::endl;
  myfilein.seekg(0);

  ftruncate(fd,filecontent.length());

  myfileout.seekp(0);
  myfileout << filecontent;
  myfileout.flush();
  myfileout.clear();



  // -----------
  // read from file for a second time and printout content
  // -----------

  std::cout << "Reading from file again... " << std::endl;
  myfilein.seekg(0, std::ios::end);
  size_t size_after = myfilein.tellg();
  myfilein.seekg(0);

  std::string filecontent_after{""};
  filecontent_after.reserve(size_after);

  std::cout << "Length of file is now: " << size_after << std::endl;

  // read full content of file in string "filecontent"
  filecontent_after.assign((std::istreambuf_iterator<char>(myfilein)),
            std::istreambuf_iterator<char>());

  std::cout << "Length of filecontent_after-string: " << filecontent_after.size() << std::endl;

  std::cout << "Content of File end" << std::endl;
  std::cout << "----------" << std::endl;
  std::cout << filecontent_after << std::endl;
  std::cout << "----------" << std::endl;



  // -----------
  // unlocking file and close file
  // -----------

  printf("Unlocking...\n");

  fl.l_type = F_UNLCK;
  if (fcntl(fd, F_SETLK, &fl) == -1) {
    perror("fcntl");
    abort();
  }

  close(fd);



  // -----------
  // done
  // -----------

  std::cout << "done" << std::endl;

  exit(0);
}

我征求您对此的意见或如何改进。

亚历山大·布伦斯

标签: c++linuxlockingc++17fcntl

解决方案


推荐阅读