在freebsd 7上快速修复32位(2GB限制)fseek / ftell

时间:2014-06-29 01:18:01

标签: c freebsd large-files fseek

我在FreeBSD上有旧的32位C / C ++程序,它被数百名用户远程使用,其作者无法修复它。它是以不安全的方式编写的,所有文件偏移都在内部存储为无符号32位偏移,并且ftell / fseek函数在使用时。在FreeBSD 7(软件的主机平台)中,means that ftell and fseek uses 32-bit signed long

 int fseek(FILE *stream, long offset, int whence);

 long ftell(FILE *stream);

我需要快速修复程序,因为一些内部数据文件在收集数据13年后突然达到2 ^ 31文件大小(2 147 483 7yy字节),并且内部fseek / ftell断言现在对任何请求都失败

在FreeBSD7世界中,对于2GB +文件存在fseeko / ftello黑客攻击。

 int
 fseeko(FILE *stream, off_t offset, int whence);

 off_t
 ftello(FILE *stream);

此处的off_t类型定义不明确;我现在所知道的,它有8个字节的大小,看起来像long longunsigned long long(我不知道哪一个)。

是否足够(最多可处理4 GB文件)并且可以安全地搜索并替换所有ftellftello,并将所有fseek替换为fseekosed -i 's/ftell/ftello',同样适用于寻求),如果可能的话,它们的用法是:

 unsigned long offset1,offset2; //32bit
 offset1 = (compute + it) * in + some - arithmetic;
 fseek(file, 0, SEEK_END);
 fseek(file, 4, SEEK_END); // or other small int constant

 offset2 = ftell(file);
 fseek(file, offset1, SEEK_SET);  // No usage of SEEK_CUR

以及此类电话的组合。

off_t的签名是什么? 将64位off_t分配到无符号32位偏移量是安全的吗?它适用于2 GB到4 GB范围内的字节吗?

除了ftell / fseek

之外,哪些函数可用于处理偏移量

1 个答案:

答案 0 :(得分:12)

FreeBSD fseeko() and ftello()被记录为POSIX.1-2001兼容,这意味着off_t is a signed integer type

在FreeBSD 7上,你可以安全地做到:

off_t          actual_offset;
unsigned long  stored_offset;

if (actual_offset >= (off_t)0 && actual_offset < (off_t)4294967296.0)
    stored_offset = (unsigned long)actual_offset;
else
    some_fatal_error("Unsupportable file offset!");

(在LP64体系结构上,上面会很愚蠢,因为off_tlong都是64位有符号整数。即使这样也会安全;只是愚蠢,因为所有可能的文件偏移可以得到支持。)

人们经常被这种情况所困扰的是,必须使用off_t完成偏移计算。也就是说,将结果强制转换为off_t是不够的,您必须将算术中使用的values强制转换为off_t。 (从技术上讲,你只需要确保每个算术运算都以off_t精度完成,但是我发现如果我只是试图并投出所有操作数就更容易记住规则。)例如:

off_t          offset;
unsigned long  some, values, used;

offset = (off_t)some * (off_t)value + (off_t)used;
fseeko(file, offset, SEEK_SET);

通常,偏移计算用于查找特定记录中的字段;算术趋于保持不变。我真的建议你尽可能将搜索操作移动到辅助函数:

int fseek_to(FILE *const file,
             const unsigned long some,
             const unsigned long values,
             const unsigned long used)
{
    const off_t  offset = (off_t)some * (off_t)value + (off_t)used;
    if (offset < (off_t)0 || offset >= (off_t)4294967296.0)
        fatal_error("Offset exceeds 4GB; I must abort!");
    return fseeko(file, offset, SEEK_SET);
}

现在,如果您碰巧处于一个幸运的位置,您知道所有的偏移都是对齐的(对于某个整数,比如4),您可以给自己几年的时间来重写应用程序,方法是使用以上的扩展:

#define BIG_N 4

