blob: 753f1f6513c466658fd70aafbeba1ebc23729ec0 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fbl/alloc_checker.h>
#include <fbl/auto_call.h>
#include <fbl/ref_ptr.h>
#include <fbl/unique_ptr.h>
#include <fuzz-utils/path.h>
#include <lib/fdio/debug.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <utility>
#define ZXDEBUG 0
namespace fuzzing {
// Public methods
Path::Path() {
fbl::AllocChecker ac;
path_ = AdoptRef(new (&ac) PathBuffer());
ZX_ASSERT(ac.check());
Reset();
}
Path::Path(fbl::RefPtr<PathBuffer> path) : path_(path), length_(path->buffer_.length()) {}
Path::Path(const Path& other) : path_(other.path_), length_(other.length_) {}
Path::~Path() {}
fbl::String Path::Join(const char* relpath) const {
ZX_DEBUG_ASSERT(relpath);
fbl::StringBuffer<PATH_MAX> abspath;
abspath.Append(c_str(), length_ - 1);
// Add each path segment
const char* p = relpath;
const char* sep;
while (p && (sep = strchr(p, '/'))) {
// Skip repeated slashes
if (p != sep) {
abspath.Append('/');
abspath.Append(p, sep - p);
}
p = sep + 1;
}
if (*p) {
abspath.Append('/');
abspath.Append(p);
}
return std::move(abspath);
}
zx_status_t Path::GetSize(const char* relpath, size_t* out) const {
fbl::String abspath = Join(relpath);
struct stat buf;
if (stat(abspath.c_str(), &buf) != 0) {
xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
return ZX_ERR_IO;
}
if (!S_ISREG(buf.st_mode)) {
xprintf("Not a regular file (%08x): %s\n", buf.st_mode, abspath.c_str());
return ZX_ERR_NOT_FILE;
}
if (out) {
*out = buf.st_size;
}
return ZX_OK;
}
fbl::unique_ptr<StringList> Path::List() const {
fbl::AllocChecker ac;
fbl::unique_ptr<StringList> list(new (&ac) StringList());
ZX_ASSERT(ac.check());
DIR* dir = opendir(c_str());
if (!dir) {
return list;
}
auto close_dir = fbl::MakeAutoCall([&dir]() { closedir(dir); });
struct dirent* ent;
while ((ent = readdir(dir))) {
if (strcmp(".", ent->d_name) != 0) {
list->push_back(ent->d_name);
}
}
return list;
}
zx_status_t Path::Ensure(const char* relpath) {
ZX_DEBUG_ASSERT(relpath);
zx_status_t rc;
// First check if already exists
fbl::String abspath = Join(relpath);
struct stat buf;
if (stat(abspath.c_str(), &buf) == 0 && S_ISDIR(buf.st_mode)) {
return ZX_OK;
}
// Now recursively create the parent directories
const char* sep = strrchr(relpath, '/');
if (sep) {
fbl::String prefix(relpath, sep - relpath);
if ((rc = Ensure(prefix)) != ZX_OK) {
xprintf("Failed to ensure parent directory: %s\n", zx_status_get_string(rc));
return rc;
}
}
// Finally, create the last directory
if (mkdir(abspath.c_str(), 0777) != 0) {
xprintf("Failed to make directory '%s': %s.\n", abspath.c_str(), strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t Path::Push(const char* relpath) {
ZX_DEBUG_ASSERT(relpath);
if (*relpath == '\0') {
xprintf("Can't push empty path.\n");
return ZX_ERR_INVALID_ARGS;
}
fbl::String abspath = Join(relpath);
struct stat buf;
if (stat(abspath.c_str(), &buf) != 0) {
xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
return ZX_ERR_IO;
}
if (!S_ISDIR(buf.st_mode)) {
xprintf("Not a directory: %s\n", abspath.c_str());
return ZX_ERR_NOT_DIR;
}
fbl::AllocChecker ac;
fbl::unique_ptr<Path> cloned(new (&ac) Path(path_));
ZX_ASSERT(ac.check());
cloned->parent_.swap(parent_);
parent_.swap(cloned);
path_->buffer_.Clear();
path_->buffer_.Append(abspath);
path_->buffer_.Append('/');
length_ = path_->buffer_.length();
return ZX_OK;
}
void Path::Pop() {
if (!parent_) {
return;
}
length_ = parent_->length_;
path_->buffer_.Resize(length_);
fbl::unique_ptr<Path> parent;
parent.swap(parent_->parent_);
parent_.swap(parent);
}
zx_status_t Path::Remove(const char* relpath) {
ZX_DEBUG_ASSERT(relpath);
zx_status_t rc;
fbl::String abspath = Join(relpath);
struct stat buf;
if (stat(abspath.c_str(), &buf) != 0) {
// Ignore missing files
if (errno != ENOENT) {
xprintf("Failed to get status for '%s': %s\n", abspath.c_str(), strerror(errno));
return ZX_ERR_IO;
}
} else if (S_ISDIR(buf.st_mode)) {
// Recursively remove directories
if ((rc = Push(relpath)) != ZX_OK) {
xprintf("Failed to push subdirectory: %s\n", zx_status_get_string(rc));
return rc;
}
auto pop = fbl::MakeAutoCall([this]() { Pop(); });
auto names = List();
for (const char* name = names->first(); name; name = names->next()) {
if ((rc = Remove(name)) != ZX_OK) {
xprintf("Failed to remove subdirectory: %s\n", zx_status_get_string(rc));
return rc;
}
}
if (rmdir(c_str()) != 0) {
xprintf("Failed to remove directory '%s': %s\n", c_str(), strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
} else {
// Remove file
if (unlink(abspath.c_str()) != 0) {
xprintf("Failed to unlink '%s': %s\n", abspath.c_str(), strerror(errno));
return ZX_ERR_IO;
}
}
return ZX_OK;
}
zx_status_t Path::Rename(const char* old_relpath, const char* new_relpath) {
fbl::String old_abspath = Join(old_relpath);
fbl::String new_abspath = Join(new_relpath);
if (rename(old_abspath.c_str(), new_abspath.c_str()) != 0) {
xprintf("Failed to rename '%s' to '%s': %s.\n", old_abspath.c_str(), new_abspath.c_str(),
strerror(errno));
return ZX_ERR_IO;
}
return ZX_OK;
}
void Path::Reset() {
parent_.reset();
path_->buffer_.Clear();
path_->buffer_.Append("/");
length_ = path_->buffer_.length();
}
} // namespace fuzzing