Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/windows-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: 6.2.4
version: 6.10.0
setup-python: false
aqtversion: '==3.1.*'
aqtversion: '==3.3.*'
py7zrversion: '==0.20.*'
timeout-minutes: 20

Expand Down
6 changes: 3 additions & 3 deletions BUILD.macOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ Avoid the version of cmake supplied, we need a newer one (see later).

#### Option 1: Using Qt official sources

Install the latest stable version of Qt6 LTS (6.2.4 at the moment): <http://www.qt.io/download-open-source/>.
Install the latest stable version of Qt6 LTS (6.10.0 at the moment): <http://www.qt.io/download-open-source/>.

If it tells you that XCode 5.0.0 needs to be installed, go to XCode > Preferences > Locations and make a selection in
the _Command Line Tools_ box.

Add Qt6 to your PATH environment variable, adding to your `.bash_profile` file the following line:

export PATH=$PATH:~/Qt/6.2.4/clang_64/bin
export PATH=$PATH:~/Qt/6.10.0/clang_64/bin

Adjust accordingly if you customized the Qt install directory.

Expand All @@ -50,7 +50,7 @@ Building

To build with official Qt sources do:

cmake -Bbuild -H. -GNinja -DCMAKE_PREFIX_PATH=~/Qt/6.2.4/clang_64
cmake -Bbuild -H. -GNinja -DCMAKE_PREFIX_PATH=~/Qt/6.10.0/clang_64

Alternatively if using Qt6 from Homebrew, do

Expand Down
8 changes: 4 additions & 4 deletions BUILD.windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ choco install aqt -yfd
Then specify the following options in the installation command:

```[pwsh]
aqt install-qt windows desktop 6.2.4 win64_msvc2019_64 -O C:/Qt
aqt install-qt windows desktop 6.10.0 win64_msvc2022_64 -O C:/Qt
```

Alternatively, download the [qtbase archive](https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_624/qt.qt6.624.win64_msvc2019_64/6.2.4-0-202203140926qtbase-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z)
and extract it to `C:\Qt` (so it ends up in `C:\Qt\6.2.4`).
Alternatively, download the [qtbase archive](https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6100/qt6_6100/qt.qt6.6100.win64_msvc2022_64/6.10.0-0-202510021201qtbase-Windows-Windows_11_24H2-MSVC2022-Windows-Windows_11_24H2-X86_64.7z)
and extract it to `C:\Qt\6.10.0`.

### Path setup

Expand All @@ -67,7 +67,7 @@ You'll have to manually add CMake and Qt to your account's PATH variable.
Search for "Edit environment variables for your account" then edit your Path variable. Add the following:

- `C:\Program Files\CMake\bin`
- `C:\Qt\6.2.4\msvc2019_64\bin`
- `C:\Qt\6.10.0\bin`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: The directory structure for the qtbase binary archive is different now.


### Console setup

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Please follow the platform-specific build instructions in the files below:
### Generic build tips

**Qt version compatibility**
Multipass is tested with **Qt 6.2.4**. Newer patch versions along the 6.2 track (e.g. 6.2.8) should be fine. Newer minor versions may work, but they may cause compatibility issues.
Multipass is tested with **Qt 6.10.0**. Newer patch versions along the 6.10 track (e.g. 6.10.4) should be fine. Newer minor versions may work, but they may cause compatibility issues.

**ARM64 and cross-compilation**
If you're using Apple Silicon (arm64) or cross-compiling, ensure that your `PATH` and `CMAKE_PREFIX_PATH` environment variables point to the correct Qt installation for your system architecture.
Expand Down
7 changes: 7 additions & 0 deletions include/multipass/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ bool is_dir(const std::string& path);
QString backend_directory_path(const Path& path, const QString& subdirectory);
std::string contents_of(const multipass::Path& file_path);

// path normalization: returns the lexically-normal form of the path with any trailing directory
// separator removed. The string overloads additionally convert directory separators to generic
// form ("/").
std::filesystem::path normalize_path(const std::filesystem::path& path);
std::string normalize_path(const std::string& path);
QString normalize_path(const QString& path);

// filesystem mount helpers
void make_target_dir(SSHSession& session,
const std::string& root,
Expand Down
5 changes: 1 addition & 4 deletions src/cert/client_cert_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,8 @@ auto load_certs_from_file(const QDir& cert_dir)

QFile cert_file{path};

if (cert_file.exists())
{
cert_file.open(QFile::ReadOnly);
if (cert_file.exists() && cert_file.open(QFile::ReadOnly))
certs = QSslCertificate::fromDevice(&cert_file);
}

return certs;
}
Expand Down
6 changes: 4 additions & 2 deletions src/client/cli/cmd/exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@

