FileUtil命名空间中,是一些操作文件的工具类和方法。
FileUtil::AppendFile::AppendFile(StringArg filename)
: fp_(::fopen(filename.c_str(), "ae")), // 'e' for O_CLOEXEC
writtenBytes_(0)
{
assert(fp_);
::setbuffer(fp_, buffer_, sizeof buffer_);
// posix_fadvise POSIX_FADV_DONTNEED ?
}
FileUtil::AppendFile::~AppendFile()
{
::fclose(fp_);
}
void FileUtil::AppendFile::append(const char* logline, const size_t len)
{
size_t n = write(logline, len);
size_t remain = len - n;
while (remain > 0)
{
size_t x = write(logline + n, remain);
if (x == 0)
{
int err = ferror(fp_);
if (err)
{
fprintf(stderr, "AppendFile::append() failed %s\n", strerror_tl(err));
}
break;
}
n += x;
remain = len - n; // remain -= x
}
writtenBytes_ += len;
}
void FileUtil::AppendFile::flush()
{
::fflush(fp_);
}
size_t FileUtil::AppendFile::write(const char* logline, size_t len)
{
// #undef fwrite_unlocked
return ::fwrite_unlocked(logline, 1, len, fp_);
}
FileUtil::ReadSmallFile::ReadSmallFile(StringArg filename)
: fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)),
err_(0)
{
buf_[0] = '\0';
if (fd_ < 0)
{
err_ = errno;
}
}
AppendFile类构造函数中打开文件并设置写入的字节数为0,使用setbuffer设置缓冲区,在博客setbuffer 中我专门写了关于这个的一些说明。因为这个函数我之前没有用过,所以专门写了测试程序进行简单测试。该类的对外提供的接口就是append和flush。在append中将数据写入文件中,并且会判断是否全部写入,没写完就一直写,直到写完或者出错为止。append会调用内部接口write,在write中使用了fwrite_unlocked函数,该函数在博客fwrite_unlocked 中我也专门做了简单测试。因为在外部会有锁,例如在使用AppendFile的LogFile中本身就会加锁,所以在AppendFile中就不需要加锁了。
FileUtil::ReadSmallFile::ReadSmallFile(StringArg filename)
: fd_(::open(filename.c_str(), O_RDONLY | O_CLOEXEC)),
err_(0)
{
buf_[0] = '\0';
if (fd_ < 0)
{
err_ = errno;
}
}
FileUtil::ReadSmallFile::~ReadSmallFile()
{
if (fd_ >= 0)
{
::close(fd_); // FIXME: check EINTR
}
}
// return errno
template<typename String>
int FileUtil::ReadSmallFile::readToString(int maxSize,
String* content,
int64_t* fileSize,
int64_t* modifyTime,
int64_t* createTime)
{
static_assert(sizeof(off_t) == 8, "_FILE_OFFSET_BITS = 64");
assert(content != NULL);
int err = err_;
if (fd_ >= 0)
{
content->clear();
if (fileSize)
{
struct stat statbuf;
if (::fstat(fd_, &statbuf) == 0)
{
if (S_ISREG(statbuf.st_mode))
{
*fileSize = statbuf.st_size;
content->reserve(static_cast<int>(std::min(implicit_cast<int64_t>(maxSize), *fileSize)));
}
else if (S_ISDIR(statbuf.st_mode))
{
err = EISDIR;
}
if (modifyTime)
{
*modifyTime = statbuf.st_mtime;
}
if (createTime)
{
*createTime = statbuf.st_ctime;
}
}
else
{
err = errno;
}
}
while (content->size() < implicit_cast<size_t>(maxSize))
{
size_t toRead = std::min(implicit_cast<size_t>(maxSize) - content->size(), sizeof(buf_));
ssize_t n = ::read(fd_, buf_, toRead);
if (n > 0)
{
content->append(buf_, n);
}
else
{
if (n < 0)
{
err = errno;
}
break;
}
}
}
return err;
}
int FileUtil::ReadSmallFile::readToBuffer(int* size)
{
int err = err_;
if (fd_ >= 0)
{
ssize_t n = ::pread(fd_, buf_, sizeof(buf_)-1, 0);
if (n >= 0)
{
if (size)
{
*size = static_cast<int>(n);
}
buf_[n] = '\0';
}
else
{
err = errno;
}
}
return err;
}
看名字就知道这个类使用来读文件的,该类的readToBuffer会将文件读到buf_中,而buf_只有64*1024字节的容量,这也是对所读文件大小的一个限制。在readToBuffer中,使用了pread函数读文件,顺便介绍下该函数:
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
pread()调用等同于将如下调用纳入同一原子操作:
off_t orig;
orig = lseek(fd, 0, SEEK_CUR);
lseek(fd, offset, SEEK_SET);
s = read(fd, buf, len);
lseek(fd, orig, SEEK_SET);
多线程应用为这些系统调用(pread,pwrite)提供了用武之地。进程下辖的所有线程将共享同一文件描述符表。这也意味着每个已打开文件的文件偏移量为所有线程所共享。当调用pread或pwrite时,多个线程可同时对同一文件描述符执行I/O操作,且不会因其他线程修改文件偏移量而受到影响。如果还试图使用lseek或read来代替read,那么将引发竞争状态。
ReadSmallFile类其他的内容都比较简单。
除了这两个类之外,FileUtil还提供了一个函数。
// read the file content, returns errno if error happens.
template<typename String>
int readFile(StringArg filename,
int maxSize,
String* content,
int64_t* fileSize = NULL,
int64_t* modifyTime = NULL,
int64_t* createTime = NULL)
{
ReadSmallFile file(filename);
return file.readToString(maxSize, content, fileSize, modifyTime, createTime);
}
该函数提供的功能就是读取一个文件,将内容写入content。在muduo中,使用该接口来读取linux系统文件。