Commit 0aa9eec1 authored by erg's avatar erg Committed by Commit bot

mandoline: Fork the files service from the mojo repository.

This forks the code from //services/files/ in the mojo repository at
commit af91be731e321b06f5f88a25ff42199feb578691.

BUG=490237

Review URL: https://codereview.chromium.org/1147083002

Cr-Commit-Position: refs/heads/master@{#330803}
parent efec71da
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//third_party/mojo/src/mojo/public/mojo_application.gni")
mojo_native_application("filesystem") {
sources = [
"directory_impl.cc",
"directory_impl.h",
"file_impl.cc",
"file_impl.h",
"files_impl.cc",
"files_impl.h",
"futimens.h",
"futimens_android.cc",
"main.cc",
"shared_impl.cc",
"shared_impl.h",
"util.cc",
"util.h",
]
deps = [
"//base",
"//components/filesystem/public/interfaces",
"//mojo/application/public/cpp",
"//mojo/common",
"//mojo/environment:chromium",
"//third_party/mojo/src/mojo/public/cpp/bindings",
"//third_party/mojo/src/mojo/public/cpp/system",
]
}
mojo_native_application("apptests") {
output_name = "files_apptests"
testonly = true
sources = [
"directory_impl_unittest.cc",
"file_impl_unittest.cc",
"files_test_base.cc",
"files_test_base.h",
]
deps = [
"//base",
"//components/filesystem/public/interfaces",
"//mojo/application/public/cpp:test_support",
"//third_party/mojo/src/mojo/public/cpp/bindings",
]
}
include_rules = [
"+mojo/application",
"+mojo/public",
]
This diff is collapsed.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_DIRECTORY_IMPL_H_
#define SERVICES_FILES_DIRECTORY_IMPL_H_
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "components/filesystem/public/interfaces/directory.mojom.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
namespace base {
class ScopedTempDir;
} // namespace base
namespace mojo {
namespace files {
class DirectoryImpl : public Directory {
public:
// Set |temp_dir| only if there's a temporary directory that should be deleted
// when this object is destroyed.
DirectoryImpl(InterfaceRequest<Directory> request,
base::ScopedFD dir_fd,
scoped_ptr<base::ScopedTempDir> temp_dir);
~DirectoryImpl() override;
// |Directory| implementation:
void Read(const ReadCallback& callback) override;
void Stat(const StatCallback& callback) override;
void Touch(TimespecOrNowPtr atime,
TimespecOrNowPtr mtime,
const TouchCallback& callback) override;
void OpenFile(const String& path,
InterfaceRequest<File> file,
uint32_t open_flags,
const OpenFileCallback& callback) override;
void OpenDirectory(const String& path,
InterfaceRequest<Directory> directory,
uint32_t open_flags,
const OpenDirectoryCallback& callback) override;
void Rename(const String& path,
const String& new_path,
const RenameCallback& callback) override;
void Delete(const String& path,
uint32_t delete_flags,
const DeleteCallback& callback) override;
private:
StrongBinding<Directory> binding_;
base::ScopedFD dir_fd_;
scoped_ptr<base::ScopedTempDir> temp_dir_;
DISALLOW_COPY_AND_ASSIGN(DirectoryImpl);
};
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_DIRECTORY_IMPL_H_
// Copyright 2015 The Chromium 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 <map>
#include <string>
#include "components/filesystem/files_test_base.h"
namespace mojo {
namespace files {
namespace {
using DirectoryImplTest = FilesTestBase;
TEST_F(DirectoryImplTest, Read) {
DirectoryPtr directory;
GetTemporaryRoot(&directory);
Error error;
// Make some files.
const struct {
const char* name;
uint32_t open_flags;
} files_to_create[] = {
{"my_file1", kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate},
{"my_file2", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagExclusive},
{"my_file3", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagAppend},
{"my_file4", kOpenFlagWrite | kOpenFlagCreate | kOpenFlagTruncate}};
for (size_t i = 0; i < arraysize(files_to_create); i++) {
error = ERROR_INTERNAL;
directory->OpenFile(files_to_create[i].name, nullptr,
files_to_create[i].open_flags, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
}
// Make a directory.
error = ERROR_INTERNAL;
directory->OpenDirectory("my_dir", nullptr,
kOpenFlagRead | kOpenFlagWrite | kOpenFlagCreate,
Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
error = ERROR_INTERNAL;
Array<DirectoryEntryPtr> directory_contents;
directory->Read(Capture(&error, &directory_contents));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Expected contents of the directory.
std::map<std::string, FileType> expected_contents;
expected_contents["my_file1"] = FILE_TYPE_REGULAR_FILE;
expected_contents["my_file2"] = FILE_TYPE_REGULAR_FILE;
expected_contents["my_file3"] = FILE_TYPE_REGULAR_FILE;
expected_contents["my_file4"] = FILE_TYPE_REGULAR_FILE;
expected_contents["my_dir"] = FILE_TYPE_DIRECTORY;
expected_contents["."] = FILE_TYPE_DIRECTORY;
expected_contents[".."] = FILE_TYPE_DIRECTORY;
EXPECT_EQ(expected_contents.size(), directory_contents.size());
for (size_t i = 0; i < directory_contents.size(); i++) {
ASSERT_TRUE(directory_contents[i]);
ASSERT_TRUE(directory_contents[i]->name);
auto it = expected_contents.find(directory_contents[i]->name.get());
ASSERT_TRUE(it != expected_contents.end());
EXPECT_EQ(it->second, directory_contents[i]->type);
expected_contents.erase(it);
}
}
// Note: Ignore nanoseconds, since it may not always be supported. We expect at
// least second-resolution support though.
// TODO(vtl): Maybe share this with |FileImplTest.StatTouch| ... but then it'd
// be harder to split this file.
TEST_F(DirectoryImplTest, StatTouch) {
DirectoryPtr directory;
GetTemporaryRoot(&directory);
Error error;
// Stat it.
error = ERROR_INTERNAL;
FileInformationPtr file_info;
directory->Stat(Capture(&error, &file_info));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
ASSERT_FALSE(file_info.is_null());
EXPECT_EQ(FILE_TYPE_DIRECTORY, file_info->type);
EXPECT_EQ(0, file_info->size);
ASSERT_FALSE(file_info->atime.is_null());
EXPECT_GT(file_info->atime->seconds, 0); // Expect that it's not 1970-01-01.
ASSERT_FALSE(file_info->mtime.is_null());
EXPECT_GT(file_info->mtime->seconds, 0);
int64_t first_mtime = file_info->mtime->seconds;
// Touch only the atime.
error = ERROR_INTERNAL;
TimespecOrNowPtr t(TimespecOrNow::New());
t->now = false;
t->timespec = Timespec::New();
const int64_t kPartyTime1 = 1234567890; // Party like it's 2009-02-13.
t->timespec->seconds = kPartyTime1;
directory->Touch(t.Pass(), nullptr, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Stat again.
error = ERROR_INTERNAL;
file_info.reset();
directory->Stat(Capture(&error, &file_info));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
ASSERT_FALSE(file_info.is_null());
ASSERT_FALSE(file_info->atime.is_null());
EXPECT_EQ(kPartyTime1, file_info->atime->seconds);
ASSERT_FALSE(file_info->mtime.is_null());
EXPECT_EQ(first_mtime, file_info->mtime->seconds);
// Touch only the mtime.
t = TimespecOrNow::New();
t->now = false;
t->timespec = Timespec::New();
const int64_t kPartyTime2 = 1425059525; // No time like the present.
t->timespec->seconds = kPartyTime2;
directory->Touch(nullptr, t.Pass(), Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Stat again.
error = ERROR_INTERNAL;
file_info.reset();
directory->Stat(Capture(&error, &file_info));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
ASSERT_FALSE(file_info.is_null());
ASSERT_FALSE(file_info->atime.is_null());
EXPECT_EQ(kPartyTime1, file_info->atime->seconds);
ASSERT_FALSE(file_info->mtime.is_null());
EXPECT_EQ(kPartyTime2, file_info->mtime->seconds);
// TODO(vtl): Also test Touch() "now" options.
// TODO(vtl): Also test touching both atime and mtime.
}
// TODO(vtl): Properly test OpenFile() and OpenDirectory() (including flags).
TEST_F(DirectoryImplTest, BasicRenameDelete) {
DirectoryPtr directory;
GetTemporaryRoot(&directory);
Error error;
// Create my_file.
error = ERROR_INTERNAL;
directory->OpenFile("my_file", nullptr, kOpenFlagWrite | kOpenFlagCreate,
Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Opening my_file should succeed.
error = ERROR_INTERNAL;
directory->OpenFile("my_file", nullptr, kOpenFlagRead, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Rename my_file to my_new_file.
directory->Rename("my_file", "my_new_file", Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Opening my_file should fail.
error = ERROR_INTERNAL;
directory->OpenFile("my_file", nullptr, kOpenFlagRead, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_UNKNOWN, error);
// Opening my_new_file should succeed.
error = ERROR_INTERNAL;
directory->OpenFile("my_new_file", nullptr, kOpenFlagRead, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Delete my_new_file (no flags).
directory->Delete("my_new_file", 0, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_OK, error);
// Opening my_new_file should fail.
error = ERROR_INTERNAL;
directory->OpenFile("my_new_file", nullptr, kOpenFlagRead, Capture(&error));
ASSERT_TRUE(directory.WaitForIncomingMethodCall());
EXPECT_EQ(ERROR_UNKNOWN, error);
}
// TODO(vtl): Test that an open file can be moved (by someone else) without
// operations on it being affected.
// TODO(vtl): Test delete flags.
} // namespace
} // namespace files
} // namespace mojo
This diff is collapsed.
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_FILE_IMPL_H_
#define SERVICES_FILES_FILE_IMPL_H_
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "components/filesystem/public/interfaces/directory.mojom.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
namespace mojo {
namespace files {
class FileImpl : public File {
public:
// TODO(vtl): Will need more for, e.g., |Reopen()|.
FileImpl(InterfaceRequest<File> request, base::ScopedFD file_fd);
~FileImpl() override;
// |File| implementation:
void Close(const CloseCallback& callback) override;
void Read(uint32_t num_bytes_to_read,
int64_t offset,
Whence whence,
const ReadCallback& callback) override;
void Write(Array<uint8_t> bytes_to_write,
int64_t offset,
Whence whence,
const WriteCallback& callback) override;
void ReadToStream(ScopedDataPipeProducerHandle source,
int64_t offset,
Whence whence,
int64_t num_bytes_to_read,
const ReadToStreamCallback& callback) override;
void WriteFromStream(ScopedDataPipeConsumerHandle sink,
int64_t offset,
Whence whence,
const WriteFromStreamCallback& callback) override;
void Tell(const TellCallback& callback) override;
void Seek(int64_t offset,
Whence whence,
const SeekCallback& callback) override;
void Stat(const StatCallback& callback) override;
void Truncate(int64_t size, const TruncateCallback& callback) override;
void Touch(TimespecOrNowPtr atime,
TimespecOrNowPtr mtime,
const TouchCallback& callback) override;
void Dup(InterfaceRequest<File> file, const DupCallback& callback) override;
void Reopen(InterfaceRequest<File> file,
uint32_t open_flags,
const ReopenCallback& callback) override;
void AsBuffer(const AsBufferCallback& callback) override;
void Ioctl(uint32_t request,
Array<uint32_t> in_values,
const IoctlCallback& callback) override;
private:
StrongBinding<File> binding_;
base::ScopedFD file_fd_;
DISALLOW_COPY_AND_ASSIGN(FileImpl);
};
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_FILE_IMPL_H_
This diff is collapsed.
// Copyright 2015 The Chromium 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 "components/filesystem/files_impl.h"
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/files/file_path.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/posix/eintr_wrapper.h"
#include "components/filesystem/directory_impl.h"
namespace mojo {
namespace files {
namespace {
base::ScopedFD CreateAndOpenTemporaryDirectory(
scoped_ptr<base::ScopedTempDir>* temp_dir) {
(*temp_dir).reset(new base::ScopedTempDir());
CHECK((*temp_dir)->CreateUniqueTempDir());
base::ScopedFD temp_dir_fd(HANDLE_EINTR(
open((*temp_dir)->path().value().c_str(), O_RDONLY | O_DIRECTORY, 0)));
PCHECK(temp_dir_fd.is_valid());
DVLOG(1) << "Made a temporary directory: " << (*temp_dir)->path().value();
return temp_dir_fd.Pass();
}
#ifndef NDEBUG
base::ScopedFD OpenMojoDebugDirectory() {
const char* home_dir_name = getenv("HOME");
if (!home_dir_name || !home_dir_name[0]) {
LOG(ERROR) << "HOME not set";
return base::ScopedFD();
}
base::FilePath mojo_debug_dir_name =
base::FilePath(home_dir_name).Append("MojoDebug");
return base::ScopedFD(HANDLE_EINTR(
open(mojo_debug_dir_name.value().c_str(), O_RDONLY | O_DIRECTORY, 0)));
}
#endif
} // namespace
FilesImpl::FilesImpl(ApplicationConnection* connection,
InterfaceRequest<Files> request)
: binding_(this, request.Pass()) {
// TODO(vtl): record other app's URL
}
FilesImpl::~FilesImpl() {
}
void FilesImpl::OpenFileSystem(const mojo::String& file_system,
InterfaceRequest<Directory> directory,
const OpenFileSystemCallback& callback) {
base::ScopedFD dir_fd;
// Set only if the |DirectoryImpl| will own a temporary directory.
scoped_ptr<base::ScopedTempDir> temp_dir;
if (file_system.is_null()) {
// TODO(vtl): ScopedGeneric (hence ScopedFD) doesn't have an operator=!
dir_fd.reset(CreateAndOpenTemporaryDirectory(&temp_dir).release());
DCHECK(temp_dir);
} else if (file_system.get() == std::string("debug")) {
#ifdef NDEBUG
LOG(WARNING) << "~/MojoDebug only available in Debug builds";
#else
// TODO(vtl): ScopedGeneric (hence ScopedFD) doesn't have an operator=!
dir_fd.reset(OpenMojoDebugDirectory().release());
#endif
if (!dir_fd.is_valid()) {
LOG(ERROR) << "~/MojoDebug unavailable";
callback.Run(ERROR_UNAVAILABLE);
return;
}
} else {
LOG(ERROR) << "Unknown file system: " << file_system.get();
callback.Run(ERROR_UNIMPLEMENTED);
return;
}
new DirectoryImpl(directory.Pass(), dir_fd.Pass(), temp_dir.Pass());
callback.Run(ERROR_OK);
}
} // namespace files
} // namespace mojo
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_FILES_IMPL_H_
#define SERVICES_FILES_FILES_IMPL_H_
#include "base/macros.h"
#include "components/filesystem/public/interfaces/files.mojom.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
namespace mojo {
class ApplicationConnection;
namespace files {
class FilesImpl : public Files {
public:
FilesImpl(ApplicationConnection* connection, InterfaceRequest<Files> request);
~FilesImpl() override;
// |Files| implementation:
// We provide a "private" temporary file system as the default. In Debug
// builds, we also provide access to a common file system named "debug"
// (stored under ~/MojoDebug).
void OpenFileSystem(const mojo::String& file_system,
InterfaceRequest<Directory> directory,
const OpenFileSystemCallback& callback) override;
private:
StrongBinding<Files> binding_;
DISALLOW_COPY_AND_ASSIGN(FilesImpl);
};
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_FILES_IMPL_H_
// Copyright 2015 The Chromium 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 "components/filesystem/files_test_base.h"
#include "components/filesystem/public/interfaces/directory.mojom.h"
#include "components/filesystem/public/interfaces/types.mojom.h"
#include "mojo/application/public/cpp/application_impl.h"
namespace mojo {
namespace files {
FilesTestBase::FilesTestBase() {
}
FilesTestBase::~FilesTestBase() {
}
void FilesTestBase::SetUp() {
test::ApplicationTestBase::SetUp();
application_impl()->ConnectToService("mojo:files", &files_);
}
void FilesTestBase::GetTemporaryRoot(DirectoryPtr* directory) {
Error error = ERROR_INTERNAL;
files()->OpenFileSystem(nullptr, GetProxy(directory), Capture(&error));
ASSERT_TRUE(files().WaitForIncomingMethodCall());
ASSERT_EQ(ERROR_OK, error);
}
} // namespace files
} // namespace mojo
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_FILES_TEST_BASE_H_
#define SERVICES_FILES_FILES_TEST_BASE_H_
#include "base/macros.h"
#include "components/filesystem/public/interfaces/files.mojom.h"
#include "mojo/application/public/cpp/application_test_base.h"
namespace mojo {
namespace files {
// TODO(vtl): Stuff copied from mojo/public/cpp/bindings/lib/template_util.h.
template <class T, T v>
struct IntegralConstant {
static const T value = v;
};
template <class T, T v>
const T IntegralConstant<T, v>::value;
typedef IntegralConstant<bool, true> TrueType;
typedef IntegralConstant<bool, false> FalseType;
template <class T>
struct IsConst : FalseType {};
template <class T>
struct IsConst<const T> : TrueType {};
template <bool B, typename T = void>
struct EnableIf {};
template <typename T>
struct EnableIf<true, T> {
typedef T type;
};
typedef char YesType;
struct NoType {
YesType dummy[2];
};
template <typename T>
struct IsMoveOnlyType {
template <typename U>
static YesType Test(const typename U::MoveOnlyTypeForCPP03*);
template <typename U>
static NoType Test(...);
static const bool value =
sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value;
};
template <typename T>
typename EnableIf<!IsMoveOnlyType<T>::value, T>::type& Forward(T& t) {
return t;
}
template <typename T>
typename EnableIf<IsMoveOnlyType<T>::value, T>::type Forward(T& t) {
return t.Pass();
}
// TODO(vtl): (End of stuff copied from template_util.h.)
template <typename T1>
Callback<void(T1)> Capture(T1* t1) {
return [t1](T1 got_t1) { *t1 = Forward(got_t1); };
}
template <typename T1, typename T2>
Callback<void(T1, T2)> Capture(T1* t1, T2* t2) {
return [t1, t2](T1 got_t1, T2 got_t2) {
*t1 = Forward(got_t1);
*t2 = Forward(got_t2);
};
}
class FilesTestBase : public test::ApplicationTestBase {
public:
FilesTestBase();
~FilesTestBase() override;
void SetUp() override;
protected:
// Note: This has an out parameter rather than returning the |DirectoryPtr|,
// since |ASSERT_...()| doesn't work with return values.
void GetTemporaryRoot(DirectoryPtr* directory);
FilesPtr& files() { return files_; }
private:
FilesPtr files_;
DISALLOW_COPY_AND_ASSIGN(FilesTestBase);
};
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_FILES_TEST_BASE_H_
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_FUTIMENS_H_
#define SERVICES_FILES_FUTIMENS_H_
#include <sys/stat.h>
#include "build/build_config.h"
// For Android: The NDK/C library we currently build against doesn't have
// |futimens()|, but has |utimensat()|. Provide the former (in terms of the
// latter) for now. Remove this file and futimens_android.cc when |futimens()|
// becomes generally available (see
// https://android-review.googlesource.com/#/c/63321/).
#if defined(OS_ANDROID)
extern "C" {
int futimens(int fd, const struct timespec times[2]);
} // extern "C"
#endif
#endif // SERVICES_FILES_FUTIMENS_H_
// Copyright 2015 The Chromium 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 "components/filesystem/futimens.h"
extern "C" {
int futimens(int fd, const struct timespec times[2]) {
return utimensat(fd, nullptr, times, 0);
}
} // extern "C"
// Copyright 2015 The Chromium 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 "base/macros.h"
#include "components/filesystem/files_impl.h"
#include "components/filesystem/public/interfaces/files.mojom.h"
#include "mojo/application/public/cpp/application_connection.h"
#include "mojo/application/public/cpp/application_delegate.h"
#include "mojo/application/public/cpp/application_runner.h"
#include "mojo/application/public/cpp/interface_factory.h"
#include "mojo/public/c/system/main.h"
namespace mojo {
namespace files {
class FilesApp : public ApplicationDelegate, public InterfaceFactory<Files> {
public:
FilesApp() {}
~FilesApp() override {}
private:
// |ApplicationDelegate| override:
bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
connection->AddService<Files>(this);
return true;
}
// |InterfaceFactory<Files>| implementation:
void Create(ApplicationConnection* connection,
InterfaceRequest<Files> request) override {
new FilesImpl(connection, request.Pass());
}
DISALLOW_COPY_AND_ASSIGN(FilesApp);
};
} // namespace files
} // namespace mojo
MojoResult MojoMain(MojoHandle application_request) {
mojo::ApplicationRunner runner(new mojo::files::FilesApp());
return runner.Run(application_request);
}
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/module_args/mojo.gni")
import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni")
mojom("interfaces") {
sources = [
"directory.mojom",
"file.mojom",
"files.mojom",
"types.mojom",
]
# import_dirs = [ get_path_info("../../../", "abspath") ]
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module mojo.files;
import "components/filesystem/public/interfaces/file.mojom";
import "components/filesystem/public/interfaces/types.mojom";
// This interface provides access to a directory in a "file system", providing
// operations such as creating/opening/removing/renaming files/directories
// within it. Note that all relative |path| arguments are relative to "this"
// directory (i.e., "this" directory functions as the current working directory
// for the various operations).
// TODO(vtl): Paths may be relative; should they allowed to be absolute?
// (Currently not.)
interface Directory {
// Operations about "this" |Directory|:
// Reads the contents of this directory.
// TODO(vtl): Clarify error codes versus |directory_contents|.
Read() => (Error error, array<DirectoryEntry>? directory_contents);
// Gets information about this directory. On success, |file_information| is
// non-null and will contain this information.
Stat() => (Error error, FileInformation? file_information);
// Updates this directory's atime and/or mtime to the time specified by
// |atime| (or |mtime|, respectively), which may also indicate "now". If
// |atime| or |mtime| is null, then the corresponding time is not modified.
Touch(TimespecOrNow? atime, TimespecOrNow? mtime) => (Error error);
// Operations *in* "this" |Directory|:
// Opens the file specified by |path| with the given |open_flags|. |file| is
// optional, mainly for consistency with |OpenDirectory()| (but may be useful,
// together with |kOpenFlagCreate|, for "touching" a file).
OpenFile(string path, File&? file, uint32 open_flags)
=> (Error error);
// Opens the directory specified by |path|. |directory| is optional, so that
// this may be used as a simple "mkdir()" with |kOpenFlagCreate|.
OpenDirectory(string path,
Directory&? directory,
uint32 open_flags) => (Error error);
// Renames/moves the file/directory given by |path| to |new_path|.
Rename(string path, string new_path) => (Error error);
// Deletes the given path, which may be a file or a directory (see
// |kDeleteFlag...| for details).
Delete(string path, uint32 delete_flags) => (Error error);
// TODO(vtl): directory "streaming"?
// TODO(vtl): "make root" (i.e., prevent cd-ing, etc., to parent); note that
// this would require a much more complicated implementation (e.g., it needs
// to be "inherited" by OpenDirectory(), and the enforcement needs to be valid
// even if the opened directory is subsequently moved -- e.g., closer to the
// "root")
// TODO(vtl): Add a "watch"?
// TODO(vtl): Should we have a "close" method?
// TODO(vtl): Add Dup() and Reopen() (like File)?
};
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(vtl): notes to self:
// - file offsets, file positions, and file sizes are int64 (though positions
// and sizes must always be non-negative)
// - buffer size parameters (for read/write) are uint32
module mojo.files;
import "components/filesystem/public/interfaces/types.mojom";
// TODO(vtl): Write comments.
interface File {
// Flushes/closes this file; no operations may be performed on this file after
// this. Note that any error code is strictly informational -- the close may
// not be retried.
Close() => (Error err);
// Reads (at most) |num_bytes_to_read| from the location specified by
// |offset|/|whence|. On success, |bytes_read| is set to the data read.
// TODO(vtl): Define/clarify behavior when less than |num_bytes_to_read| bytes
// are read.
// TODO(vtl): Clarify when (for what values of |offset|/|whence|) this
// modifies the file position. Or maybe there should be a flag?
Read(uint32 num_bytes_to_read, int64 offset, Whence whence)
=> (Error error, array<uint8>? bytes_read);
// Writes |bytes_to_write| to the location specified by |offset|/|whence|.
// TODO(vtl): Clarify behavior when |num_bytes_written| is less than the size
// of |bytes_to_write|.
Write(array<uint8> bytes_to_write, int64 offset, Whence whence)
=> (Error error, uint32 num_bytes_written);
// TODO(vtl): We definitely want 64 bits for |num_bytes_to_read|; but do we
// want it to be signed (this is consistent with |size| values, but
// inconsistent with 32-bit |num_bytes_to_read| values)? Do we want to have
// separate "read to end" versus "tail" (i.e., keep on reading as more data is
// appended) modes, and how would those be signalled?
ReadToStream(handle<data_pipe_producer> source,
int64 offset,
Whence whence,
int64 num_bytes_to_read) => (Error error);
WriteFromStream(handle<data_pipe_consumer> sink, int64 offset, Whence whence)
=> (Error error);
// Gets the current file position. On success, |position| is the current
// offset (in bytes) from the beginning of the file).
Tell() => (Error error, int64 position);
// Sets the current file position to that specified by |offset|/|whence|. On
// success, |position| is the offset (in bytes) from the beginning of the
// file.
Seek(int64 offset, Whence whence) => (Error error, int64 position);
// Gets information about this file. On success, |file_information| is
// non-null and will contain this information.
Stat() => (Error error, FileInformation? file_information);
// Truncates this file to the size specified by |size| (in bytes).
Truncate(int64 size) => (Error error);
// Updates this file's atime and/or mtime to the time specified by |atime| (or
// |mtime|, respectively), which may also indicate "now". If |atime| or
// |mtime| is null, then the corresponding time is not modified.
Touch(TimespecOrNow? atime, TimespecOrNow? mtime) => (Error error);
// Creates a new |File| instance, which shares the same "file description".
// I.e., the access mode, etc. (as specified to |Directory::OpenFile()| by the
// |open_flags| argument) as well as file position.
Dup(File& file) => (Error error);
// TODO(vtl): What are the rules for reopening (w.r.t. changing mode/flags).
// E.g., obviously can go from "read-write" to "read", but reverse? (probably
// not), can remove "append"? (probably not?). Do we allow "truncate"?
Reopen(File& file, uint32 open_flags) => (Error error);
// TODO(vtl): probably should have access flags (but also exec?); how do these
// relate to access mode?
AsBuffer() => (Error error, handle<shared_buffer>? buffer);
// Special-file-specific control function, for device "files". |in| and |out|
// are dependent on |request|.
// TODO(vtl): Make a master list of request values somewhere.
Ioctl(uint32 request, array<uint32>? in_values)
=> (Error error, array<uint32>? out_values);
// TODO(vtl): Add a "watch"?
// TODO(vtl): Add something analogous to fsync(2)?
};
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module mojo.files;
import "components/filesystem/public/interfaces/directory.mojom";
import "components/filesystem/public/interfaces/types.mojom";
interface Files {
// Opens the root directory for the file system with the given name; null
// yields the default file system, if any.
OpenFileSystem(string? file_system, Directory& directory) => (Error error);
};
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module mojo.files;
// Error codes used by the file manager.
// TODO(vtl): Add more (to, e.g., cover all of errno).
enum Error {
OK = 0,
UNKNOWN,
INVALID_ARGUMENT,
PERMISSION_DENIED,
OUT_OF_RANGE,
UNIMPLEMENTED,
CLOSED,
UNAVAILABLE,
INTERNAL,
};
// Used to explain the meaning of an offset within a file.
enum Whence {
// Offset is from current position in the file.
FROM_CURRENT = 0,
// Offset is relative to the beginning of the file.
FROM_START,
// Offset is relative to the end of the file.
FROM_END,
};
// Describes (idealized) wall-clock time, since Unix epoch (i.e., since
// "1970-01-01 00:00 UTC", ignoring leap seconds and that UTC as we know it
// started in 1972).
// TODO(vtl): Should probably be moved out of mojo.files (maybe to mojo.time?).
struct Timespec {
int64 seconds;
int32 nanoseconds; // Always in the interval [0, 10^9).
};
// Used for |Touch()| calls. If |now| is set, |timespec| must be null (the time
// "now" will be used). Otherwise, |timespec| must not be null.
// TODO(vtl): Use a union instead, when that becomes possible.
struct TimespecOrNow {
bool now;
Timespec? timespec;
};
// Describes various information about a file or directory (for |Stat()|). Note
// that access/modification times may be set arbitrarily (by those with
// appropriate capabilities) and may not reflect reality.
struct FileInformation {
// Type of the file.
FileType type;
// Size of the file, in bytes. Zero for directories.
int64 size;
// Last access time, if available/supported.
Timespec? atime;
// Last modification time, if available/supported.
Timespec? mtime;
};
// File and directory open flags (at least one of |kOpenFlagRead| and
// |kOpenFlagWrite| is required):
// Opens the file/directory for reading.
const uint32 kOpenFlagRead = 0x1;
// Opens the file/directory for writing.
const uint32 kOpenFlagWrite = 0x2;
// Only meaningful together with |kOpenFlagWrite|: creates the file if
// necessary.
const uint32 kOpenFlagCreate = 0x4;
// Only meaningful together with |kOpenFlagCreate|: requires file/directory to
// be created, failing if it already exists.
const uint32 kOpenFlagExclusive = 0x8;
// Only meaningful for files, together with |kOpenFlagWrite|: writes will always
// append to the file.
const uint32 kOpenFlagAppend = 0x10;
// Only meaningful for files, together with |kOpenFlagWrite|: truncates the
// file.
const uint32 kOpenFlagTruncate = 0x20;
// File types.
enum FileType {
UNKNOWN = 0,
REGULAR_FILE,
DIRECTORY,
};
// Describes a directory entry (i.e., a single member of a directory).
struct DirectoryEntry {
FileType type;
string name;
};
// Deletion flags:
// Only delete if the path refers to a file/non-directory (by default, will
// delete files and directories).
const uint32 kDeleteFlagFileOnly = 0x1;
// Only delete if the path refers to a directory.
const uint32 kDeleteFlagDirectoryOnly = 0x2;
// Recursively delete (neither of the two flags above may be specified).
const uint32 kDeleteFlagRecursive = 0x4;
// Copyright 2015 The Chromium 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 "components/filesystem/shared_impl.h"
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "base/logging.h"
#include "components/filesystem/futimens.h"
#include "components/filesystem/util.h"
namespace mojo {
namespace files {
void StatFD(int fd, FileType type, const StatFDCallback& callback) {
DCHECK_NE(fd, -1);
struct stat buf;
if (fstat(fd, &buf) != 0) {
callback.Run(ErrnoToError(errno), nullptr);
return;
}
FileInformationPtr file_info(FileInformation::New());
file_info->type = type;
// Only fill in |size| for files.
if (S_ISREG(buf.st_mode)) {
file_info->size = static_cast<int64_t>(buf.st_size);
} else {
LOG_IF(WARNING, !S_ISDIR(buf.st_mode))
<< "Unexpected fstat() of special file";
file_info->size = 0;
}
file_info->atime = Timespec::New();
file_info->mtime = Timespec::New();
#if defined(OS_ANDROID)
file_info->atime->seconds = static_cast<int64_t>(buf.st_atime);
file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atime_nsec);
file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtime);
file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtime_nsec);
#else
file_info->atime->seconds = static_cast<int64_t>(buf.st_atim.tv_sec);
file_info->atime->nanoseconds = static_cast<int32_t>(buf.st_atim.tv_nsec);
file_info->mtime->seconds = static_cast<int64_t>(buf.st_mtim.tv_sec);
file_info->mtime->nanoseconds = static_cast<int32_t>(buf.st_mtim.tv_nsec);
#endif
callback.Run(ERROR_OK, file_info.Pass());
}
void TouchFD(int fd,
TimespecOrNowPtr atime,
TimespecOrNowPtr mtime,
const TouchFDCallback& callback) {
DCHECK_NE(fd, -1);
struct timespec times[2];
if (Error error = TimespecOrNowToStandardTimespec(atime.get(), &times[0])) {
callback.Run(error);
return;
}
if (Error error = TimespecOrNowToStandardTimespec(mtime.get(), &times[1])) {
callback.Run(error);
return;
}
if (futimens(fd, times) != 0) {
callback.Run(ErrnoToError(errno));
return;
}
callback.Run(ERROR_OK);
}
} // namespace files
} // namespace mojo
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Shared implementation of things common between |DirectoryImpl| and
// |FileImpl|.
#ifndef SERVICES_FILES_SHARED_IMPL_H_
#define SERVICES_FILES_SHARED_IMPL_H_
#include "components/filesystem/public/interfaces/types.mojom.h"
#include "mojo/public/cpp/bindings/callback.h"
namespace mojo {
namespace files {
// Stats the given FD (which must be valid), calling |callback| appropriately.
// The type in the |FileInformation| given to the callback will be assigned from
// |type|.
using StatFDCallback = Callback<void(Error, FileInformationPtr)>;
void StatFD(int fd, FileType type, const StatFDCallback& callback);
// Touches the given FD (which must be valid), calling |callback| appropriately.
using TouchFDCallback = Callback<void(Error)>;
void TouchFD(int fd,
TimespecOrNowPtr atime,
TimespecOrNowPtr mtime,
const TouchFDCallback& callback);
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_SHARED_IMPL_H_
// Copyright 2015 The Chromium 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 "components/filesystem/util.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <limits>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "mojo/public/cpp/bindings/string.h"
namespace mojo {
namespace files {
Error IsPathValid(const String& path) {
DCHECK(!path.is_null());
if (!base::IsStringUTF8(path.get()))
return ERROR_INVALID_ARGUMENT;
if (path.size() > 0 && path[0] == '/')
return ERROR_PERMISSION_DENIED;
return ERROR_OK;
}
Error IsWhenceValid(Whence whence) {
return (whence == WHENCE_FROM_CURRENT || whence == WHENCE_FROM_START ||
whence == WHENCE_FROM_END)
? ERROR_OK
: ERROR_UNIMPLEMENTED;
}
Error IsOffsetValid(int64_t offset) {
return (offset >= std::numeric_limits<off_t>::min() &&
offset <= std::numeric_limits<off_t>::max())
? ERROR_OK
: ERROR_OUT_OF_RANGE;
}
Error ErrnoToError(int errno_value) {
// TODO(vtl)
return ERROR_UNKNOWN;
}
int WhenceToStandardWhence(Whence whence) {
DCHECK_EQ(IsWhenceValid(whence), ERROR_OK);
switch (whence) {
case WHENCE_FROM_CURRENT:
return SEEK_CUR;
case WHENCE_FROM_START:
return SEEK_SET;
case WHENCE_FROM_END:
return SEEK_END;
}
NOTREACHED();
return 0;
}
Error TimespecToStandardTimespec(const Timespec* in, struct timespec* out) {
if (!in) {
out->tv_sec = 0;
out->tv_nsec = UTIME_OMIT;
return ERROR_OK;
}
static_assert(sizeof(int64_t) >= sizeof(time_t), "whoa, time_t is huge");
if (in->seconds < std::numeric_limits<time_t>::min() ||
in->seconds > std::numeric_limits<time_t>::max())
return ERROR_OUT_OF_RANGE;
if (in->nanoseconds < 0 || in->nanoseconds >= 1000000000)
return ERROR_INVALID_ARGUMENT;
out->tv_sec = static_cast<time_t>(in->seconds);
out->tv_nsec = static_cast<long>(in->nanoseconds);
return ERROR_OK;
}
Error TimespecOrNowToStandardTimespec(const TimespecOrNow* in,
struct timespec* out) {
if (!in) {
out->tv_sec = 0;
out->tv_nsec = UTIME_OMIT;
return ERROR_OK;
}
if (in->now) {
if (!in->timespec.is_null())
return ERROR_INVALID_ARGUMENT;
out->tv_sec = 0;
out->tv_nsec = UTIME_NOW;
return ERROR_OK;
}
return TimespecToStandardTimespec(in->timespec.get(), out);
}
} // namespace files
} // namespace mojo
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_FILES_UTIL_H_
#define SERVICES_FILES_UTIL_H_
#include "components/filesystem/public/interfaces/types.mojom.h"
namespace mojo {
class String;
namespace files {
// Validation functions (typically used to check arguments; they return
// |ERROR_OK| if valid, else the standard/recommended error for the validation
// error):
// Checks if |path|, which must be non-null, is (looks like) a valid (relative)
// path. (On failure, returns |ERROR_INVALID_ARGUMENT| if |path| is not UTF-8,
// or |ERROR_PERMISSION_DENIED| if it is not relative.)
Error IsPathValid(const String& path);
// Checks if |whence| is a valid (known) |Whence| value. (On failure, returns
// |ERROR_UNIMPLEMENTED|.)
Error IsWhenceValid(Whence whence);
// Checks if |offset| is a valid file offset (from some point); this is
// implementation-dependent (typically checking if |offset| fits in an |off_t|).
// (On failure, returns |ERROR_OUT_OF_RANGE|.)
Error IsOffsetValid(int64_t offset);
// Conversion functions:
// Converts a standard errno value (|E...|) to an |Error| value.
Error ErrnoToError(int errno_value);
// Converts a |Whence| value to a standard whence value (|SEEK_...|).
int WhenceToStandardWhence(Whence whence);
// Converts a |Timespec| to a |struct timespec|. If |in| is null, |out->tv_nsec|
// is set to |UTIME_OMIT|.
Error TimespecToStandardTimespec(const Timespec* in, struct timespec* out);
// Converts a |TimespecOrNow| to a |struct timespec|. If |in| is null,
// |out->tv_nsec| is set to |UTIME_OMIT|; if |in->now| is set, |out->tv_nsec| is
// set to |UTIME_NOW|.
Error TimespecOrNowToStandardTimespec(const TimespecOrNow* in,
struct timespec* out);
} // namespace files
} // namespace mojo
#endif // SERVICES_FILES_UTIL_H_
......@@ -12,6 +12,10 @@ group("all") {
if (!is_component_build) {
deps += [ "//mandoline/app" ]
if (!is_win) {
deps += [ "//components/filesystem" ]
}
}
}
......@@ -31,5 +35,9 @@ group("tests") {
"//components/resource_provider:tests",
"//components/view_manager:tests",
]
if (!is_win) {
deps += [ "//components/filesystem:apptests" ]
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment