C 文件描述符复制而不共享偏移量或标志

C File descriptor duplication without sharing offset or flags

我需要使用 C 从不同偏移量的文件中并发读取。 dup 不幸地创建了一个与原始文件共享偏移量和标志的文件描述符。

是否有像dup这样不共享偏移量和标志的函数?

编辑我只能访问文件指针FILE* fp;我没有文件路径

EDIT 除了 mac 和 linux

的多种风格之外,该程序还针对 windows 编译

解决方案 我们可以在 posix 系统上使用 pread,我为 windows 写了一个 pread 函数来解决这个问题 https://github.com/Storj/libstorj/blob/master/src/utils.c#L227

不,C 和 POSIX(因为你提到 dup())都没有基于现有文件句柄打开新的独立文件句柄的功能。如您所见,您可以 dup() 一个文件描述符,但结果引用相同的基础打开文件描述。

要获得一个独立的句柄,你需要open()fopen()相同的路径(只有当FILE指的是一个可以通过文件系统访问的对象时才有可能) .如果您不知道那是什么路径,或者一开始就没有路径,那么您将需要一种不同的方法。

一些可供考虑的备选方案:

  • 在内存中缓冲部分或全部文件内容,并根据需要从缓冲区中读取以满足您对独立文件偏移量的需求;
  • 构建tee命令的内部等价物;这可能需要第二个线程,并且您可能无法先于另一个文件读取一个文件,或者无法在其中一个文件中查找;
  • 将文件内容复制到已知名称的临时文件中,并根据需要多次打开该文件;
  • 如果 FILE 对应于一个常规文件,则将其映射到内存并在那里访问其内容。 POSIX 函数 fmemopen() 在这种情况下可能很有用,可以使内存映射适应您现有的基于流的使用。

在Linux上,可以从/proc/self/fd/N恢复文件名,其中N是文件描述符的整数值:

sprintf( linkname, "/proc/self/fd/%d", fd );

然后在生成的 link 名称上使用 readlink()

如果文件已被重命名或删除,您可能会倒霉。

但是为什么您需要另一个文件描述符?您可以在原始文件描述符上使用 pread() and/or pwrite() 到 read/write from/to 文件而不影响当前偏移量。 (警告:在 Linux 上,pwrite() 到以追加模式打开的文件是错误的 - POSIX 指出 pwrite() 到以追加模式打开的文件将写入指定的偏移量在 pwrite() 调用中,但 Linux pwrite() 实现被破坏,将忽略偏移量并将数据附加到文件末尾 - see the BUGS section of the Linux man page)

在 windows(假设是 VisualStudio)上,您可以从 stdio FILE 句柄访问 OS 文件句柄。 从那里重新打开它并转换回新的文件句柄。

这只是 windows,但我认为 Andrews 的答案适用于 Linux,也可能适用于 Mac - 不幸的是,没有可移植的方式让它适用于所有系统。

#include <Windows.h>
#include <fcntl.h>
#include <io.h>  
#include <stdio.h>

FILE *jreopen(FILE* f)
{
    int n = _fileno(f);
    HANDLE h = (HANDLE)_get_osfhandle(n);
    HANDLE h2 = ReOpenFile(h, GENERIC_READ, FILE_SHARE_READ, 0);
    int n2 = _open_osfhandle((intptr_t)h2, _O_RDONLY);
    FILE* g = _fdopen(n2, "r");

    return g;
}

我能够在 POSIX 系统上使用 pread 和 pwrite,并且我将 ReadFile/WriteFile 在 Windows 系统上包装成 pread 和 pwrite 函数

#ifdef _WIN32
ssize_t pread(int fd, void *buf, size_t count, uint64_t offset)
{
    long unsigned int read_bytes = 0;

    OVERLAPPED overlapped;
    memset(&overlapped, 0, sizeof(OVERLAPPED));

    overlapped.OffsetHigh = (uint32_t)((offset & 0xFFFFFFFF00000000LL) >> 32);
    overlapped.Offset = (uint32_t)(offset & 0xFFFFFFFFLL);

    HANDLE file = (HANDLE)_get_osfhandle(fd);
    SetLastError(0);
    bool RF = ReadFile(file, buf, count, &read_bytes, &overlapped);

     // For some reason it errors when it hits end of file so we don't want to check that
    if ((RF == 0) && GetLastError() != ERROR_HANDLE_EOF) {
        errno = GetLastError();
        // printf ("Error reading file : %d\n", GetLastError());
        return -1;
    }

    return read_bytes;
}

ssize_t pwrite(int fd, const void *buf, size_t count, uint64_t offset)
{
    long unsigned int written_bytes = 0;

    OVERLAPPED overlapped;
    memset(&overlapped, 0, sizeof(OVERLAPPED));

    overlapped.OffsetHigh = (uint32_t)((offset & 0xFFFFFFFF00000000LL) >> 32);
    overlapped.Offset = (uint32_t)(offset & 0xFFFFFFFFLL);

    HANDLE file = (HANDLE)_get_osfhandle(fd);
    SetLastError(0);
    bool RF = WriteFile(file, buf, count, &written_bytes, &overlapped);
    if ((RF == 0)) {
        errno = GetLastError();
        // printf ("Error reading file :%d\n", GetLastError());
        return -1;
    }

    return written_bytes;
}
#endif