blob: f732090c820841b74e701ce6f8ac6dee1fcd36d9 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed 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.
*/
// Author: jmarantz@google.com (Joshua Marantz)
#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:
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.
virtual bool ReadFile(GoogleString* buf, MessageHandler* handler) = 0;
protected:
friend class FileSystem;
virtual ~InputFile();
};
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;
virtual ~OutputFile();
};
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. All
// routines close the file.
virtual bool ReadFile(const char* filename,
GoogleString* buffer,
MessageHandler* handler);
virtual bool ReadFile(const char* filename,
Writer* writer,
MessageHandler* handler);
virtual bool ReadFile(InputFile* input_file,
GoogleString* buffer,
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
virtual void GetDirInfo(const StringPiece& path, DirInfo* dirinfo,
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) = 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 the lock if it appears to be staler
// than the given number of milliseconds. (The default implementation never
// actually breaks locks.) 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>
virtual BoolOrError TryLockWithTimeout(const StringPiece& lock_name,
int64 timeout_millis,
const Timer* timer,
MessageHandler* handler) {
return TryLock(lock_name, handler);
}
// 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_