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

Replace Qt with std in critical parts of sftp_server #3270

Merged
merged 6 commits into from
Dec 5, 2023

Conversation

andrei-toterman
Copy link
Contributor

No description provided.

@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch from 9fb684a to 92464e9 Compare October 24, 2023 12:02
@codecov
Copy link

codecov bot commented Oct 24, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (8bc1e83) 83.91% compared to head (147abc0) 83.90%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3270      +/-   ##
==========================================
- Coverage   83.91%   83.90%   -0.02%     
==========================================
  Files         250      250              
  Lines       13526    13547      +21     
==========================================
+ Hits        11351    11367      +16     
- Misses       2175     2180       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch 3 times, most recently from 9153913 to b279dc6 Compare October 26, 2023 10:39
@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch from b279dc6 to 4163ea5 Compare November 3, 2023 14:03
Copy link
Contributor

@townsend2010 townsend2010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @andrei-toterman!

Thanks for this! Unfortunately, it is not working for me. If I create a mount, shell into the instance, cd to the mounted directory, I get the following:

$ git clone https://github.com/canonical/multipass.git
Cloning into 'multipass'...
error: Unable to create '/home/ubuntu/test/multipass/.git/HEAD.lock': Operation not permitted

Also, I haven't full reviewed, but I did leave one comment below.

