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

Fix symlink resolution when mounting #3673

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Conversation

Sploder12
Copy link
Contributor

@Sploder12 Sploder12 commented Sep 12, 2024

fixes #1722

Symbolic links are now resolved when performing a mount.
Previously they were not resolved.

MULTI-1549

Copy link

codecov bot commented Sep 12, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.87%. Comparing base (21430a3) to head (d8bb07d).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3673   +/-   ##
=======================================
  Coverage   88.87%   88.87%           
=======================================
  Files         256      256           
  Lines       14579    14581    +2     
=======================================
+ Hits        12957    12959    +2     
  Misses       1622     1622           

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

Copy link
Collaborator

@ricab ricab left a comment

Choose a reason for hiding this comment

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

Thank you Trevor! Superficial review from my side, but it looks like it's headed in the right direction. I have a couple of comments inline.

? std::make_unique<SSHFSMountHandler>(vm, config->ssh_key_provider.get(), target, mount)
: vm->make_native_mount_handler(target, mount);
? std::make_unique<SSHFSMountHandler>(vm, config->ssh_key_provider.get(), target, std::move(mount))
: vm->make_native_mount_handler(target, std::move(mount));
Copy link
Collaborator

Choose a reason for hiding this comment

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

This one doesn't help much unless you change make_native_mount_handler to take by value and move too. Otherwise you're asking for one extra temporary.

src/utils/vm_mount.cpp Outdated Show resolved Hide resolved
Copy link
Collaborator

@ricab ricab left a comment

Choose a reason for hiding this comment

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

Hey Trevor, this works well for what it does and the code looks good. However, currently it covers only paths ending in a symlink. I guess we should cover symlinks anywhere in the path, don't you think?

ricab@ricab-XPS-9320:~/tmp/asdf $ ln -s .. l4
ricab@ricab-XPS-9320:~/tmp/asdf $ cd
ricab@ricab-XPS-9320:~ $ mp mount tmp/asdf/l4/subl-mdpreview/ amazed-topi:l4
ricab@ricab-XPS-9320:~ $ mp info amazed-topi
Name:           amazed-topi
State:          Running
Snapshots:      2
IPv4:           10.251.95.73
Release:        Ubuntu 24.04.1 LTS
Image hash:     78547d336e4c (Ubuntu 24.04 LTS)
CPU(s):         2
Load:           0.00 0.00 0.00
Disk usage:     2.0GiB out of 4.8GiB
Memory usage:   342.3MiB out of 955.7MiB
Mounts:         /home/ricab/tmp/asdf/l4/subl-mdpreview => l4
                    UID map: 1000:default
                    GID map: 1000:default

I expected the source to become just /home/ricab/tmp/subl-mdpreview