int fseek_to(FILE *const file,
             const unsigned long some,
             const unsigned long values,
             const unsigned long used)
{
    const off_t  offset = (off_t)some * (off_t)value + (off_t)used;
    if (offset < (off_t)0)
        fatal_error("Offset is negative; I must abort!");
    if (offset >= (off_t)(BIG_N * 2147483648.0))
        fatal_error("Offset is too large; I must abort!");
    if ((offset % BIG_N) && (offset >= (off_t)2147483648.0))
        fatal_error("Offset is not a multiple of BIG_N; I must abort!");
    return fseeko(file, offset, SEEK_SET);
}

int fseek_big(FILE *const file, const unsigned long position)
{
    off_t  offset;
    if (position >= 2147483648UL)
        offset = (off_t)2147483648UL
               + (off_t)BIG_N * (off_t)(position - 2147483648UL);
    else
        offset = (off_t)position;
    return fseeko(file, offset, SEEK_SET);
}

unsigned long ftell_big(FILE *const file)
{
    off_t  offset;
    offset = ftello(file);
    if (offset < (off_t)0)
        fatal_error("Offset is negative; I must abort!");
    if (offset < (off_t)2147483648UL)
        return (unsigned long)offset;
    if (offset % BIG_N)
        fatal_error("Offset is not a multiple of BIG_N; I must abort!");
    if (offset >= (off_t)(BIG_N * 2147483648.0))
        fatal_error("Offset is too large; I must abort!");
    return (unsigned long)2147483648UL
         + (unsigned long)((offset - (off_t)2147483648UL) / (off_t)BIG_N);
}

逻辑很简单:如果 offset 小于2 31 ,则按原样使用。否则,它由表示值2 31 + BIG_N ×( offset - 2 31 )。唯一的要求是偏移2 31 及以上总是 BIG_N 的倍数。

显然,你们必须只使用上述三个功能 - 加上你需要的 fseek_to()的任何变体,只要他们做同样的检查,使用不同的参数和公式进行偏移计算 - ,您可以支持最大2147483648 + BIG_N ×2147483647的文件大小。对于 BIG_N == 4,即10 GiB (少于4个字节;确切地说是10,737,418,236个字节)。

有问题吗?


编辑澄清:

首先通过调用fseek(file, position, SEEK_SET)

替换您的fseek_pos(file, position)
static inline void fseek_pos(FILE *const file, const unsigned long position)
{
    if (fseeko(file, (off_t)position, SEEK_SET))
        fatal_error("Cannot set file position!");
}

fseek(file, position, SEEK_END)调用fseek_end(file, position)(对称性 - 我假设这个位置通常是一个字面整数常量),

static inline void fseek_end(FILE *const file, const off_t relative)
{
    if (fseeko(file, relative, SEEK_END))
        fatal_error("Cannot set file position!");
}

最后,ftell(file)调用ftell_pos(file)

static inline unsigned long ftell_pos(FILE *const file)
{
    off_t position;
    position = ftello(file);
    if (position == (off_t)-1)
        fatal_error("Lost file position!");
    if (position < (off_t)0 || position >= (off_t)4294967296.0)
        fatal_error("File position outside the 4GB range!");
    return (unsigned long)position;
}

由于您的体系结构和操作系统unsigned long是32位无符号整数类型而off_t是64位有符号整数类型,因此这将为您提供完整的4GB范围。

对于偏移计算,请定义一个或多个类似于

的函数
static inline void fseek_to(FILE *const file, const off_t term1,
                                              const off_t term2,
                                              const off_t term3)
{
    const off_t position = term1 * term2 + term3;

    if (position < (off_t)0 || position >= (off_t)4294967296.0)
        fatal_error("File position outside the 4GB range!");
    if (fseeko(file, position, SEEK_SET))
        fatal_error("Cannot set file position!");
}

对于每个偏移计算算法,定义一个fseek_to变体。命名参数以使算法有意义。如上所述,设置参数const off_t,这样您就不需要在算术中使用额外的强制转换。只有参数和定义计算算法的const off_t position =行在变体函数之间有所不同。

有问题吗?