Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SFTP write support - backport for Matrix #85

Open
wants to merge 1 commit into
base: Matrix
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 132 additions & 19 deletions src/SFTPFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
* See LICENSE.md for more information.
*/


#include <map>
#include <sstream>
#include <fcntl.h>
#include "SFTPSession.h"

// This works around a Windows build bug whereby those functions are defined as macro
#if defined(CreateDirectory)
#undef CreateDirectory
#endif
#if defined(RemoveDirectory)
#undef RemoveDirectory
#endif

#include <kodi/General.h>
#include <kodi/addon-instance/VFS.h>
#include <map>
#include <sstream>

class ATTRIBUTE_HIDDEN CSFTPFile : public kodi::addon::CInstanceVFS
{
Expand All @@ -26,22 +36,7 @@ class ATTRIBUTE_HIDDEN CSFTPFile : public kodi::addon::CInstanceVFS

kodi::addon::VFSFileHandle Open(const kodi::addon::VFSUrl& url) override
{
SFTPContext* result = new SFTPContext;

result->session = CSFTPSessionManager::Get().CreateSession(url);

if (result->session)
{
result->file = url.GetFilename().c_str();
result->sftp_handle = result->session->CreateFileHande(result->file);
if (result->sftp_handle)
return result;
}
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to allocate session");

delete result;
return nullptr;
return OpenInternal(url, O_RDONLY);
}

ssize_t Read(kodi::addon::VFSFileHandle context, uint8_t* buffer, size_t uiBufSize) override
Expand All @@ -54,14 +49,32 @@ class ATTRIBUTE_HIDDEN CSFTPFile : public kodi::addon::CInstanceVFS
if (rc >= 0)
return rc;
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to read %i", rc);
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to read %s", ctx->file.c_str());
}
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Can't read without a handle");

return -1;
}

ssize_t Write(kodi::addon::VFSFileHandle context, const uint8_t* buffer, size_t uiBufSize) override
{
SFTPContext* ctx = static_cast<SFTPContext*>(context);
if (ctx && ctx->session && ctx->sftp_handle)
{
int writeBytes = ctx->session->Write(ctx->sftp_handle, buffer, uiBufSize);

if (writeBytes >= 0)
return writeBytes;
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to write %s", ctx->file.c_str());
}
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Can't write without a handle");

return -1;
}

int64_t Seek(kodi::addon::VFSFileHandle context, int64_t iFilePosition, int whence) override
{
SFTPContext* ctx = static_cast<SFTPContext*>(context);
Expand Down Expand Up @@ -173,6 +186,106 @@ class ATTRIBUTE_HIDDEN CSFTPFile : public kodi::addon::CInstanceVFS

return session->GetDirectory(str.str(), url.GetFilename(), items);
}

bool Delete(const kodi::addon::VFSUrl& url) override
{
CSFTPSessionPtr session = CSFTPSessionManager::Get().CreateSession(url);
if (session)
return session->DeleteFile(url.GetFilename());
else
{
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to create session to delete file '%s'",
url.GetFilename().c_str());
return false;
}
}

bool RemoveDirectory(const kodi::addon::VFSUrl& url) override
{
CSFTPSessionPtr session = CSFTPSessionManager::Get().CreateSession(url);
if (session)
return session->DeleteDirectory(url.GetFilename());
else
{
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to create session to delete folder '%s'",
url.GetFilename().c_str());
return false;
}
}

bool CreateDirectory(const kodi::addon::VFSUrl& url) override
{
CSFTPSessionPtr session = CSFTPSessionManager::Get().CreateSession(url);
if (session)
return session->MakeDirectory(url.GetFilename());
else
{
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to create session to create folder '%s'",
url.GetFilename().c_str());
return false;
}
}

bool Rename(const kodi::addon::VFSUrl& url_from, const kodi::addon::VFSUrl& url_to) override
{
CSFTPSessionPtr session = CSFTPSessionManager::Get().CreateSession(url_from);
if (session)
return session->RenameFile(url_from.GetFilename(), url_to.GetFilename());
else
{
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to create session to rename file '%s'",
url_from.GetFilename().c_str());
return false;
}
}

bool ContainsFiles(const kodi::addon::VFSUrl& url,
std::vector<kodi::vfs::CDirEntry>& items,
std::string &rootPath) override
{
return DirectoryExists(url) && !items.empty();
}

kodi::addon::VFSFileHandle OpenForWrite(const kodi::addon::VFSUrl& url, bool overWrite) override
{
if (overWrite)
return OpenInternal(url, O_RDWR | O_CREAT | O_TRUNC);
else
return OpenInternal(url, O_RDWR | O_CREAT);
}

int Truncate(kodi::addon::VFSFileHandle context, int64_t size) override
{
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Truncate is not implemented");
return -1;
}

bool IoControlGetCacheStatus (kodi::addon::VFSFileHandle context, kodi::vfs::CacheStatus &status) override { return false; }