if (err)
throw std::runtime_error(
fmt::format("Mount symlink source path \"{}\" could not be made weakly canonical: {}.",
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would end up being shown to the user, right? If so, I would try to make it more user friendly. Perhaps:

Suggested change
fmt::format("Mount symlink source path \"{}\" could not be made weakly canonical: {}.",
fmt::format("Could not resolve symlinks in mount source path \"{}\": {}.",

src/utils/vm_mount.cpp Outdated Show resolved Hide resolved
@ricab
Copy link
Collaborator

ricab commented Oct 14, 2024

Hey Trevor, a quick experiment I did:

#include <filesystem>
#include <iostream>

using namespace std;
namespace fs = std::filesystem;

int main()
{
  // l4 -> ..
  // l2 -> ../subl-mdpreview
  auto src_path = "/home/ricab/tmp/asdf/l4/asdf/l4/asdf/l2/";

  error_code ec{};
  auto dst_path = fs::weakly_canonical(src_path, ec);

  if (!ec)
    cout << "\"" << src_path << "\" == " << dst_path << endl;

  // prints "/home/ricab/tmp/asdf/l4/asdf/l4/asdf/l2/" == "/home/ricab/tmp/subl-mdpreview"

  return bool(ec);
}

@Sploder12 Sploder12 force-pushed the fix-symlink-mounting branch 2 times, most recently from 9b71479 to ccac3af Compare October 18, 2024 19:30
@Sploder12
Copy link
Contributor Author

I wonder if we should check elsewhere in the repo for usage of \"{}\" since a string like Hello\\ could cause issues (becomes "Hello\"). WDYT?

@sharder996 sharder996 added this to the 1.15.0 milestone Oct 30, 2024
ricab
ricab previously approved these changes Nov 15, 2024
Copy link
Collaborator

@ricab ricab left a comment

Choose a reason for hiding this comment

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

Thank you @Sploder12! I would like for someone to do a primary review on this, but I am approving on secondary.

src/utils/file_ops.cpp Outdated Show resolved Hide resolved
@ricab
Copy link
Collaborator

ricab commented Nov 15, 2024

I wonder if we should check elsewhere in the repo for usage of "{}" since a string like Hello\ could cause issues (becomes "Hello"). WDYT?

We ended up considering this moot, correct @Sploder12 ?

@Sploder12
Copy link
Contributor Author

We ended up considering this moot, correct @Sploder12 ?

There was one issue that came from it #3759, but no real impact.

Copy link
Contributor

@georgeliao georgeliao left a comment

Choose a reason for hiding this comment

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

@Sploder12 , thanks for the good work. It looks very good in general. I just have a few minor comments and questions. The functional testing went also fine.

tests/test_daemon_mount.cpp Outdated Show resolved Hide resolved

TEST_F(TestDaemonMount, symlinkSourceGetsResolved)
{
const auto [temp_dir, filename] = plant_instance_json(fake_json_contents(mac_addr, extra_interfaces));
Copy link
Contributor

Choose a reason for hiding this comment

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

filename field can be set to _, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

_ is used later on line 277 so it'd cause a redefinition error. I have this one named over the other since the filename might be useful, but the mock guard shouldn't be touched.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In such case, I sometimes name it along the lines of ignored_filename.

mp::Daemon daemon{config_builder.build()};

mp::MountRequest request;
request.set_source_path(mount_dir.path().toStdString());
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I fully get the purpose of this unit test, mount_dir.path() is a normal temp directory without dot, dot-dot, or symbolic, right? So we are not really testing resolving as far as I can tell.

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 point, this test isn't clear in what it's doing. The test does not test that resolution works, but rather that weakly_canonical is called when making a mount request. But it also doesn't verify that the value from weakly_canonical is actually used so it is a terrible test. Good catch!

" \"source_path\": \"{}\",\n"
" \"target_path\": \"{}\",\n",
" \"source_path\": {:?},\n"
" \"target_path\": {:?},\n",
Copy link
Contributor

Choose a reason for hiding this comment

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

Two questions, here.

  1. What is the point of using debug specifier here if we are just injecting two std::strings and there is no debug or normal display implementation?
  2. I am not sure {:?} is supported in c++ fmt::format as well as it is in rust, do you know any official documentation where it introduces this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Without the debug specifier the JSON on Windows will contain "C:\some\path\", since the \ is not escaped when we parse the JSON the " gets escaped and things break.
  2. Yep, their syntax documentation describes the purpose of '?', which is "Debug format. The string is quoted and special characters escaped." So we get "C:\\some\\path\\" instead which gets parsed correctly.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, so the difference between {:?} and {} on a std::string is just the quotes and escapes. {:?} always gives the raw and quoted string, which is definitely more robust.

@@ -521,7 +521,7 @@ TEST_F(TestBaseSnapshot, adoptsMountsFromJson)
const auto [snapshot_mnt_dst, snapshot_mount] = *snapshot_mounts.begin();

EXPECT_EQ(snapshot_mnt_dst, dst_path);
EXPECT_EQ(snapshot_mount.get_source_path(), src_path);
EXPECT_EQ(snapshot_mount.get_source_path(), mp::fs::weakly_canonical(src_path));
Copy link
Contributor

Choose a reason for hiding this comment

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

src_path does not seem to have anything to resolve, is this for future proof meaning that if src_path becomes resolvable it can still match the snapshot_mount.get_source_path()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't remember, but my assumption is that my past self did so to fix a test failing on Windows. But maybe not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Huh, turns out it was for the test failing on MacOS.

src/utils/vm_mount.cpp Outdated Show resolved Hide resolved
src/utils/vm_mount.cpp Outdated Show resolved Hide resolved
ricab
ricab previously approved these changes Nov 22, 2024
Copy link
Collaborator

@ricab ricab left a comment

Choose a reason for hiding this comment

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

Approving on secondary again. Please don't wait for me to merge once the primary review is settled.

EXPECT_CALL(*mock_factory, create_virtual_machine).WillOnce(Return(std::move(mock_vm)));

// setup to make the daemon happy
MP_DELEGATE_MOCK_CALLS_ON_BASE(*mock_file_ops, mkpath, FileOps);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice macro usage, better than the boilerplate code before.

@@ -267,3 +268,51 @@ TEST_F(TestDaemonMount, performanceMountsNotImplementedHasErrorFails)
EXPECT_EQ(status.error_code(), grpc::StatusCode::FAILED_PRECONDITION);
EXPECT_THAT(status.error_message(), StrEq("The native mounts feature is not implemented on this backend."));
}

TEST_F(TestDaemonMount, mount_uses_resolved_source)
{
Copy link
Contributor

@georgeliao georgeliao Nov 26, 2024

Choose a reason for hiding this comment

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

The new unit test name is definitely more expressive.

georgeliao
georgeliao previously approved these changes Nov 26, 2024
Copy link
Contributor

@georgeliao georgeliao left a comment

Choose a reason for hiding this comment

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

It looks good to me now, thanks for addressing all the comments. Maybe do a rebase and we can get ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mount does not follow symlinks
4 participants