pw_file: FlatFileSystem service implementation
Provides an optional implementation for the FileSystem RPC service that
enumerates a list of files as a "flat" file system.
Change-Id: Icfabbec17ba9d458aba8652d22342d3697ca3a07
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/60401
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Armando Montanez <amontanez@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index f48f7c9..a86c9c8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -280,6 +280,7 @@
"$dir_pw_containers:tests",
"$dir_pw_cpu_exception_cortex_m:tests",
"$dir_pw_crypto:tests",
+ "$dir_pw_file:tests",
"$dir_pw_function:tests",
"$dir_pw_fuzzer:tests",
"$dir_pw_hdlc:tests",
diff --git a/pw_file/BUILD.bazel b/pw_file/BUILD.bazel
new file mode 100644
index 0000000..7eda65c
--- /dev/null
+++ b/pw_file/BUILD.bazel
@@ -0,0 +1,60 @@
+# Copyright 2021 The Pigweed Authors
+#
+# 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
+#
+# https://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.
+
+load(
+ "//pw_build:pigweed.bzl",
+ "pw_cc_library",
+ "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+proto_library(
+ name = "proto",
+ srcs = ["file.proto"],
+)
+
+pw_cc_library(
+ name = "flat_file_system",
+ srcs = [
+ "flat_file_system.cc",
+ ],
+ hdrs = [
+ "public/pw_file/flat_file_system.h",
+ ],
+ deps = [
+ ":proto",
+ "//pw_bytes",
+ "//pw_result",
+ "//pw_rpc/raw:method",
+ "//pw_status",
+ ],
+)
+
+pw_cc_test(
+ name = "flat_file_system_test",
+ srcs = [
+ "flat_file_system_test.cc",
+ ],
+ deps = [
+ ":flat_file_system",
+ ":proto",
+ "//pw_bytes",
+ "//pw_protobuf",
+ "//pw_rpc/raw:test_method_context",
+ "//pw_status",
+ ],
+)
diff --git a/pw_file/BUILD.gn b/pw_file/BUILD.gn
index f12c20c..13404d8 100644
--- a/pw_file/BUILD.gn
+++ b/pw_file/BUILD.gn
@@ -16,6 +16,11 @@
import("$dir_pw_docgen/docs.gni")
import("$dir_pw_protobuf_compiler/proto.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("public_includes") {
+ include_dirs = [ "public" ]
+}
pw_proto_library("proto") {
sources = [ "file.proto" ]
@@ -23,7 +28,39 @@
deps = [ "$dir_pw_protobuf:common_protos" ]
}
+pw_source_set("flat_file_system") {
+ public_deps = [
+ ":proto.pwpb",
+ ":proto.raw_rpc",
+ "$dir_pw_rpc/raw:method",
+ dir_pw_assert,
+ dir_pw_bytes,
+ dir_pw_log,
+ dir_pw_result,
+ dir_pw_status,
+ ]
+ public_configs = [ ":public_includes" ]
+ public = [ "public/pw_file/flat_file_system.h" ]
+ sources = [ "flat_file_system.cc" ]
+}
+
pw_doc_group("docs") {
sources = [ "docs.rst" ]
inputs = [ "file.proto" ]
}
+
+pw_test_group("tests") {
+ tests = [ ":flat_file_system_test" ]
+}
+
+pw_test("flat_file_system_test") {
+ deps = [
+ ":flat_file_system",
+ ":proto.pwpb",
+ "$dir_pw_rpc/raw:test_method_context",
+ dir_pw_bytes,
+ dir_pw_protobuf,
+ dir_pw_status,
+ ]
+ sources = [ "flat_file_system_test.cc" ]
+}
diff --git a/pw_file/docs.rst b/pw_file/docs.rst
index 3bfd29d..43f3aa2 100644
--- a/pw_file/docs.rst
+++ b/pw_file/docs.rst
@@ -29,3 +29,17 @@
.. literalinclude:: file.proto
:language: protobuf
:lines: 14-
+
+------------------------------
+Flat FileSystem implementation
+------------------------------
+This module provides the ``FlatFileSystemService``, an optional implementation
+of the FileSystem RPC service with a virtual interface that allows different
+data storage implementations to expose logical files. As the name implies, the
+file system is treated as a flat file system; it does not support any
+directory-like interactions.
+
+The ``FlatFileSystemService`` implementation requires a static, fixed list of
+``FileSystemEntry`` pointers. Each ``FileSystemEntry`` represents a potential
+file, and acts as an interface boundary that is backed by some kind of storage
+mechanism (e.g. ``BlobStore``, ``PersistentBuffer``).
diff --git a/pw_file/flat_file_system.cc b/pw_file/flat_file_system.cc
new file mode 100644
index 0000000..d347903
--- /dev/null
+++ b/pw_file/flat_file_system.cc
@@ -0,0 +1,175 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+// https://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.
+
+#define PW_LOG_MODULE_NAME "FS"
+
+#include "pw_file/flat_file_system.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+#include "pw_assert/check.h"
+#include "pw_bytes/span.h"
+#include "pw_file/file.pwpb.h"
+#include "pw_log/log.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_protobuf/encoder.h"
+#include "pw_protobuf/serialized_size.h"
+#include "pw_rpc/raw/server_reader_writer.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::file {
+
+using FileSystemEntry = FlatFileSystemService::FileSystemEntry;
+
+Status FlatFileSystemService::EnumerateFile(
+ FileSystemEntry& entry,
+ pw::file::ListResponse::StreamEncoder& output_encoder) {
+ StatusWithSize sws = entry.Name(file_name_buffer_);
+ if (!sws.ok()) {
+ return sws.status();
+ }
+ {
+ pw::file::Path::StreamEncoder encoder = output_encoder.GetPathsEncoder();
+
+ encoder.WritePath(reinterpret_cast<const char*>(file_name_buffer_.data()),
+ sws.size());
+ encoder.WriteSizeBytes(entry.SizeBytes());
+ encoder.WritePermissions(entry.Permissions());
+ encoder.WriteFileId(entry.FileId());
+ }
+ return output_encoder.status();
+}
+
+void FlatFileSystemService::EnumerateAllFiles(RawServerWriter& writer) {
+ for (FileSystemEntry* entry : entries_) {
+ PW_DCHECK_NOTNULL(entry);
+ // For now, don't try to pack entries.
+ pw::file::ListResponse::MemoryEncoder encoder(writer.PayloadBuffer());
+ if (Status status = EnumerateFile(*entry, encoder); !status.ok()) {
+ PW_LOG_ERROR("Failed to enumerate file (id: %u) with status %d",
+ static_cast<unsigned>(entry->FileId()),
+ static_cast<int>(status.code()));
+ continue;
+ }
+
+ Status write_status = writer.Write(encoder);
+ if (!write_status.ok()) {
+ writer.Finish(write_status);
+ return;
+ }
+ }
+ writer.Finish(OkStatus());
+}
+
+void FlatFileSystemService::List(ServerContext&,
+ ConstByteSpan request,
+ RawServerWriter& writer) {
+ protobuf::Decoder decoder(request);
+ // If a file name was provided, try and find and enumerate the file.
+ while (decoder.Next().ok()) {
+ if (decoder.FieldNumber() !=
+ static_cast<uint32_t>(pw::file::ListRequest::Fields::PATH)) {
+ continue;
+ }
+
+ std::string_view file_name_view;
+ if (!decoder.ReadString(&file_name_view).ok() ||
+ file_name_view.length() == 0) {
+ writer.Finish(Status::DataLoss());
+ return;
+ }
+
+ // Find and enumerate the file requested.
+ Result<FileSystemEntry*> result = FindFile(file_name_view);
+ if (!result.ok()) {
+ writer.Finish(result.status());
+ return;
+ }
+
+ pw::file::ListResponse::MemoryEncoder encoder(writer.PayloadBuffer());
+ Status proto_encode_status = EnumerateFile(*result.value(), encoder);
+ if (!proto_encode_status.ok()) {
+ writer.Finish(proto_encode_status);
+ return;
+ }
+
+ writer.Finish(writer.Write(encoder));
+ return;
+ }
+
+ // If no path was provided in the ListRequest, just enumerate everything.
+ EnumerateAllFiles(writer);
+}
+
+StatusWithSize FlatFileSystemService::Delete(ServerContext&,
+ ConstByteSpan request,
+ ByteSpan) {
+ protobuf::Decoder decoder(request);
+ while (decoder.Next().ok()) {
+ if (decoder.FieldNumber() !=
+ static_cast<uint32_t>(pw::file::DeleteRequest::Fields::PATH)) {
+ continue;
+ }
+
+ std::string_view file_name_view;
+ if (!decoder.ReadString(&file_name_view).ok()) {
+ return StatusWithSize(Status::DataLoss(), 0);
+ }
+ return StatusWithSize(FindAndDeleteFile(file_name_view), 0);
+ }
+ return StatusWithSize(Status::InvalidArgument(), 0);
+}
+
+Result<FileSystemEntry*> FlatFileSystemService::FindFile(
+ std::string_view file_name) {
+ Status search_status;
+ for (FileSystemEntry* entry : entries_) {
+ PW_DCHECK_NOTNULL(entry);
+ StatusWithSize sws = entry->Name(file_name_buffer_);
+
+ // If there not an exact file name length match, don't try and check against
+ // a prefix.
+ if (!sws.ok() || file_name.length() != sws.size()) {
+ if (sws.status() != Status::NotFound()) {
+ PW_LOG_ERROR("Failed to read file name (id: %u) with status %d",
+ static_cast<unsigned>(entry->FileId()),
+ static_cast<int>(sws.status().code()));
+ }
+ continue;
+ }
+
+ if (memcmp(file_name.data(), file_name_buffer_.data(), file_name.size()) ==
+ 0) {
+ return entry;
+ }
+ }
+
+ search_status.Update(Status::NotFound());
+ return search_status;
+}
+
+Status FlatFileSystemService::FindAndDeleteFile(std::string_view file_name) {
+ Result<FileSystemEntry*> result = FindFile(file_name);
+ if (!result.ok()) {
+ return result.status();
+ }
+
+ return result.value()->Delete();
+}
+
+} // namespace pw::file
diff --git a/pw_file/flat_file_system_test.cc b/pw_file/flat_file_system_test.cc
new file mode 100644
index 0000000..80c91a4
--- /dev/null
+++ b/pw_file/flat_file_system_test.cc
@@ -0,0 +1,233 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+// https://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.
+
+#include "pw_file/flat_file_system.h"
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+#include "gtest/gtest.h"
+#include "pw_bytes/span.h"
+#include "pw_file/file.pwpb.h"
+#include "pw_protobuf/decoder.h"
+#include "pw_rpc/raw/test_method_context.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::file {
+namespace {
+
+class FakeFile : public FlatFileSystemService::FileSystemEntry {
+ public:
+ constexpr FakeFile(std::string_view file_name, size_t size, uint32_t file_id)
+ : name_(file_name), size_(size), file_id_(file_id) {}
+
+ StatusWithSize Name(ByteSpan dest) override {
+ if (name_.empty()) {
+ return StatusWithSize(Status::NotFound(), 0);
+ }
+
+ size_t bytes_to_copy = std::min(dest.size_bytes(), name_.size());
+ memcpy(dest.data(), name_.data(), bytes_to_copy);
+ if (bytes_to_copy != name_.size()) {
+ return StatusWithSize(Status::ResourceExhausted(), bytes_to_copy);
+ }
+
+ return StatusWithSize(OkStatus(), bytes_to_copy);
+ }
+
+ size_t SizeBytes() override { return size_; }
+
+ FlatFileSystemService::FileSystemEntry::FilePermissions Permissions()
+ override {
+ return FlatFileSystemService::FileSystemEntry::FilePermissions::NONE;
+ }
+
+ Status Delete() override { return Status::Unimplemented(); }
+
+ FlatFileSystemService::FileSystemEntry::Id FileId() override {
+ return file_id_;
+ }
+
+ private:
+ std::string_view name_;
+ size_t size_;
+ uint32_t file_id_;
+};
+
+bool FileSystemEntryHasName(FlatFileSystemService::FileSystemEntry* entry) {
+ std::array<std::byte, 4> expected_name;
+ StatusWithSize file_name_sws = entry->Name(expected_name);
+ return file_name_sws.size() != 0;
+}
+
+// Compares a serialized Path message to a flat file system entry.
+void ComparePathToEntry(ConstByteSpan serialized_path,
+ FlatFileSystemService::FileSystemEntry* entry) {
+ std::array<std::byte, 64> expected_name;
+ StatusWithSize file_name_sws = entry->Name(expected_name);
+
+ // A partial name read shouldn't happen.
+ ASSERT_EQ(OkStatus(), file_name_sws.status());
+
+ protobuf::Decoder decoder(serialized_path);
+ while (decoder.Next().ok()) {
+ switch (decoder.FieldNumber()) {
+ case static_cast<uint32_t>(pw::file::Path::Fields::PATH): {
+ std::string_view serialized_name;
+ EXPECT_EQ(OkStatus(), decoder.ReadString(&serialized_name));
+ size_t name_bytes_to_read =
+ std::min(serialized_name.size(), file_name_sws.size());
+ EXPECT_EQ(0,
+ memcmp(expected_name.data(),
+ serialized_name.data(),
+ name_bytes_to_read));
+ break;
+ }
+
+ case static_cast<uint32_t>(pw::file::Path::Fields::PERMISSIONS): {
+ uint32_t seralized_permissions;
+ EXPECT_EQ(OkStatus(), decoder.ReadUint32(&seralized_permissions));
+ EXPECT_EQ(static_cast<uint32_t>(entry->Permissions()),
+ seralized_permissions);
+ break;
+ }
+
+ case static_cast<uint32_t>(pw::file::Path::Fields::SIZE_BYTES): {
+ uint32_t serialized_file_size;
+ EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_size));
+ EXPECT_EQ(static_cast<uint32_t>(entry->SizeBytes()),
+ serialized_file_size);
+ break;
+ }
+
+ case static_cast<uint32_t>(pw::file::Path::Fields::FILE_ID): {
+ uint32_t serialized_file_id;
+ EXPECT_EQ(OkStatus(), decoder.ReadUint32(&serialized_file_id));
+ EXPECT_EQ(static_cast<uint32_t>(entry->FileId()), serialized_file_id);
+ break;
+ }
+
+ default:
+ // unexpected result.
+ // TODO something here.
+ break;
+ }
+ }
+}
+
+size_t ValidateExpectedPaths(
+ std::span<FlatFileSystemService::FileSystemEntry*> flat_file_system,
+ const Vector<ByteSpan>& results) {
+ size_t serialized_path_entry_count = 0;
+ size_t file_system_index = 0;
+ for (ConstByteSpan response : results) {
+ protobuf::Decoder decoder(response);
+ while (decoder.Next().ok()) {
+ constexpr uint32_t kListResponsePathsFieldNumber =
+ static_cast<uint32_t>(pw::file::ListResponse::Fields::PATHS);
+ EXPECT_EQ(decoder.FieldNumber(), kListResponsePathsFieldNumber);
+ if (decoder.FieldNumber() != kListResponsePathsFieldNumber) {
+ return 0;
+ }
+
+ serialized_path_entry_count++;
+
+ // Skip any file system entries without names.
+ while (!FileSystemEntryHasName(flat_file_system[file_system_index])) {
+ file_system_index++;
+ EXPECT_GT(flat_file_system.size(), file_system_index);
+ }
+
+ // There's a 1:1 mapping in the same order for all files that have a name.
+ ConstByteSpan serialized_path;
+ EXPECT_EQ(OkStatus(), decoder.ReadBytes(&serialized_path));
+ ComparePathToEntry(serialized_path,
+ flat_file_system[file_system_index++]);
+ }
+ }
+ return serialized_path_entry_count;
+}
+
+TEST(FlatFileSystem, List_NoFiles) {
+ std::array<std::byte, 1> file_name_buffer;
+
+ PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemService, List)
+ ctx(std::span<FlatFileSystemService::FileSystemEntry*>(), file_name_buffer);
+ ctx.call(ConstByteSpan());
+
+ EXPECT_TRUE(ctx.done());
+ EXPECT_EQ(OkStatus(), ctx.status());
+ EXPECT_EQ(0u, ctx.responses().size());
+}
+
+TEST(FlatFileSystem, List_OneFile) {
+ std::array<std::byte, 20> file_name_buffer;
+ FakeFile file{"compressed.zip.gz", 2, 1231};
+ std::array<FlatFileSystemService::FileSystemEntry*, 1> static_file_system{
+ &file};
+
+ PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemService, List)
+ ctx(static_file_system, file_name_buffer);
+ ctx.call(ConstByteSpan());
+
+ EXPECT_EQ(1u, ValidateExpectedPaths(static_file_system, ctx.responses()));
+}
+
+TEST(FlatFileSystem, List_ThreeFiles) {
+ std::array<std::byte, 10> file_name_buffer;
+ std::array<FakeFile, 3> files{
+ {{"SNAP_001", 372, 9}, {"tokens.csv", 808, 15038202}, {"a.txt", 0, 2}}};
+ std::array<FlatFileSystemService::FileSystemEntry*, 3> static_file_system{
+ &files[0], &files[1], &files[2]};
+
+ PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemService, List)
+ ctx(static_file_system, file_name_buffer);
+ ctx.call(ConstByteSpan());
+
+ EXPECT_EQ(3u, ValidateExpectedPaths(static_file_system, ctx.responses()));
+}
+
+TEST(FlatFileSystem, List_UnnamedFile) {
+ std::array<std::byte, 10> file_name_buffer;
+ FakeFile file{"", 0, 0};
+ std::array<FlatFileSystemService::FileSystemEntry*, 1> static_file_system{
+ &file};
+
+ PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemService, List)
+ ctx(static_file_system, file_name_buffer);
+ ctx.call(ConstByteSpan());
+
+ EXPECT_EQ(0u, ValidateExpectedPaths(static_file_system, ctx.responses()));
+}
+
+TEST(FlatFileSystem, List_FileMissingName) {
+ std::array<std::byte, 10> file_name_buffer;
+ std::array<FakeFile, 3> files{
+ {{"SNAP_001", 372, 9}, {"", 808, 15038202}, {"a.txt", 0, 2}}};
+ std::array<FlatFileSystemService::FileSystemEntry*, 3> static_file_system{
+ &files[0], &files[1], &files[2]};
+
+ PW_RAW_TEST_METHOD_CONTEXT(FlatFileSystemService, List)
+ ctx(static_file_system, file_name_buffer);
+ ctx.call(ConstByteSpan());
+
+ EXPECT_EQ(2u, ValidateExpectedPaths(static_file_system, ctx.responses()));
+}
+
+} // namespace
+} // namespace pw::file
diff --git a/pw_file/public/pw_file/flat_file_system.h b/pw_file/public/pw_file/flat_file_system.h
new file mode 100644
index 0000000..59a0e45
--- /dev/null
+++ b/pw_file/public/pw_file/flat_file_system.h
@@ -0,0 +1,100 @@
+// Copyright 2021 The Pigweed Authors
+//
+// 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
+//
+// https://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.
+#pragma once
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+#include "pw_bytes/span.h"
+#include "pw_file/file.pwpb.h"
+#include "pw_file/file.raw_rpc.pb.h"
+#include "pw_result/result.h"
+#include "pw_rpc/raw/server_reader_writer.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+
+namespace pw::file {
+
+// This implements the pw.file.FileSystem RPC service. This implementation
+// has a strict limitation that everything is treated as if the file system
+// was "flat" (i.e. no directories). This means there's no concept of logical
+// directories, despite any "path like" naming that may be employed by a user.
+class FlatFileSystemService
+ : public generated::FileSystem<FlatFileSystemService> {
+ public:
+ class FileSystemEntry {
+ public:
+ using FilePermissions = pw::file::Path::Permissions;
+ using Id = uint32_t;
+
+ FileSystemEntry() = default;
+ virtual ~FileSystemEntry() = default;
+
+ // All readable files MUST be named, and names must be globally unique to
+ // prevent ambiguity. Unnamed file entries will NOT be enumerated by a
+ // FlatFileSystemService.
+ //
+ // Returns:
+ // OK - Successfully read file name to `dest`.
+ // NOT_FOUND - No file to enumerate for this entry.
+ // RESOURCE_EXHAUSTED - `dest` buffer too small to fit the full file name.
+ virtual StatusWithSize Name(ByteSpan dest) = 0;
+
+ virtual size_t SizeBytes() = 0;
+ virtual FilePermissions Permissions() = 0;
+
+ // Deleting a file, if allowed, should cause a
+ virtual Status Delete() = 0;
+
+ // File IDs must be globally unique, and map to a pw_transfer
+ // TransferService read/write handler.
+ virtual Id FileId() = 0;
+ };
+
+ // Constructs a flat file system from a static list of file entries.
+ //
+ // Args:
+ // entry_list - A list of pointers to all FileSystemEntry objects that may
+ // contain files. These pointers may not be null. The span's underlying
+ // buffer must outlive this object.
+ // file_name_buffer - Used internally by this class to find and enumerate
+ // files. Should be large enough to hold the longest expected file name.
+ // The span's underlying buffer must outlive this object.
+ FlatFileSystemService(std::span<FileSystemEntry*> entry_list,
+ ByteSpan file_name_buffer)
+ : file_name_buffer_(file_name_buffer), entries_(entry_list) {}
+
+ // Method definitions for pw.file.FileSystem.
+ void List(ServerContext&, ConstByteSpan request, RawServerWriter& writer);
+
+ // Returns:
+ // OK - File successfully deleted.
+ // NOT_FOUND - Could not find
+ StatusWithSize Delete(ServerContext&, ConstByteSpan request, ByteSpan);
+
+ private:
+ Result<FileSystemEntry*> FindFile(std::string_view file_name);
+ Status FindAndDeleteFile(std::string_view file_name);
+
+ Status EnumerateFile(FileSystemEntry& entry,
+ pw::file::ListResponse::StreamEncoder& output_encoder);
+ void EnumerateAllFiles(RawServerWriter& writer);
+
+ ByteSpan file_name_buffer_;
+ std::span<FileSystemEntry*> entries_;
+};
+
+} // namespace pw::file