blob: f327c7c0954a39f29600c6bedbcb30e1c427d0f8 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "smbfs/recursive_delete_operation.h"
#include <list>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/logging.h>
#include <base/posix/safe_strerror.h>
#include "smbfs/samba_interface.h"
#include "smbfs/smb_filesystem.h"
namespace smbfs {
RecursiveDeleteOperation::RecursiveDeleteOperation(
SambaInterface* samba_impl,
const std::string& base_share_path,
const base::FilePath& root_path,
CompletionCallback completion_callback)
: samba_impl_(samba_impl),
base_share_path_(base_share_path),
root_path_(root_path),
completion_callback_(std::move(completion_callback)) {
CHECK_NE('/', base_share_path.back());
}
void RecursiveDeleteOperation::SetSambaInterface(SambaInterface* samba_impl) {
samba_impl_ = samba_impl;
}
void RecursiveDeleteOperation::Start() {
std::string share_path = MakeSharePath(root_path_);
struct stat entry_stat = {0};
int error = samba_impl_->Stat(share_path, &entry_stat);
if (error) {
VLOG(1) << "Stat path: " << share_path
<< " failed: " << base::safe_strerror(error);
std::move(completion_callback_)
.Run(mojom::DeleteRecursivelyError::kPathNotFound);
return;
}
if (S_ISREG(entry_stat.st_mode)) {
bool deleted = DeleteFile(root_path_);
std::move(completion_callback_)
.Run(deleted ? mojom::DeleteRecursivelyError::kOk
: mojom::DeleteRecursivelyError::kFailedToDeleteNode);
return;
}
DeleteRecursively(root_path_,
base::BindOnce(&RecursiveDeleteOperation::Finished,
base::Unretained(this)));
}
void RecursiveDeleteOperation::Finished(bool success) {
LOG_IF(WARNING, success && last_error_ != mojom::DeleteRecursivelyError::kOk)
<< "Operation completed successfully but reported error: " << last_error_;
std::move(completion_callback_).Run(last_error_);
}
void RecursiveDeleteOperation::DeleteRecursively(
const base::FilePath& dir_path,
ContinuationCallback path_removed_callback) {
VLOG(1) << "Recursively deleting directory: " << dir_path;
std::list<Entry> entries;
if (!GetDirectoryListing(dir_path, &entries)) {
last_error_ = mojom::DeleteRecursivelyError::kFailedToListDirectory;
std::move(path_removed_callback).Run(false);
return;
}
// Spread SMB calls (directory listing or directory / file removal) over
// multiple tasks to enable fair access to synchronous libsmbclient APIs.
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&RecursiveDeleteOperation::ProcessDirectoryEntries,
weak_factory_.GetWeakPtr(), std::move(entries),
// Schedule removal of the entry itself once all children are removed.
base::BindOnce(
&RecursiveDeleteOperation::OnProcessDirectoryEntriesDone,
weak_factory_.GetWeakPtr(), std::move(dir_path),
std::move(path_removed_callback)),
true /* previous_entry_succeeded */));
}
void RecursiveDeleteOperation::ProcessDirectoryEntries(
std::list<Entry> entries,
ContinuationCallback processing_complete_callback,
bool previous_entry_succeeded) {
VLOG(1) << "Processing directory entries, " << entries.size()
<< " remaining at this level";
if (!previous_entry_succeeded) {
LOG(WARNING) << "Aborting entry processing as previous entry failed";
std::move(processing_complete_callback).Run(false);
return;
}
// Handle the case where an empty |root_path_| is processed.
if (entries.empty()) {
std::move(processing_complete_callback).Run(true);
return;
}
Entry entry = std::move(entries.front());
entries.pop_front();
if (entries.empty()) {
// This is the last entry, delete the containing directory once done.
ProcessSingleDirectoryEntry(entry, std::move(processing_complete_callback));
return;
}
ProcessSingleDirectoryEntry(
entry,
// Continue to process the remaining siblings (here: entries) of this
// entry once it (and all of its descendants, if it's a directory) have
// been removed.
base::BindOnce(&RecursiveDeleteOperation::ProcessDirectoryEntries,
weak_factory_.GetWeakPtr(), std::move(entries),
std::move(processing_complete_callback)));
}
void RecursiveDeleteOperation::OnProcessDirectoryEntriesDone(
const base::FilePath& dir_path,
ContinuationCallback path_removed_callback,
bool all_descendants_removed) {
bool directory_removed = false;
if (all_descendants_removed) {
// All descendants were successfully removed, remove myself.
VLOG(1) << "Finished processing all descendants of " << dir_path
<< ", deleting myself";
directory_removed = DeleteDirectory(dir_path);
} else {
LOG(WARNING) << "Failed to process all descendants of " << dir_path
<< ", will abort operation";
}
// The callback may be |root_path_removed_callback_| or a follow-up call to
// ProcessDirectoryEntries() to continue processing the list of siblings
// of an entry that was itself a directory.
std::move(path_removed_callback).Run(directory_removed);
}
void RecursiveDeleteOperation::ProcessSingleDirectoryEntry(
const Entry& entry, ContinuationCallback entry_removed_callback) {
if (entry.is_directory) {
DeleteRecursively(entry.path, std::move(entry_removed_callback));
} else {
bool file_removed = DeleteFile(entry.path);
std::move(entry_removed_callback).Run(file_removed);
}
}
bool RecursiveDeleteOperation::DeleteDirectory(const base::FilePath& dir_path) {
std::string share_dir_path = MakeSharePath(dir_path);
int error = samba_impl_->RemoveDirectory(share_dir_path);
if (error) {
VLOG(1) << "RemoveDirectory path: " << share_dir_path
<< " failed: " << base::safe_strerror(error);
last_error_ = mojom::DeleteRecursivelyError::kFailedToDeleteNode;
return false;
}
return true;
}
bool RecursiveDeleteOperation::DeleteFile(const base::FilePath& file_path) {
std::string share_file_path = MakeSharePath(file_path);
int error = samba_impl_->UnlinkFile(share_file_path);
if (error) {
VLOG(1) << "UnlinkFile path: " << share_file_path
<< " failed: " << base::safe_strerror(error);
last_error_ = mojom::DeleteRecursivelyError::kFailedToDeleteNode;
return false;
}
return true;
}
bool RecursiveDeleteOperation::GetDirectoryListing(
const base::FilePath& dir_path, std::list<Entry>* const entries) {
DCHECK(entries);
std::string share_path = MakeSharePath(dir_path);
// Open a directory handle
SMBCFILE* dir;
int error = samba_impl_->OpenDirectory(share_path, &dir);
if (error) {
VLOG(1) << "OpenDirectory path: " << dir_path
<< " failed: " << base::safe_strerror(error);
return false;
}
while (true) {
// Explicitly set |errno| to 0 to detect EOF vs. error cases.
errno = 0;
const struct libsmb_file_info* dirent_info = nullptr;
struct stat inode_stat = {0};
error = samba_impl_->ReadDirectory(dir, &dirent_info, &inode_stat);
if (error) {
VLOG(1) << "ReadDirectory path: " << dir_path
<< " failed: " << base::safe_strerror(error);
CloseDirectory(dir);
return false;
}
if (!dirent_info) {
// EOF.
break;
}
base::StringPiece filename(dirent_info->name);
if (filename == "." || filename == "..") {
// Ignore . and .. since FUSE already takes care of these.
continue;
}
CHECK(!filename.empty());
CHECK_EQ(filename.find("/"), base::StringPiece::npos);
Entry entry;
entry.is_directory = S_ISDIR(inode_stat.st_mode);
entry.path = dir_path.Append(filename);
entries->push_back(std::move(entry));
}
return CloseDirectory(dir);
}
bool RecursiveDeleteOperation::CloseDirectory(SMBCFILE* dir) {
int error = samba_impl_->CloseDirectory(dir);
if (error) {
LOG(WARNING) << "Failed to close directory: " << base::safe_strerror(error);
return false;
}
return true;
}
std::string RecursiveDeleteOperation::MakeSharePath(
const base::FilePath& path) {
DCHECK(path.IsAbsolute());
DCHECK(!path.EndsWithSeparator());
return base_share_path_ + path.value();
}
} // namespace smbfs