| // 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; |
| } |