-
Notifications
You must be signed in to change notification settings - Fork 662
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
Conversation
9fb684a
to
92464e9
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
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. |
9153913
to
b279dc6
Compare
b279dc6
to
4163ea5
Compare
There was a problem hiding this 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.
6eaae09
to
17f8756
Compare
0a16dfd
to
c2bec0d
Compare
src/sshfs_mount/sftp_server.cpp
Outdated
template <typename T> | ||
T* multipass::SftpServer::get_handle(sftp_client_message msg) | ||
{ | ||
return reinterpret_cast<T*>(sftp_handle(msg->sftp, msg->handle)); |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
- About the data member
current
, it holds the same information as*iter
. I think you did this double bookkeeping because you need to returnDirectoryEntry
reference. I am wondering if it is gotta be better if you return by value and remove all thecurrent
related code. - There are some implicit constructions from
fs::directory_entry
toDirectoryEntry
, maybe it is better to change the constructorDirectoryEntry(const fs::directory_entry& _entry)
to explicit and explicitly convert these.
There was a problem hiding this comment.
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.
src/sshfs_mount/sftp_server.h
Outdated
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; |
There was a problem hiding this comment.
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*)
.
There was a problem hiding this comment.
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.
src/utils/file_ops.cpp
Outdated
const auto named_fd = new NamedFd{path, fd}; | ||
const auto deleter = [](NamedFd* named_fd) { | ||
if (named_fd->second != -1) | ||
::close(named_fd->second); |
There was a problem hiding this comment.
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};
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, really good catch!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/sshfs_mount/sftp_server.cpp
Outdated
{ | ||
mp::platform::symlink_attr_from(entry.absoluteFilePath().toStdString().c_str(), &attr); | ||
std::error_code _; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
tests/test_sftpserver.cpp
Outdated
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(); |
There was a problem hiding this comment.
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.
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. |
714457c
to
64519b6
Compare
@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. |
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! |
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? |
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! |
7735da5
to
2a4a538
Compare
2a4a538
to
a41f57d
Compare
tests/test_file_ops.cpp
Outdated
@@ -19,6 +19,9 @@ | |||
|
|||
#include <multipass/file_ops.h> | |||
|
|||
#include <fcntl.h> | |||
#include <streambuf> |
There was a problem hiding this comment.
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.
a41f57d
to
147abc0
Compare
@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? |
There was a problem hiding this 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!
No description provided.