blob: ae6a2bf68a41378e4616796d84bda8f8f96cbc24 [file] [log] [blame]
// Copyright 2019 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 "src/sys/appmgr/storage_watchdog.h"
#include <dirent.h>
#include <fcntl.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <sys/types.h>
#include <src/lib/files/directory.h>
#include <src/lib/files/path.h>
#include "src/lib/fxl/strings/concatenate.h"
namespace {
// Delete the given dirent inside the openend directory. If the dirent is a
// directory itself, it will be recursively deleted.
void DeleteDirentInFd(int dir_fd, struct dirent* ent) {
if (ent->d_type == DT_DIR) {
int child_dir = openat(dir_fd, ent->d_name, O_DIRECTORY);
if (child_dir == -1) {
return;
}
DIR* child_dir_stream = fdopendir(child_dir);
struct dirent* child_ent;
while ((child_ent = readdir(child_dir_stream))) {
if (strncmp(child_ent->d_name, ".", 2) != 0) {
DeleteDirentInFd(child_dir, child_ent);
}
}
closedir(child_dir_stream);
unlinkat(dir_fd, ent->d_name, AT_REMOVEDIR);
} else {
unlinkat(dir_fd, ent->d_name, 0);
}
}
// PurgeCacheIn will remove elements in cache directories inside dir_fd,
// recurse on any nested realms in dir_fd, and close dir_fd when its done.
void PurgeCacheIn(int dir_fd) {
DIR* dir_stream = fdopendir(dir_fd);
// For all children in the path we're looking at, if it's named "r", then
// it's a child realm that we should walk into. If it's not, it's a
// component's cache that should be cleaned. Note that the path naming logic
// implemented in realm.cc:IsolatedPathForPackage() makes it impossible for
// a component to be named "r".
struct dirent* ent = nullptr;
while ((ent = readdir(dir_stream))) {
if (strncmp(ent->d_name, ".", 2) == 0) {
// Don't treat `.` as a component directory to be deleted!
continue;
} else if (strncmp(ent->d_name, "r", 2) == 0) {
// This is a realm, open and queue up the child realms to be cleaned
int r_dir = openat(dir_fd, ent->d_name, O_DIRECTORY);
if (r_dir == -1) {
// We failed to open the directory. Keep going, as we want to delete as
// much as we can!
continue;
}
DIR* r_dir_stream = fdopendir(r_dir);
struct dirent* ent = nullptr;
while ((ent = readdir(r_dir_stream))) {
if (strncmp(ent->d_name, ".", 2) != 0) {
int new_dir_fd = openat(r_dir, ent->d_name, O_DIRECTORY);
if (new_dir_fd == -1) {
continue;
}
PurgeCacheIn(new_dir_fd);
}
}
closedir(r_dir_stream);
} else {
int component_dir = openat(dir_fd, ent->d_name, O_DIRECTORY);
if (component_dir == -1) {
continue;
}
DIR* component_dir_stream = fdopendir(component_dir);
struct dirent* ent = nullptr;
while ((ent = readdir(component_dir_stream))) {
if (strncmp(ent->d_name, ".", 2) != 0) {
DeleteDirentInFd(component_dir, ent);
}
}
closedir(component_dir_stream);
}
}
closedir(dir_stream);
}
} // namespace
// GetStorageUsage will return the percentage, from 0 to 100, of used bytes on
// the disk located at this.path_to_watch_.
size_t StorageWatchdog::GetStorageUsage() {
TRACE_DURATION("appmgr", "StorageWatchdog::GetStorageUsage");
fbl::unique_fd fd;
fd.reset(open(path_to_watch_.c_str(), O_RDONLY));
if (!fd) {
FX_LOGS(WARNING) << "storage_watchdog: could not open target: " << path_to_watch_;
return 0;
}
fuchsia_io_FilesystemInfo info;
fdio_cpp::FdioCaller caller(std::move(fd));
zx_status_t status = GetFilesystemInfo(caller.borrow_channel(), &info);
if (status != ZX_OK) {
FX_LOGS(WARNING) << "storage_watchdog: cannot query filesystem: " << status;
return 0;
}
info.name[fuchsia_io_MAX_FS_NAME_BUFFER - 1] = '\0';
// The number of bytes which may be allocated plus the number of bytes which
// have been allocated
size_t free_plus_allocated = info.free_shared_pool_bytes + info.total_bytes;
if (free_plus_allocated == 0) {
FX_LOGS(WARNING) << "storage_watchdog: unable to determine storage "
<< "pressure";
return 0;
}
// The number of used bytes (*100, because we want a percent) over the number
// of bytes which may be used
size_t use_percentage = info.used_bytes * 100 / free_plus_allocated;
return use_percentage;
}
void StorageWatchdog::CheckStorage(async_dispatcher_t* dispatcher) {
size_t use_percentage = this->GetStorageUsage();
if (use_percentage >= 95) {
FX_LOGS(INFO) << "storage usage has reached " << use_percentage
<< "%% capacity, purging the cache now";
this->PurgeCache();
}
async::PostDelayedTask(
dispatcher, [this, dispatcher] { this->CheckStorage(dispatcher); }, zx::sec(60));
}
void StorageWatchdog::Run(async_dispatcher_t* dispatcher) {
async::PostTask(dispatcher, [this, dispatcher] { this->CheckStorage(dispatcher); });
}
// PurgeCache will remove cache items from this.path_to_clean_.
void StorageWatchdog::PurgeCache() {
TRACE_DURATION("appmgr", "StorageWatchdog::PurgeCache");
// Walk the directory tree from `path_to_clean_`.
int dir_fd = open(path_to_clean_.c_str(), O_DIRECTORY);
if (dir_fd == -1) {
if (errno == ENOENT) {
FX_LOGS(INFO) << "nothing in cache to purge";
} else {
FX_LOGS(ERROR) << "error opening directory: " << errno;
}
return;
}
PurgeCacheIn(dir_fd);
size_t use_percentage = this->GetStorageUsage();
FX_LOGS(INFO) << "cache purge is complete, new storage usage is at " << use_percentage
<< "%% capacity";
}
zx_status_t StorageWatchdog::GetFilesystemInfo(zx_handle_t directory,
fuchsia_io_FilesystemInfo* out_info) {
zx_status_t status = ZX_OK;
zx_status_t io_status = fuchsia_io_DirectoryAdminQueryFilesystem(directory, &status, out_info);
return io_status != ZX_OK ? io_status : status;
}