blob: e9c2e7501c89023f3a572fd1b81451f518f3c512 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#ifndef PAGESPEED_KERNEL_BASE_FILE_SYSTEM_H_
#define PAGESPEED_KERNEL_BASE_FILE_SYSTEM_H_
#include <vector>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
// Three-way return type for distinguishing Errors from boolean answer.
//
// This is physically just an enum, but is wrapped in a class to prevent
// accidental usage in an if- or ternary-condition without explicitly indicating
// whether you are looking for true, false, or error.
class BoolOrError {
enum Choice { kIsFalse, kIsTrue, kIsError };
public:
BoolOrError() : choice_(kIsError) {}
explicit BoolOrError(bool t_or_f) : choice_(t_or_f ? kIsTrue : kIsFalse) {}
// Intended to be passed by value; explicitly support copy & assign
BoolOrError(const BoolOrError& src) : choice_(src.choice_) {}
BoolOrError& operator=(const BoolOrError& src) {
if (&src != this) {
choice_ = src.choice_;
}
return *this;
}
bool is_false() const { return choice_ == kIsFalse; }
bool is_true() const { return choice_ == kIsTrue; }
bool is_error() const { return choice_ == kIsError; }
void set_error() { choice_ = kIsError; }
void set(bool t_or_f) { choice_ = t_or_f ? kIsTrue : kIsFalse; }
private:
Choice choice_;
};
class MessageHandler;
class Timer;
class Writer;
// Provides abstract file system interface. This isolation layer helps us:
// - write unit tests that don't test the physical filesystem via a
// MemFileSystem.
// - Eases integration with Apache, which has its own file system interface,
// and this class can help serve as the glue.
// - provides a speculative conduit to a database so we can store resources
// in a place where multiple Apache servers can see them.
class FileSystem {
public:
// This is documented as -1 in user-facing documentation, so don't change it.
static const int64 kUnlimitedSize = -1;
virtual ~FileSystem();
class File {
public:
virtual ~File();
// Gets the name of the file.
virtual const char* filename() = 0;
protected:
// Use public interface provided by FileSystem::Close.
friend class FileSystem;
virtual bool Close(MessageHandler* handler) = 0;
};
class InputFile : public File {
public:
// Note: This returns num bytes read, NOT a success bool.
virtual int Read(char* buf, int size, MessageHandler* handler) = 0;
// Reads entire file into buf, returning true if successful. Calling this
// with max_file_size=kUnlimitedSize doesn't limit the read size, but it's
// dangerous, since we can OOM if the file somehow ended up being much
// larger than expected, so you should set a reasonable limit.
virtual bool ReadFile(GoogleString* buf, int64 max_file_size,
MessageHandler* handler) = 0;
protected:
friend class FileSystem;
~InputFile() override;
};
class OutputFile : public File {
public:
// Note: Write is not atomic. If Write fails, there is no indication of how
// much data has already been written to the file.
virtual bool Write(const StringPiece& buf, MessageHandler* handler) = 0;
virtual bool Flush(MessageHandler* handler) = 0;
virtual bool SetWorldReadable(MessageHandler* handler) = 0;
protected:
friend class FileSystem;
~OutputFile() override;
};
class ProgressNotifier {
public:
virtual void Notify() = 0;
virtual ~ProgressNotifier() {}
};
class NullProgressNotifier : public ProgressNotifier {
public:
void Notify() override {}
};
struct FileInfo {
FileInfo(int64 size_bytes, int64 atime_sec, const GoogleString& name)
: size_bytes(size_bytes), atime_sec(atime_sec), name(name) {}
int64 size_bytes;
int64 atime_sec;
GoogleString name;
};
struct DirInfo {
DirInfo() : size_bytes(0), inode_count(0) {}
std::vector<FileInfo> files;
StringVector empty_dirs;
int64 size_bytes;
int64 inode_count;
};
// Returns the maximum possible length of a path in a given directory.
// Note that this is the total, and there may be further constraints
// on each level. It also depends on the base path.
//
// Default implementation defensively returns 8192.
virtual int MaxPathLength(const StringPiece& base) const;
// High level support to read/write entire files in one shot. The input_file
// versions accept a NULL input_file, in which case they report failure. If
// the file is larget than max_file_size, return false. All routines close
// the file.
virtual bool ReadFile(const char* filename, int64 max_file_size,
Writer* writer, MessageHandler* handler);
virtual bool ReadFile(InputFile* input_file, int64 max_file_size,
Writer* writer, MessageHandler* handler);
virtual bool ReadFile(const char* filename, int64 max_file_size,
GoogleString* buffer, MessageHandler* handler);
virtual bool ReadFile(InputFile* input_file, int64 max_file_size,
GoogleString* buffer, MessageHandler* handler);
// Deprecated versions of ReadFile, because they can OOM if the file they're
// trying to read happens to be surprisingly large. Instead, call ReadFile
// with a limit. If you can guarantee that you'll never encounter a large
// file with this call, perhaps because you're reading a file you created,
// then call ReadFile with an explicit limit of kUnlimitedSize.
virtual bool ReadFile(const char* filename, GoogleString* buffer,
MessageHandler* handler);
virtual bool ReadFile(InputFile* input_file, GoogleString* buffer,
MessageHandler* handler);
virtual bool ReadFile(const char* filename, Writer* writer,
MessageHandler* handler);
virtual bool ReadFile(InputFile* input_file, Writer* writer,
MessageHandler* handler);
// Non-atomic. Use WriteFileAtomic() for atomic version.
virtual bool WriteFile(const char* filename, const StringPiece& buffer,
MessageHandler* handler);
// Writes given data to a temp file in one shot, storing the filename
// in filename on success. Returns false and clears filename on failure.
virtual bool WriteTempFile(const StringPiece& prefix_name,
const StringPiece& buffer, GoogleString* filename,
MessageHandler* handler);
// Write a temp file first and then copy to filename so that the file
// cannot be read after being partially written.
// Temp file name is based on filename.
bool WriteFileAtomic(const StringPiece& filename, const StringPiece& buffer,
MessageHandler* handler);
virtual InputFile* OpenInputFile(const char* filename,
MessageHandler* handler) = 0;
// Automatically creates sub-directories to filename.
OutputFile* OpenOutputFile(const char* filename, MessageHandler* handler) {
SetupFileDir(filename, handler);
return OpenOutputFileHelper(filename, false, handler);
}
// Open a file to append to it.
// Automatically creates sub-directories to filename.
OutputFile* OpenOutputFileForAppend(const char* filename,
MessageHandler* handler) {
SetupFileDir(filename, handler);
return OpenOutputFileHelper(filename, true, handler);
}
// Opens a temporary file to write, with the specified prefix.
// If successful, the filename can be obtained from File::filename().
// Automatically creates sub-directories to filename.
//
// NULL is returned on failure.
OutputFile* OpenTempFile(const StringPiece& prefix_name,
MessageHandler* handler) {
SetupFileDir(prefix_name, handler);
return OpenTempFileHelper(prefix_name, handler);
}
// Closes the File and cleans up memory.
virtual bool Close(File* file, MessageHandler* handler);
// Like POSIX 'rm'.
virtual bool RemoveFile(const char* filename, MessageHandler* handler) = 0;
// Like POSIX 'mv', except it automatically creates sub-directories for
// new_filename.
bool RenameFile(const char* old_filename, const char* new_filename,
MessageHandler* handler) {
SetupFileDir(new_filename, handler);
return RenameFileHelper(old_filename, new_filename, handler);
}
// Like POSIX 'mkdir', makes a directory only if parent directory exists.
// Fails if directory_name already exists or parent directory doesn't exist.
virtual bool MakeDir(const char* directory_path, MessageHandler* handler) = 0;
// Like POSIX 'rmdir', remove a directory only if it is empty.
virtual bool RemoveDir(const char* directory_path,
MessageHandler* handler) = 0;
// Like POSIX 'test -e', checks if path exists (is a file, directory, etc.).
virtual BoolOrError Exists(const char* path, MessageHandler* handler) = 0;
// Like POSIX 'test -d', checks if path exists and refers to a directory.
virtual BoolOrError IsDir(const char* path, MessageHandler* handler) = 0;
// Like POSIX 'mkdir -p', makes all directories up to this one recursively.
// Fails if we do not have permission to make any directory in chain.
virtual bool RecursivelyMakeDir(const StringPiece& directory_path,
MessageHandler* handler);
// Like POSIX 'ls -a', lists all files and directories under the given
// directory (but omits "." and ".."). Full paths (not just filenames) will
// be pushed onto the back of the supplied vector (without clearing it).
// Returns true on success (even if the dir was empty), false on error (even
// if some files were pushed onto the vector). This is generally not
// threadsafe! Use a mutex.
virtual bool ListContents(const StringPiece& dir, StringVector* files,
MessageHandler* handler) = 0;
// Stores in *timestamp_sec the timestamp (in seconds since the
// epoch) of the last time the file was accessed (through one of our
// Read methods, or by someone else accessing the filesystem
// directly). Returns true on success, false on failure.
// TODO(abliss): replace this with a single Stat() function.
virtual bool Atime(const StringPiece& path, int64* timestamp_sec,
MessageHandler* handler) = 0;
// Modified time. Time the file contents were modified.
virtual bool Mtime(const StringPiece& path, int64* timestamp_sec,
MessageHandler* handler) = 0;
// Given a directory path, list the files in the directory and all
// subdirectories along with total size, inode count, and list of empty
// directories (useful for cache cleaning). The files/directories in the
// 'files' and 'empty_dirs' members of dirinfo will have the 'path' input
// parameter prepended to them. We assume no circular links. If the files or
// directories are modified while we traverse, we are not guaranteed to
// represent their final state. The path name should NOT end in a "/".
// TODO(abliss): unify all slash-ending assumptions
void GetDirInfo(const StringPiece& path, DirInfo* dirinfo,
MessageHandler* handler);
// Like GetDirInfo, but notifier->Notify() is called repeatedly as long as
// GetDirInfo is making progress.
virtual void GetDirInfoWithProgress(const StringPiece& path, DirInfo* dirinfo,
ProgressNotifier* notifier,
MessageHandler* handler);
// Given a file, computes its size in bytes and store it in *size. Returns
// true on success, false on failure. Behavior is undefined if path refers to
// a directory.
// This function has different behavior depending on the underlying
// implementation. Memory-based implementations will report the size of the
// file, while disk-based implementations should return the actual allocated
// size on disk.
// TODO(abliss): replace this with a single Stat() function.
virtual bool Size(const StringPiece& path, int64* size,
MessageHandler* handler) const = 0;
// Attempts to obtain a global (cross-process, cross-thread) lock of the given
// name (which should be a valid filename, not otherwise used, in an extant
// directory). If someone else has this lock, returns False immediately. If
// anything goes wrong, returns Error. On success, returns True: then you
// must call Unlock when you are done.
virtual BoolOrError TryLock(const StringPiece& lock_name,
MessageHandler* handler) = 0;
// Like TryLock, but may attempt to break stale locks, though the default
// implementation never actually breaks any. A lock is stale if it was taken
// (or last bumped) more than timeout_millis ms ago.
//
// If you obtain a lock through this method, there are no hard guarantees that
// nobody else has it too.
// <blink> If you use this function, your lock becomes "best-effort". </blink>
//
// If you override this function, you need to override BumpLockTimeout as
// well.
virtual BoolOrError TryLockWithTimeout(const StringPiece& lock_name,
int64 timeout_millis,
const Timer* timer,
MessageHandler* handler) {
return TryLock(lock_name, handler);
}
// If you're holding a lock for a long running task you want to avoid someone
// else receiving the lock if they request it with TryLockWithTimeout because
// you've been working for longer than the timeout, you should bump it often
// enough that it doesn't expire.
virtual bool BumpLockTimeout(const StringPiece& lock_name,
MessageHandler* handler) {
// Default implementation does nothing, since the default implementation of
// TryLockWithTimeout doesn't do anything either.
return true;
}
// Attempts to release a lock previously obtained through TryLock. If your
// thread did not previously obtain the lock, the behavior is undefined.
// Returns true if we successfully release the lock. Returns false if we were
// unable to release the lock (e.g. somebody came along and write-protected
// the lockfile). You might try again, or start using a different lock name.
virtual bool Unlock(const StringPiece& lock_name,
MessageHandler* handler) = 0;
protected:
// These interfaces must be defined by implementers of FileSystem.
// They may assume the directory already exists.
virtual OutputFile* OpenOutputFileHelper(const char* filename, bool append,
MessageHandler* handler) = 0;
virtual OutputFile* OpenTempFileHelper(const StringPiece& filename,
MessageHandler* handler) = 0;
virtual bool RenameFileHelper(const char* old_filename,
const char* new_filename,
MessageHandler* handler) = 0;
private:
// RecursiveMakeDir the directory needed for filename.
void SetupFileDir(const StringPiece& filename, MessageHandler* handler);
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_BASE_FILE_SYSTEM_H_