bool IoControlSetCacheRate (kodi::addon::VFSFileHandle context, unsigned int rate) override { return false; }

bool IoControlSetRetry (kodi::addon::VFSFileHandle context, bool retry) override { return false; }

private:
kodi::addon::VFSFileHandle OpenInternal(const kodi::addon::VFSUrl& url, mode_t mode)
{
SFTPContext* result = new SFTPContext;

result->session = CSFTPSessionManager::Get().CreateSession(url);

if (result->session)
{
result->file = url.GetFilename().c_str();
result->sftp_handle = result->session->CreateFileHande(result->file, mode);
if (result->sftp_handle)
return result;
}
else
kodi::Log(ADDON_LOG_ERROR, "SFTPFile: Failed to allocate session");

delete result;
return nullptr;
}
};

class ATTRIBUTE_HIDDEN CMyAddon : public kodi::addon::CAddonBase
Expand Down
53 changes: 51 additions & 2 deletions src/SFTPSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
#ifndef S_ISLNK
#define S_ISLNK(m) ((((m)) & 0170000) == (0120000))
#endif
#ifndef S_IWUSR
#define S_IWUSR 00200
#endif
#ifndef S_IRUSR
#define S_IRUSR 00400
#endif
#ifndef S_IRWXU
#define S_IRWXU 00700
#endif


static std::string CorrectPath(const std::string& path)
Expand Down Expand Up @@ -90,13 +99,13 @@ CSFTPSession::~CSFTPSession()
Disconnect();
}

sftp_file CSFTPSession::CreateFileHande(const std::string& file)
sftp_file CSFTPSession::CreateFileHande(const std::string& file, mode_t mode)
{
if (m_connected)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
sftp_file handle = sftp_open(m_sftp_session, CorrectPath(file).c_str(), O_RDONLY, 0);
sftp_file handle = sftp_open(m_sftp_session, CorrectPath(file).c_str(), mode, S_IRUSR | S_IWUSR);
if (handle)
{
sftp_file_set_blocking(handle);
Expand Down Expand Up @@ -291,6 +300,14 @@ int CSFTPSession::Read(sftp_file handle, void* buffer, size_t length)
return result;
}

int CSFTPSession::Write(sftp_file handle, const void* buffer, size_t length)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
int result = sftp_write(handle, buffer, length);
return result;
}

int64_t CSFTPSession::GetPosition(sftp_file handle)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
Expand All @@ -307,6 +324,38 @@ bool CSFTPSession::IsIdle()
90000;
}

bool CSFTPSession::DeleteFile(const std::string& path)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
int result = sftp_unlink(m_sftp_session, CorrectPath(path).c_str());
return result == 0 ? true : false;
}

bool CSFTPSession::DeleteDirectory(const std::string& path)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
int result = sftp_rmdir(m_sftp_session, CorrectPath(path).c_str());
return result == 0 ? true : false;
}

bool CSFTPSession::MakeDirectory(const std::string& path)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
int result = sftp_mkdir(m_sftp_session, CorrectPath(path).c_str(), S_IRWXU);
return result == 0 ? true : false;
}

bool CSFTPSession::RenameFile(const std::string& path_from, const std::string& path_to)
{
std::unique_lock<std::recursive_mutex> lock(m_lock);
m_LastActive = std::chrono::high_resolution_clock::now();
int result = sftp_rename(m_sftp_session, CorrectPath(path_from).c_str(), CorrectPath(path_to).c_str());
return result == 0 ? true : false;
}

bool CSFTPSession::VerifyKnownHost(ssh_session session)
{
#if !(LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR < 8)
Expand Down
7 changes: 6 additions & 1 deletion src/SFTPSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CSFTPSession
CSFTPSession(const kodi::addon::VFSUrl& url);
virtual ~CSFTPSession();

sftp_file CreateFileHande(const std::string& file);
sftp_file CreateFileHande(const std::string& file, mode_t mode);
void CloseFileHandle(sftp_file handle);
bool GetDirectory(const std::string& base,
const std::string& folder,
Expand All @@ -33,8 +33,13 @@ class CSFTPSession
int Stat(const std::string& path, kodi::vfs::FileStatus& buffer);
int Seek(sftp_file handle, uint64_t position);
int Read(sftp_file handle, void* buffer, size_t length);
int Write(sftp_file handle, const void* buffer, size_t length);
int64_t GetPosition(sftp_file handle);
bool IsIdle();
bool DeleteFile(const std::string& path);
bool DeleteDirectory(const std::string& path);
bool MakeDirectory(const std::string& path);
bool RenameFile(const std::string& path_from, const std::string& path_to);

private:
bool VerifyKnownHost(ssh_session session);
Expand Down
1 change: 1 addition & 0 deletions vfs.sftp/addon.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
supportPassword="true"
supportPort="true"
supportBrowsing="false"
supportWrite="true"
defaultPort="22"
type="sftp"
label="20260"
Expand Down