#include <multipass/cli/argparser.h>
#include <multipass/ssh/ssh_client.h>
#include <multipass/utils.h>

namespace mp = multipass;
namespace mpu = multipass::utils;
namespace cmd = multipass::cmd;

namespace
Expand Down Expand Up @@ -75,14 +77,14 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser)
(!parser->executeAlias() && !parser->isSet(no_dir_mapping_option)))
{
// The host directory on which the user is executing the command.
QString clean_exec_dir = QDir::cleanPath(QDir::current().canonicalPath());
QString clean_exec_dir = mpu::normalize_path(QDir::current().canonicalPath());
QStringList split_exec_dir = clean_exec_dir.split('/');

auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) {
for (const auto& mount : reply.details(0).mount_info().mount_paths())
{
auto source_dir = QDir(QString::fromStdString(mount.source_path()));
auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath());
auto clean_source_dir = mpu::normalize_path(source_dir.absolutePath());
QStringList split_source_dir = clean_source_dir.split('/');

// If the directory is mounted, we need to `cd` to it in the instance before
Expand Down
3 changes: 1 addition & 2 deletions src/daemon/daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2500,8 +2500,7 @@ try
for (const auto& path_entry : request->target_paths())
{
const auto& name = path_entry.instance_name();
const auto target_path =
QDir::cleanPath(QString::fromStdString(path_entry.target_path())).toStdString();
const auto target_path = mpu::normalize_path(path_entry.target_path());

if (operative_instances.find(name) == operative_instances.end())
{
Expand Down
4 changes: 3 additions & 1 deletion src/network/url_downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ void mp::URLDownloader::download_to(const QUrl& url,
auto manager{MP_NETMGRFACTORY.make_network_manager(cache_dir_path)};

QFile file{file_name};
file.open(QIODevice::ReadWrite | QIODevice::Truncate);
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate))
throw std::runtime_error(
fmt::format("unable to write to file \"{}\"", file_name.toStdString()));

auto progress_monitor = [this, &abort_download, &monitor, download_type, size](
QNetworkReply* reply,
Expand Down
7 changes: 5 additions & 2 deletions src/platform/backends/qemu/linux/dnsmasq_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ mp::DNSMasqServer::DNSMasqServer(const Path& data_dir,
subnet{subnet},
conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")}
{
conf_file.open();
if (!conf_file.open())
throw std::runtime_error("unable to create temporary dnsmasq conf file");
conf_file.close();

QFile dnsmasq_hosts(QDir(data_dir).filePath("dnsmasq.hosts"));
if (!dnsmasq_hosts.exists())
{
dnsmasq_hosts.open(QIODevice::WriteOnly);
if (!dnsmasq_hosts.open(QIODevice::WriteOnly))
throw std::runtime_error(
fmt::format("unable to create file {}", dnsmasq_hosts.filesystemFileName()));
}

dnsmasq_cmd = make_dnsmasq_process(data_dir, bridge_name, subnet, conf_file.fileName());
Expand Down
2 changes: 1 addition & 1 deletion src/utils/json_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const

// Interprocess lock file to ensure that we can synchronize the request from
// both the daemon and the client.
QLockFile lock(fi.filePath() + u".lock"_qs);
QLockFile lock(fi.filePath() + ".lock");

// Make the lock file stale after a while to avoid deadlocking
// on process crashes, etc.
Expand Down
49 changes: 41 additions & 8 deletions src/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <array>
#include <cassert>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <optional>
#include <random>
Expand Down Expand Up @@ -161,6 +162,25 @@ QDir mp::utils::base_dir(const QString& path)
return info.absoluteDir();
}

std::filesystem::path mp::utils::normalize_path(const std::filesystem::path& path)
{
auto result = path.lexically_normal();
// Remove the trailing slash if present.
if (!result.has_filename())
result = result.parent_path();
return result;
}

std::string mp::utils::normalize_path(const std::string& path)
{
return normalize_path(std::filesystem::path{path}).generic_string();
}

QString mp::utils::normalize_path(const QString& path)
{
return QString::fromStdString(normalize_path(path.toStdString()));
}

bool mp::utils::valid_hostname(const std::string& name_string)
{
QRegularExpression matcher{
Expand Down Expand Up @@ -432,7 +452,9 @@ void mp::utils::check_and_create_config_file(const QString& config_file_path)
MP_UTILS.make_dir(
{},
QFileInfo{config_file_path}.dir().path()); // make sure parent dir is there
config_file.open(QIODevice::WriteOnly);
if (!config_file.open(QIODevice::WriteOnly))
throw std::runtime_error(
fmt::format("Unable to create config file \"{}\"", config_file_path.toStdString()));
}
}

Expand Down Expand Up @@ -604,22 +626,33 @@ bool mp::Utils::is_ipv4_valid(const std::string& ipv4) const

mp::Path mp::Utils::default_mount_target(const Path& source) const
{
return source.isEmpty()
? ""
: QDir{QDir::cleanPath(source)}.dirName().prepend(QString{home_in_instance} + '/');
// The GUI calls this function on every update of the host mount directory, so we need to be
// pretty lenient with what we accept for now. In the future, the GUI should be able to handle
// errors from this function as an indicator that the host mount directory is invalid.
if (source == "")
return "";

auto path = mp::utils::normalize_path(std::filesystem::absolute(source.toStdString()));
if (!path.has_filename())
return "";
auto mount_target = home_in_instance / path.filename();
return QString::fromStdString(mount_target.generic_string());
}

QString mp::Utils::normalize_mount_target(QString target_mount_path) const
{
if (QDir::isRelativePath(target_mount_path)) // rely on Qt to understand Linux paths on Windows
target_mount_path.prepend('/').prepend(home_in_instance); // QString::prepend is fast
std::filesystem::path target_mount = target_mount_path.toStdString();
if (target_mount.is_relative())
target_mount = home_in_instance / target_mount;

return QDir::cleanPath(target_mount_path);
target_mount = mp::utils::normalize_path(target_mount);
return QString::fromStdString(target_mount.generic_string());
}

bool mp::Utils::invalid_target_path(const QString& target_path) const
{
assert(target_path == QDir::cleanPath(target_path) && "target_path must be normalized");
assert(target_path == mp::utils::normalize_path(target_path) &&
"target_path must be normalized");
static QRegularExpression matcher{
QRegularExpression::anchoredPattern("/+|/+(dev|proc|sys)(/.*)*|/+home(/*)(/ubuntu/*)*")};

Expand Down
11 changes: 5 additions & 6 deletions tests/file_operations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,11 @@ namespace mpt = multipass::test;
QByteArray mpt::load(QString path)
{
QFile file(path);
if (file.exists())
{
file.open(QIODevice::ReadOnly);
return file.readAll();
}
throw std::invalid_argument(path.toStdString() + " does not exist");
if (!file.exists())
throw std::invalid_argument(path.toStdString() + " does not exist");
if (!file.open(QIODevice::ReadOnly))
throw std::runtime_error("failed to open " + path.toStdString());
return file.readAll();
}

QByteArray mpt::load_test_file(const char* file_name)
Expand Down
4 changes: 2 additions & 2 deletions tests/linux/test_platform_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ TEST_F(PlatformLinux, createAliasScriptWorksUnconfined)
mp::AliasDefinition{"instance", "command", "map"}));

QFile checked_script(tmp_dir.path() + "/bin/alias_name");
checked_script.open(QFile::ReadOnly);
ASSERT_TRUE(checked_script.open(QFile::ReadOnly));

EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n");
EXPECT_EQ(checked_script.readLine().toStdString(), "\n");
Expand Down Expand Up @@ -559,7 +559,7 @@ TEST_F(PlatformLinux, createAliasScriptWorksConfined)
mp::AliasDefinition{"instance", "command", "map"}));

QFile checked_script(tmp_dir.path() + "/bin/alias_name");
checked_script.open(QFile::ReadOnly);
ASSERT_TRUE(checked_script.open(QFile::ReadOnly));

EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n");
EXPECT_EQ(checked_script.readLine().toStdString(), "\n");
Expand Down
2 changes: 1 addition & 1 deletion tests/macos/test_platform_osx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ TEST(PlatformOSX, createAliasScriptWorks)
MP_PLATFORM.create_alias_script("alias_name", mp::AliasDefinition{"instance", "command"}));

QFile checked_script(tmp_dir.path() + "/bin/alias_name");
checked_script.open(QFile::ReadOnly);
ASSERT_TRUE(checked_script.open(QFile::ReadOnly));

EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n");
EXPECT_EQ(checked_script.readLine().toStdString(), "\n");
Expand Down
Loading
Loading