src/sshfs_mount/sftp_server.cpp Show resolved Hide resolved
@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch 4 times, most recently from 6eaae09 to 17f8756 Compare November 10, 2023 07:11
@andrei-toterman andrei-toterman linked an issue Nov 10, 2023 that may be closed by this pull request
@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch 5 times, most recently from 0a16dfd to c2bec0d Compare November 16, 2023 11:39
template <typename T>
T* multipass::SftpServer::get_handle(sftp_client_message msg)
{
return reinterpret_cast<T*>(sftp_handle(msg->sftp, msg->handle));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe using static_cast which has some compile time checks instead of reinterpret_cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, makes sense.

std::optional<fs::path> self;
std::optional<fs::path> parent;
fs::directory_iterator iter;
DirectoryEntry current;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice wrapper utility class, besides that, I have a few small comments.

  1. About the data member current, it holds the same information as *iter. I think you did this double bookkeeping because you need to return DirectoryEntry reference. I am wondering if it is gotta be better if you return by value and remove all the current related code.
  2. There are some implicit constructions from fs::directory_entry to DirectoryEntry, maybe it is better to change the constructor DirectoryEntry(const fs::directory_entry& _entry) to explicit and explicitly convert these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, current is necessary for returning a DirectoryEntry reference. But the reason it returns a reference instead of a value is so that it can return a MockDirectoryEntry reference when writing unit tests.
As for the explicit constructors, sure, I will convert them.

SSHSession ssh_session;
SSHFSProcUptr sshfs_process;
SftpSessionUptr sftp_server_session;
const std::string source_path;
const std::string target_path;
std::unordered_map<void*, std::unique_ptr<QFileInfoList>> open_dir_handles;
std::unordered_map<void*, std::unique_ptr<QFile>> open_file_handles;
std::unordered_map<void*, std::unique_ptr<NamedFd, void (*)(NamedFd*)>> open_file_handles;
Copy link
Contributor

@georgeliao georgeliao Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change from void (*)(NamedFd*) to std::function<void(NamedFd*)>, so we can have a more readable functor signature. The same goes to other occurrences of void (*)(NamedFd*).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. At first I avoided std::function because I thought it always involves a dynamic allocation, but after reading the docs better, I understand that in the case of free function pointers, small object optimization is guaranteed and no dynamic allocation takes place. So I'll change it.

const auto named_fd = new NamedFd{path, fd};
const auto deleter = [](NamedFd* named_fd) {
if (named_fd->second != -1)
::close(named_fd->second);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is the responsibility of the custom deleter to delete the memory explicitly. So a line delete named_fd; will have to be added after the if branch.

        if (named_fd->second != -1)
            ::close(named_fd->second);
        delete named_fd;

Besides that, maybe move the new NamedFd{path, fd}; into the return statement that makes return {new NamedFd{path, fd}, deleter}; and remove the line const auto named_fd = new NamedFd{path, fd};.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, really good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that I made this mistake regarding the destructor, and the std::fucntion over simple function pointer comment, do you think that it would be worth it to change NamedFd from a simple std::pair to a proper struct that has its own destructor? That way, we can use a simple std::unique_ptr<NamedFd> and not worry about providing a correct deleter. Initially I used std::pair because I wanted to keep things simple, but maybe they are not as simple as I thought.

Copy link
Contributor

@georgeliao georgeliao Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that is an even better idea. Having a proper struct with the proper RAII and avoiding all the custom deleter hassle.

QFile file(filename);
if (!MP_FILEOPS.setPermissions(file, to_qt_permissions(msg->attr->permissions)))
std::error_code err;
MP_FILEOPS.permissions(filename, static_cast<fs::perms>(msg->attr->permissions), err);
Copy link
Contributor

@georgeliao georgeliao Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe static_cast<fs::perms>(msg->attr->permissions) -> fs::perms(msg->attr->permissions)? both interpret uint32_t into bitmask, but the second one is cleaner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey guys, sorry I noticed this flying by. Why is a functional cast here preferable to a static_cast?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My take would be that they both rely on the underneath type conversion, but the direct cast is cleaner.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps I am missing something in this case, but to me it looks like this would apply: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-casts-named

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess {} is OK too if no narrowing is involved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think {} is the cleanest. I understand that a C-style cast is not really recommended and, if this was a conversion between more complex data types i.e. classes, I would also definitely prefer named casts. In this case, since it's just an integer to enum conversion, I initially agreed to the C-style cast since it would simply act like the static_cast that it replaced. But now that you mention {}, I think that is preferable. Thanks for pointing it out!

Copy link
Contributor

@georgeliao georgeliao Nov 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess {} is OK too if no narrowing is involved.

That is what the item of the guideline said. On the top of that, () and {} functional cast are also slightly different in terms of narrowing prevention.

Yes, I think {} is the cleanest. I understand that a C-style cast is not really recommended and,

I think you mean functional cast, C-style is the (T) var based on the guideline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, unfortunately using {} doesn't compile on windows, so I'll just go back to the static_cast for correctness.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahaa! And thus we just prevented a narrowing conversion.

{
mp::platform::symlink_attr_from(entry.absoluteFilePath().toStdString().c_str(), &attr);
std::error_code _;
Copy link
Contributor

@georgeliao georgeliao Nov 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This _ variable is not used. I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. Must've forgotten it there from a previous attempt.

msg->filename = dir_name.data();

int perm_denied_num_calls{0};
auto reply_status = make_reply_status(msg.get(), SSH_FX_PERMISSION_DENIED, perm_denied_num_calls);
auto [file_ops, mock_file_ops_guard] = mpt::MockFileOps::inject();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pair can be const, maybe do a auto [file_ops, mock_file_ops_guard] = mpt::MockFileOps::inject(); -> const auto [file_ops, mock_file_ops_guard] = mpt::MockFileOps::inject(); string replacement in the whole file.

@georgeliao
Copy link
Contributor

Hi @andrei-toterman , excellent work on the refactoring and some utility function addition. Only a few comments need to be processed and most of them are small ones.

@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch 2 times, most recently from 714457c to 64519b6 Compare November 30, 2023 13:43
@georgeliao
Copy link
Contributor

@andrei-toterman , I have done the functional testing on various platforms and backends. The testing includes mounting the folder via classic mount and using the folder for creating/deleting files, git clone repo and delete the cloned folder, etc. They all work fine. @townsend2010 , I could not reproduce your permission denied error. Maybe that was based on the old version of the code.

@townsend2010
Copy link
Contributor

Hi @georgeliao! Yes, that issue I ran into was with an older version of this branch and I have verified that it is now fixed as well. Thanks!

@townsend2010
Copy link
Contributor

Hey @andrei-toterman,

Just a question about test coverage...this branch is not very well covered. Is this due to a difficulty in testing the actual changes or is it an oversight?

@andrei-toterman
Copy link
Contributor Author

Hey, @townsend2010! Most of the uncovered code comes from wrapper functions for file IO and filesystem information. I could write some tests for those, but they would probably look like "make sure that the wrapper function does the same thing as the wrapped function". But I agree that it's not nice to decrease the coverage, so I will add some simple tests for those. As for the uncovered lines from the sftp server code, that is indeed an oversight on my part, and I'll fix it!

@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch 2 times, most recently from 7735da5 to 2a4a538 Compare December 1, 2023 14:30
@@ -19,6 +19,9 @@

#include <multipass/file_ops.h>

#include <fcntl.h>
#include <streambuf>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think we need the header streambuf.

@andrei-toterman andrei-toterman force-pushed the sftp-server-std-over-qt branch from a41f57d to 147abc0 Compare December 4, 2023 10:44
@georgeliao
Copy link
Contributor

@andrei-toterman I have done another round of functional testing. It includes qemu and lxd on linux; Hyper-v and VirtualBox on virtualized windows; qemu on macOs. They all work fine. I think now it is ready to merge. @townsend2010 can you also remove the change request?

Copy link
Contributor

@townsend2010 townsend2010 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM now too, thanks!

@townsend2010 townsend2010 merged commit 102cd1d into main Dec 5, 2023
13 of 14 checks passed
@townsend2010 townsend2010 deleted the sftp-server-std-over-qt branch December 5, 2023 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants