| // Copyright 2019 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SMBFS_SMB_FILESYSTEM_H_ |
| #define SMBFS_SMB_FILESYSTEM_H_ |
| |
| #include <libsmbclient.h> |
| #include <sys/types.h> |
| |
| #include <atomic> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include <base/callback.h> |
| #include <base/containers/lru_cache.h> |
| #include <base/files/file_path.h> |
| #include <base/memory/weak_ptr.h> |
| #include <base/synchronization/lock.h> |
| #include <base/threading/thread.h> |
| #include <base/time/time.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <gtest/gtest_prod.h> |
| |
| #include "smbfs/filesystem.h" |
| #include "smbfs/inode_map.h" |
| #include "smbfs/recursive_delete_operation.h" |
| #include "smbfs/smb_credential.h" |
| |
| namespace smbfs { |
| |
| class SambaInterface; |
| |
| class SmbFilesystem : public Filesystem { |
| public: |
| // Delegate functions will always be called on the SmbFilesystem's constructor |
| // thread. Any callback parameters must be invoked on the caller thread (read: |
| // constructor thread). |
| class Delegate { |
| public: |
| using RequestCredentialsCallback = |
| base::OnceCallback<void(std::unique_ptr<SmbCredential> credentials)>; |
| |
| // Request username/password auth credentials for the share. Invoke |
| // |callback| with the requested credentials, or with nullptr if no |
| // credentials are provided (eg. if the user closes the request dialog). |
| virtual void RequestCredentials(RequestCredentialsCallback callback) = 0; |
| }; |
| |
| struct Options { |
| Options(); |
| ~Options(); |
| |
| // Allow moves. |
| Options(Options&&); |
| Options& operator=(Options&&); |
| |
| std::string share_path; |
| uid_t uid = 0; |
| gid_t gid = 0; |
| std::unique_ptr<SmbCredential> credentials; |
| bool allow_ntlm = false; |
| bool use_kerberos = false; |
| }; |
| |
| enum class ConnectError { |
| kOk = 0, |
| kNotFound, |
| kAccessDenied, |
| kSmb1Unsupported, |
| kUnknownError, |
| }; |
| |
| SmbFilesystem(Delegate* delegate, Options options); |
| |
| SmbFilesystem() = delete; |
| SmbFilesystem(const SmbFilesystem&) = delete; |
| SmbFilesystem& operator=(const SmbFilesystem&) = delete; |
| |
| ~SmbFilesystem() override; |
| |
| base::WeakPtr<SmbFilesystem> GetWeakPtr(); |
| |
| // Ensures that the SMB share can be connected to. Must NOT be called after |
| // the filesystem is attached to a FUSE session. |
| // Virtual for testing. |
| virtual ConnectError EnsureConnected(); |
| |
| // Sets the resolved IP address of the share host. |ip_address| is an IPv4 |
| // address in network byte order, or empty. If |ip_address| is empty, any |
| // existing resolved address will be reset. |
| // Virtual for testing. |
| virtual void SetResolvedAddress(const std::vector<uint8_t>& ip_address); |
| |
| const std::string& resolved_share_path() const { |
| return resolved_share_path_; |
| } |
| |
| // Filesystem overrides. |
| void StatFs(std::unique_ptr<StatFsRequest> request, |
| fuse_ino_t inode) override; |
| void Lookup(std::unique_ptr<EntryRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name) override; |
| void Forget(fuse_ino_t inode, uint64_t count) override; |
| void GetAttr(std::unique_ptr<AttrRequest> request, fuse_ino_t inode) override; |
| void SetAttr(std::unique_ptr<AttrRequest> request, |
| fuse_ino_t inode, |
| std::optional<uint64_t> file_handle, |
| const struct stat& attr, |
| int to_set) override; |
| void Open(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t inode, |
| int flags) override; |
| void Create(std::unique_ptr<CreateRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name, |
| mode_t mode, |
| int flags) override; |
| void Read(std::unique_ptr<BufRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| size_t size, |
| off_t offset) override; |
| void Write(std::unique_ptr<WriteRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| const char* buf, |
| size_t size, |
| off_t offset) override; |
| void Release(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle) override; |
| void Rename(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t old_parent_inode, |
| const std::string& old_name, |
| fuse_ino_t new_parent_inode, |
| const std::string& new_name) override; |
| void Unlink(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name) override; |
| void OpenDir(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t inode, |
| int flags) override; |
| void ReadDir(std::unique_ptr<DirentryRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| off_t offset) override; |
| void ReleaseDir(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle) override; |
| void MkDir(std::unique_ptr<EntryRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name, |
| mode_t mode) override; |
| void RmDir(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name) override; |
| |
| // mojom::SmbFs helpers. |
| // |
| // Recursively deletes |path| and all of its contents if it is a directory. |
| // |path| is an absolute path from the root of the share (ie. it does not |
| // include the smb://host portion). |callback| is called with the outcome of |
| // the operation. |
| void DeleteRecursively(const base::FilePath& path, |
| RecursiveDeleteOperation::CompletionCallback callback); |
| |
| protected: |
| // Protected constructor for unit tests. |
| SmbFilesystem(Delegate* delegate, const std::string& share_path); |
| |
| // Allow mock interface to be provided during tests. |
| void SetSambaInterface(std::unique_ptr<SambaInterface> samba_interface); |
| |
| private: |
| FRIEND_TEST(SmbFilesystemTest, MakeStatModeBits); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_NoRequest); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_RequestOnEPERM); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_RequestOnEACCES); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_NoDelegate); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_OnlyOneRequest); |
| FRIEND_TEST(SmbFilesystemTest, MaybeUpdateCredentials_IgnoreEmptyResponse); |
| |
| // Cache stat information when listing directories to reduce unnecessary |
| // network requests. |
| struct StatCacheItem { |
| struct stat inode_stat; |
| base::Time expires_at; |
| }; |
| |
| // Filesystem implementations that execute on |samba_thread_|. |
| void StatFsInternal(std::unique_ptr<StatFsRequest> request, fuse_ino_t inode); |
| void LookupInternal(std::unique_ptr<EntryRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name); |
| void ForgetInternal(fuse_ino_t inode, uint64_t count); |
| void GetAttrInternal(std::unique_ptr<AttrRequest> request, fuse_ino_t inode); |
| void SetAttrInternal(std::unique_ptr<AttrRequest> request, |
| fuse_ino_t inode, |
| std::optional<uint64_t> file_handle, |
| const struct stat& attr, |
| int to_set); |
| int SetFileSizeInternal(const std::string& share_file_path, |
| std::optional<uint64_t> file_handle, |
| off_t size, |
| const struct stat& current_stat, |
| struct stat* reply_stat); |
| int SetUtimesInternal(const std::string& share_file_path, |
| int to_set, |
| const struct timespec& atime, |
| const struct timespec& mtime, |
| const struct stat& current_stat, |
| struct stat* reply_stat); |
| void OpenInternal(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t inode, |
| int flags); |
| void CreateInternal(std::unique_ptr<CreateRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name, |
| mode_t mode, |
| int flags); |
| void ReadInternal(std::unique_ptr<BufRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| size_t size, |
| off_t offset); |
| void WriteInternal(std::unique_ptr<WriteRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| const std::vector<char>& buf, |
| off_t offset); |
| void ReleaseInternal(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle); |
| void RenameInternal(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t old_parent_inode, |
| const std::string& old_name, |
| fuse_ino_t new_parent_inode, |
| const std::string& new_name); |
| void UnlinkInternal(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name); |
| void OpenDirInternal(std::unique_ptr<OpenRequest> request, |
| fuse_ino_t inode, |
| int flags); |
| void ReadDirInternal(std::unique_ptr<DirentryRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle, |
| off_t offset); |
| void ReleaseDirInternal(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t inode, |
| uint64_t file_handle); |
| void MkDirInternal(std::unique_ptr<EntryRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name, |
| mode_t mode); |
| void RmDirinternal(std::unique_ptr<SimpleRequest> request, |
| fuse_ino_t parent_inode, |
| const std::string& name); |
| |
| // mojom::SmbFs helpers that execute on |samba_thread_|. |
| void DeleteRecursivelyInternal( |
| const base::FilePath& path, |
| RecursiveDeleteOperation::CompletionCallback callback); |
| |
| // Called on completion of the recursive delete. |
| void OnDeleteRecursivelyDone( |
| RecursiveDeleteOperation::CompletionCallback callback, |
| mojom::DeleteRecursivelyError error); |
| |
| // Constructs a sanitised stat struct for sending as a response. |
| struct stat MakeStat(ino_t inode, const struct stat& in_stat) const; |
| |
| // Clear / propagate permission bits appropriately (crbug.com/1063715). |
| mode_t MakeStatModeBits(mode_t in_mode) const; |
| |
| // Constructs a share file path suitable for passing to libsmbclient from the |
| // given absolute file path. |
| std::string MakeShareFilePath(const base::FilePath& path) const; |
| |
| // Construct a share file path from the |inode|. |inode| must be a valid inode |
| // number. |
| std::string ShareFilePathFromInode(ino_t inode) const; |
| |
| // Registers an open file and returns a handle to that file. Always returns a |
| // non-zero handle. |
| uint64_t AddOpenFile(SMBCFILE* file); |
| |
| // Removes |handle| from the open file table. |
| void RemoveOpenFile(uint64_t handle); |
| |
| // Returns the open file referred to by |handle|. Returns nullptr if |handle| |
| // does not exist. |
| SMBCFILE* LookupOpenFile(uint64_t handle) const; |
| |
| // Request credentials, if |error| is an auth failure, and the share has not |
| // previously connected successfully. |
| void MaybeUpdateCredentials(int error); |
| |
| // Request authentication credentials. Will do nothing is a request is |
| // currently in progress. |
| void RequestCredentialUpdate(); |
| |
| // Callback handler for Delegate::RequestCredentials(). |
| void OnRequestCredentialsDone(std::unique_ptr<SmbCredential> credentials); |
| |
| // Cache a stat structure. |inode_stat.st_ino| is used as the key. |
| void AddCachedInodeStat(const struct stat& inode_stat); |
| |
| // Remove the cached stat structure for |inode|. |
| void EraseCachedInodeStat(ino_t inode); |
| |
| // Lookup the cached stat structure for |inode|. Returns true on cache hit or |
| // false on a miss. |
| bool GetCachedInodeStat(ino_t inode, struct stat* out_stat); |
| |
| Delegate* const delegate_ = nullptr; |
| const std::string share_path_; |
| const uid_t uid_ = 0; |
| const gid_t gid_ = 0; |
| const bool use_kerberos_ = false; |
| base::Thread samba_thread_; |
| InodeMap inode_map_{FUSE_ROOT_ID}; |
| |
| // Origin/constructor thread task runner. |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_ = |
| base::ThreadTaskRunnerHandle::Get(); |
| |
| std::unordered_map<uint64_t, SMBCFILE*> open_files_; |
| uint64_t open_files_seq_ = 1; |
| |
| mutable base::Lock lock_; |
| std::string resolved_share_path_ = share_path_; |
| |
| // Interface to libsmbclient. |
| std::unique_ptr<SambaInterface> samba_impl_; |
| |
| // Cache stat information during ReadDir() to speed up subsequent access. |
| base::HashingLRUCache<ino_t, StatCacheItem> stat_cache_; |
| |
| // Whether a successful connection to the SMB server has been made. Used to |
| // determine whether or not to request auth credentials. |
| // std::atomic<> load/store by default have acquire/release memory ordering. |
| std::atomic<bool> connected_{false}; |
| // Flag to ensure only one credential request is active at a time. |
| bool requesting_credentials_ = false; |
| |
| // At most one outstanding recursive delete operation can be in flight. This |
| // object must live entirely on |samba_thread_|. |
| std::unique_ptr<RecursiveDeleteOperation> recursive_delete_operation_; |
| |
| base::WeakPtrFactory<SmbFilesystem> weak_factory_{this}; |
| }; |
| |
| std::ostream& operator<<(std::ostream& out, SmbFilesystem::ConnectError error); |
| |
| } // namespace smbfs |
| |
| #endif // SMBFS_SMB_FILESYSTEM_H_ |