|
Packit |
209faa |
// Copyright 2010 The Kyua Authors.
|
|
Packit |
209faa |
// All rights reserved.
|
|
Packit |
209faa |
//
|
|
Packit |
209faa |
// Redistribution and use in source and binary forms, with or without
|
|
Packit |
209faa |
// modification, are permitted provided that the following conditions are
|
|
Packit |
209faa |
// met:
|
|
Packit |
209faa |
//
|
|
Packit |
209faa |
// * Redistributions of source code must retain the above copyright
|
|
Packit |
209faa |
// notice, this list of conditions and the following disclaimer.
|
|
Packit |
209faa |
// * Redistributions in binary form must reproduce the above copyright
|
|
Packit |
209faa |
// notice, this list of conditions and the following disclaimer in the
|
|
Packit |
209faa |
// documentation and/or other materials provided with the distribution.
|
|
Packit |
209faa |
// * Neither the name of Google Inc. nor the names of its contributors
|
|
Packit |
209faa |
// may be used to endorse or promote products derived from this software
|
|
Packit |
209faa |
// without specific prior written permission.
|
|
Packit |
209faa |
//
|
|
Packit |
209faa |
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
Packit |
209faa |
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
Packit |
209faa |
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
Packit |
209faa |
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
Packit |
209faa |
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
Packit |
209faa |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
Packit |
209faa |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
Packit |
209faa |
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
Packit |
209faa |
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
Packit |
209faa |
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
Packit |
209faa |
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#include "utils/fs/operations.hpp"
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#if defined(HAVE_CONFIG_H)
|
|
Packit |
209faa |
# include "config.h"
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
|
|
Packit |
209faa |
extern "C" {
|
|
Packit |
209faa |
#include <sys/param.h>
|
|
Packit |
209faa |
#if defined(HAVE_SYS_MOUNT_H)
|
|
Packit |
209faa |
# include <sys/mount.h>
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
#include <sys/stat.h>
|
|
Packit |
209faa |
#if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
|
|
Packit |
209faa |
# include <sys/statvfs.h>
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
#if defined(HAVE_SYS_VFS_H)
|
|
Packit |
209faa |
# include <sys/vfs.h>
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
#include <sys/wait.h>
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#include <unistd.h>
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#include <cerrno>
|
|
Packit |
209faa |
#include <cstdlib>
|
|
Packit |
209faa |
#include <cstring>
|
|
Packit |
209faa |
#include <fstream>
|
|
Packit |
209faa |
#include <iostream>
|
|
Packit |
209faa |
#include <sstream>
|
|
Packit |
209faa |
#include <string>
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#include "utils/auto_array.ipp"
|
|
Packit |
209faa |
#include "utils/defs.hpp"
|
|
Packit |
209faa |
#include "utils/env.hpp"
|
|
Packit |
209faa |
#include "utils/format/macros.hpp"
|
|
Packit |
209faa |
#include "utils/fs/directory.hpp"
|
|
Packit |
209faa |
#include "utils/fs/exceptions.hpp"
|
|
Packit |
209faa |
#include "utils/fs/path.hpp"
|
|
Packit |
209faa |
#include "utils/logging/macros.hpp"
|
|
Packit |
209faa |
#include "utils/optional.ipp"
|
|
Packit |
209faa |
#include "utils/sanity.hpp"
|
|
Packit |
209faa |
#include "utils/units.hpp"
|
|
Packit |
209faa |
|
|
Packit |
209faa |
namespace fs = utils::fs;
|
|
Packit |
209faa |
namespace units = utils::units;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
using utils::optional;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
namespace {
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Operating systems recognized by the code below.
|
|
Packit |
209faa |
enum os_type {
|
|
Packit |
209faa |
os_unsupported = 0,
|
|
Packit |
209faa |
os_freebsd,
|
|
Packit |
209faa |
os_linux,
|
|
Packit |
209faa |
os_netbsd,
|
|
Packit |
209faa |
os_sunos,
|
|
Packit |
209faa |
};
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// The current operating system.
|
|
Packit |
209faa |
static enum os_type current_os =
|
|
Packit |
209faa |
#if defined(__FreeBSD__)
|
|
Packit |
209faa |
os_freebsd
|
|
Packit |
209faa |
#elif defined(__linux__)
|
|
Packit |
209faa |
os_linux
|
|
Packit |
209faa |
#elif defined(__NetBSD__)
|
|
Packit |
209faa |
os_netbsd
|
|
Packit |
209faa |
#elif defined(__SunOS__)
|
|
Packit |
209faa |
os_sunos
|
|
Packit |
209faa |
#else
|
|
Packit |
209faa |
os_unsupported
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Specifies if a real unmount(2) is available.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// We use this as a constant instead of a macro so that we can compile both
|
|
Packit |
209faa |
/// versions of the unmount code unconditionally. This is a way to prevent
|
|
Packit |
209faa |
/// compilation bugs going unnoticed for long.
|
|
Packit |
209faa |
static const bool have_unmount2 =
|
|
Packit |
209faa |
#if defined(HAVE_UNMOUNT)
|
|
Packit |
209faa |
true;
|
|
Packit |
209faa |
#else
|
|
Packit |
209faa |
false;
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#if !defined(UMOUNT)
|
|
Packit |
209faa |
/// Fake replacement value to the path to umount(8).
|
|
Packit |
209faa |
# define UMOUNT "do-not-use-this-value"
|
|
Packit |
209faa |
#else
|
|
Packit |
209faa |
# if defined(HAVE_UNMOUNT)
|
|
Packit |
209faa |
# error "umount(8) detected when unmount(2) is also available"
|
|
Packit |
209faa |
# endif
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
#if !defined(HAVE_UNMOUNT)
|
|
Packit |
209faa |
/// Fake unmount(2) function for systems without it.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// This is only provided to allow our code to compile in all platforms
|
|
Packit |
209faa |
/// regardless of whether they actually have an unmount(2) or not.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param unused_path The mount point to be unmounted.
|
|
Packit |
209faa |
/// \param unused_flags The flags to the unmount(2) call.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return -1 to indicate error, although this should never happen.
|
|
Packit |
209faa |
static int
|
|
Packit |
209faa |
unmount(const char* UTILS_UNUSED_PARAM(path),
|
|
Packit |
209faa |
const int UTILS_UNUSED_PARAM(flags))
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
PRE(false);
|
|
Packit |
209faa |
return -1;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Error code returned by subprocess to indicate a controlled failure.
|
|
Packit |
209faa |
const int exit_known_error = 123;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
static void run_mount_tmpfs(const fs::path&, const uint64_t) UTILS_NORETURN;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Executes 'mount -t tmpfs' (or a similar variant).
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// This function must be called from a subprocess as it never returns.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param mount_point Location on which to mount a tmpfs.
|
|
Packit |
209faa |
/// \param size The size of the tmpfs to mount. If 0, use unlimited.
|
|
Packit |
209faa |
static void
|
|
Packit |
209faa |
run_mount_tmpfs(const fs::path& mount_point, const uint64_t size)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
const char* mount_args[16];
|
|
Packit |
209faa |
std::string size_arg;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
std::size_t last = 0;
|
|
Packit |
209faa |
switch (current_os) {
|
|
Packit |
209faa |
case os_freebsd:
|
|
Packit |
209faa |
mount_args[last++] = "mount";
|
|
Packit |
209faa |
mount_args[last++] = "-ttmpfs";
|
|
Packit |
209faa |
if (size > 0) {
|
|
Packit |
209faa |
size_arg = F("-osize=%s") % size;
|
|
Packit |
209faa |
mount_args[last++] = size_arg.c_str();
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
mount_args[last++] = "tmpfs";
|
|
Packit |
209faa |
mount_args[last++] = mount_point.c_str();
|
|
Packit |
209faa |
break;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
case os_linux:
|
|
Packit |
209faa |
mount_args[last++] = "mount";
|
|
Packit |
209faa |
mount_args[last++] = "-ttmpfs";
|
|
Packit |
209faa |
if (size > 0) {
|
|
Packit |
209faa |
size_arg = F("-osize=%s") % size;
|
|
Packit |
209faa |
mount_args[last++] = size_arg.c_str();
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
mount_args[last++] = "tmpfs";
|
|
Packit |
209faa |
mount_args[last++] = mount_point.c_str();
|
|
Packit |
209faa |
break;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
case os_netbsd:
|
|
Packit |
209faa |
mount_args[last++] = "mount";
|
|
Packit |
209faa |
mount_args[last++] = "-ttmpfs";
|
|
Packit |
209faa |
if (size > 0) {
|
|
Packit |
209faa |
size_arg = F("-o-s%s") % size;
|
|
Packit |
209faa |
mount_args[last++] = size_arg.c_str();
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
mount_args[last++] = "tmpfs";
|
|
Packit |
209faa |
mount_args[last++] = mount_point.c_str();
|
|
Packit |
209faa |
break;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
case os_sunos:
|
|
Packit |
209faa |
mount_args[last++] = "mount";
|
|
Packit |
209faa |
mount_args[last++] = "-Ftmpfs";
|
|
Packit |
209faa |
if (size > 0) {
|
|
Packit |
209faa |
size_arg = F("-o-s%s") % size;
|
|
Packit |
209faa |
mount_args[last++] = size_arg.c_str();
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
mount_args[last++] = "tmpfs";
|
|
Packit |
209faa |
mount_args[last++] = mount_point.c_str();
|
|
Packit |
209faa |
break;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
default:
|
|
Packit |
209faa |
std::cerr << "Don't know how to mount a temporary file system in this "
|
|
Packit |
209faa |
"host operating system\n";
|
|
Packit |
209faa |
std::exit(exit_known_error);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
mount_args[last] = NULL;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const char** arg;
|
|
Packit |
209faa |
std::cout << "Mounting tmpfs onto " << mount_point << " with:";
|
|
Packit |
209faa |
for (arg = &mount_args[0]; *arg != NULL; arg++)
|
|
Packit |
209faa |
std::cout << " " << *arg;
|
|
Packit |
209faa |
std::cout << "\n";
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const int ret = ::execvp(mount_args[0],
|
|
Packit |
209faa |
UTILS_UNCONST(char* const, mount_args));
|
|
Packit |
209faa |
INV(ret == -1);
|
|
Packit |
209faa |
std::cerr << "Failed to exec " << mount_args[0] << "\n";
|
|
Packit |
209faa |
std::exit(EXIT_FAILURE);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Unmounts a file system using unmount(2).
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \pre unmount(2) must be available; i.e. have_unmount2 must be true.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param mount_point The file system to unmount.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the call to unmount(2) fails.
|
|
Packit |
209faa |
static void
|
|
Packit |
209faa |
unmount_with_unmount2(const fs::path& mount_point)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
PRE(have_unmount2);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
if (::unmount(mount_point.c_str(), 0) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("unmount(%s) failed") % mount_point,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Unmounts a file system using umount(8).
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \pre umount(2) must not be available; i.e. have_unmount2 must be false.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param mount_point The file system to unmount.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::error If the execution of umount(8) fails.
|
|
Packit |
209faa |
static void
|
|
Packit |
209faa |
unmount_with_umount8(const fs::path& mount_point)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
PRE(!have_unmount2);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const pid_t pid = ::fork();
|
|
Packit |
209faa |
if (pid == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error("Cannot fork to execute unmount tool",
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
} else if (pid == 0) {
|
|
Packit |
209faa |
const int ret = ::execlp(UMOUNT, "umount", mount_point.c_str(), NULL);
|
|
Packit |
209faa |
INV(ret == -1);
|
|
Packit |
209faa |
std::cerr << "Failed to exec " UMOUNT "\n";
|
|
Packit |
209faa |
std::exit(EXIT_FAILURE);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
int status;
|
|
Packit |
209faa |
retry:
|
|
Packit |
209faa |
if (::waitpid(pid, &status, 0) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
if (errno == EINTR)
|
|
Packit |
209faa |
goto retry;
|
|
Packit |
209faa |
throw fs::system_error("Failed to wait for unmount subprocess",
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
if (WIFEXITED(status)) {
|
|
Packit |
209faa |
if (WEXITSTATUS(status) == EXIT_SUCCESS)
|
|
Packit |
209faa |
return;
|
|
Packit |
209faa |
else
|
|
Packit |
209faa |
throw fs::error(F("Failed to unmount %s; returned exit code %s")
|
|
Packit |
209faa |
% mount_point % WEXITSTATUS(status));
|
|
Packit |
209faa |
} else
|
|
Packit |
209faa |
throw fs::error(F("Failed to unmount %s; unmount tool received signal")
|
|
Packit |
209faa |
% mount_point);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Stats a file, without following links.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path The file to stat.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The stat structure on success.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw system_error An error on failure.
|
|
Packit |
209faa |
static struct ::stat
|
|
Packit |
209faa |
safe_stat(const fs::path& path)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
struct ::stat sb;
|
|
Packit |
209faa |
if (::lstat(path.c_str(), &sb) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Cannot get information about %s") % path,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
return sb;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
} // anonymous namespace
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Copies a file.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param source The file to copy.
|
|
Packit |
209faa |
/// \param target The destination of the new copy; must be a file name, not a
|
|
Packit |
209faa |
/// directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw error If there is a problem copying the file.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::copy(const fs::path& source, const fs::path& target)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
std::ifstream input(source.c_str());
|
|
Packit |
209faa |
if (!input)
|
|
Packit |
209faa |
throw error(F("Cannot open copy source %s") % source);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
std::ofstream output(target.c_str());
|
|
Packit |
209faa |
if (!output)
|
|
Packit |
209faa |
throw error(F("Cannot create copy target %s") % target);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
char buffer[1024];
|
|
Packit |
209faa |
while (input.good()) {
|
|
Packit |
209faa |
input.read(buffer, sizeof(buffer));
|
|
Packit |
209faa |
if (input.good() || input.eof())
|
|
Packit |
209faa |
output.write(buffer, input.gcount());
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
if (!input.good() && !input.eof())
|
|
Packit |
209faa |
throw error(F("Error while reading input file %s") % source);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Queries the path to the current directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The path to the current directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::error If there is a problem querying the current directory.
|
|
Packit |
209faa |
fs::path
|
|
Packit |
209faa |
fs::current_path(void)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
char* cwd;
|
|
Packit |
209faa |
#if defined(HAVE_GETCWD_DYN)
|
|
Packit |
209faa |
cwd = ::getcwd(NULL, 0);
|
|
Packit |
209faa |
#else
|
|
Packit |
209faa |
cwd = ::getcwd(NULL, MAXPATHLEN);
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
if (cwd == NULL) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Failed to get current working directory"),
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
try {
|
|
Packit |
209faa |
const fs::path result(cwd);
|
|
Packit |
209faa |
std::free(cwd);
|
|
Packit |
209faa |
return result;
|
|
Packit |
209faa |
} catch (...) {
|
|
Packit |
209faa |
std::free(cwd);
|
|
Packit |
209faa |
throw;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Checks if a file exists.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// Be aware that this is racy in the same way as access(2) is.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path The file to check the existance of.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return True if the file exists; false otherwise.
|
|
Packit |
209faa |
bool
|
|
Packit |
209faa |
fs::exists(const fs::path& path)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
return ::access(path.c_str(), F_OK) == 0;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Locates a file in the PATH.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param name The file to locate.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The path to the located file or none if it was not found. The
|
|
Packit |
209faa |
/// returned path is always absolute.
|
|
Packit |
209faa |
optional< fs::path >
|
|
Packit |
209faa |
fs::find_in_path(const char* name)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
const optional< std::string > current_path = utils::getenv("PATH");
|
|
Packit |
209faa |
if (!current_path || current_path.get().empty())
|
|
Packit |
209faa |
return none;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
std::istringstream path_input(current_path.get() + ":");
|
|
Packit |
209faa |
std::string path_component;
|
|
Packit |
209faa |
while (std::getline(path_input, path_component, ':').good()) {
|
|
Packit |
209faa |
const fs::path candidate = path_component.empty() ?
|
|
Packit |
209faa |
fs::path(name) : (fs::path(path_component) / name);
|
|
Packit |
209faa |
if (exists(candidate)) {
|
|
Packit |
209faa |
if (candidate.is_absolute())
|
|
Packit |
209faa |
return utils::make_optional(candidate);
|
|
Packit |
209faa |
else
|
|
Packit |
209faa |
return utils::make_optional(candidate.to_absolute());
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
return none;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Calculates the free space in a given file system.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path Path to a file in the file system for which to check the free
|
|
Packit |
209faa |
/// disk space.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The amount of free space usable by a non-root user.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw system_error If the call to statfs(2) fails.
|
|
Packit |
209faa |
utils::units::bytes
|
|
Packit |
209faa |
fs::free_disk_space(const fs::path& path)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
#if defined(HAVE_STATVFS)
|
|
Packit |
209faa |
struct ::statvfs buf;
|
|
Packit |
209faa |
if (::statvfs(path.c_str(), &buf) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Failed to stat file system for %s") % path,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail);
|
|
Packit |
209faa |
#elif defined(HAVE_STATFS)
|
|
Packit |
209faa |
struct ::statfs buf;
|
|
Packit |
209faa |
if (::statfs(path.c_str(), &buf) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Failed to stat file system for %s") % path,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
return units::bytes(uint64_t(buf.f_bsize) * buf.f_bavail);
|
|
Packit |
209faa |
#else
|
|
Packit |
209faa |
# error "Don't know how to query free disk space"
|
|
Packit |
209faa |
#endif
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Checks if the given path is a directory or not.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return True if the path is a directory; false otherwise.
|
|
Packit |
209faa |
bool
|
|
Packit |
209faa |
fs::is_directory(const fs::path& path)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
const struct ::stat sb = safe_stat(path);
|
|
Packit |
209faa |
return S_ISDIR(sb.st_mode);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Creates a directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param dir The path to the directory to create.
|
|
Packit |
209faa |
/// \param mode The permissions for the new directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw system_error If the call to mkdir(2) fails.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::mkdir(const fs::path& dir, const int mode)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
if (::mkdir(dir.c_str(), static_cast< mode_t >(mode)) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Failed to create directory %s") % dir,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Creates a directory and any missing parents.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// This is separate from the fs::mkdir function to clearly differentiate the
|
|
Packit |
209faa |
/// libc wrapper from the more complex algorithm implemented here.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param dir The path to the directory to create.
|
|
Packit |
209faa |
/// \param mode The permissions for the new directories.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw system_error If any call to mkdir(2) fails.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::mkdir_p(const fs::path& dir, const int mode)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
try {
|
|
Packit |
209faa |
fs::mkdir(dir, mode);
|
|
Packit |
209faa |
} catch (const fs::system_error& e) {
|
|
Packit |
209faa |
if (e.original_errno() == ENOENT) {
|
|
Packit |
209faa |
fs::mkdir_p(dir.branch_path(), mode);
|
|
Packit |
209faa |
fs::mkdir(dir, mode);
|
|
Packit |
209faa |
} else if (e.original_errno() != EEXIST)
|
|
Packit |
209faa |
throw e;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Creates a temporary directory that is world readable/accessible.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// The temporary directory is created using mkdtemp(3) using the provided
|
|
Packit |
209faa |
/// template. This should be most likely used in conjunction with
|
|
Packit |
209faa |
/// fs::auto_directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// The temporary directory is given read and execute permissions to everyone
|
|
Packit |
209faa |
/// and thus should not be used to protect data that may be subject to snooping.
|
|
Packit |
209faa |
/// This goes together with the assumption that this function is used to create
|
|
Packit |
209faa |
/// temporary directories for test cases, and that those test cases may
|
|
Packit |
209faa |
/// sometimes be executed as an unprivileged user. In those cases, we need to
|
|
Packit |
209faa |
/// support two different things:
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// - Allow the unprivileged code to write to files in the work directory by
|
|
Packit |
209faa |
/// name (e.g. to write the results file, whose name is provided by the
|
|
Packit |
209faa |
/// monitor code running as root). This requires us to grant search
|
|
Packit |
209faa |
/// permissions.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// - Allow the test cases themselves to call getcwd(3) at any point. At least
|
|
Packit |
209faa |
/// on NetBSD 7.x, getcwd(3) requires both read and search permissions on all
|
|
Packit |
209faa |
/// path components leading to the current directory. This requires us to
|
|
Packit |
209faa |
/// grant both read and search permissions.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// TODO(jmmv): A cleaner way to support this would be for the test executor to
|
|
Packit |
209faa |
/// create two work directory hierarchies directly rooted under TMPDIR: one for
|
|
Packit |
209faa |
/// root and one for the unprivileged user. However, that requires more
|
|
Packit |
209faa |
/// bookkeeping for no real gain, because we are not really trying to protect
|
|
Packit |
209faa |
/// the data within our temporary directories against attacks.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path_template The template for the temporary path, which is a
|
|
Packit |
209faa |
/// basename that is created within the TMPDIR. Must contain the XXXXXX
|
|
Packit |
209faa |
/// pattern, which is atomically replaced by a random unique string.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The generated path for the temporary directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the call to mkdtemp(3) fails.
|
|
Packit |
209faa |
fs::path
|
|
Packit |
209faa |
fs::mkdtemp_public(const std::string& path_template)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
PRE(path_template.find("XXXXXX") != std::string::npos);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
|
|
Packit |
209faa |
const fs::path full_template = tmpdir / path_template;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
|
|
Packit |
209faa |
std::strcpy(buf.get(), full_template.c_str());
|
|
Packit |
209faa |
if (::mkdtemp(buf.get()) == NULL) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Cannot create temporary directory using "
|
|
Packit |
209faa |
"template %s") % full_template,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
const fs::path path(buf.get());
|
|
Packit |
209faa |
|
|
Packit |
209faa |
if (::chmod(path.c_str(), 0755) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
try {
|
|
Packit |
209faa |
rmdir(path);
|
|
Packit |
209faa |
} catch (const fs::system_error& e) {
|
|
Packit |
209faa |
// This really should not fail. We just created the directory and
|
|
Packit |
209faa |
// have not written anything to it so there is no reason for this to
|
|
Packit |
209faa |
// fail. But better handle the failure just in case.
|
|
Packit |
209faa |
LW(F("Failed to delete just-created temporary directory %s")
|
|
Packit |
209faa |
% path);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
throw fs::system_error(F("Failed to grant search permissions on "
|
|
Packit |
209faa |
"temporary directory %s") % path,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
return path;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Creates a temporary file.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// The temporary file is created using mkstemp(3) using the provided template.
|
|
Packit |
209faa |
/// This should be most likely used in conjunction with fs::auto_file.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path_template The template for the temporary path, which is a
|
|
Packit |
209faa |
/// basename that is created within the TMPDIR. Must contain the XXXXXX
|
|
Packit |
209faa |
/// pattern, which is atomically replaced by a random unique string.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The generated path for the temporary directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the call to mkstemp(3) fails.
|
|
Packit |
209faa |
fs::path
|
|
Packit |
209faa |
fs::mkstemp(const std::string& path_template)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
PRE(path_template.find("XXXXXX") != std::string::npos);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const fs::path tmpdir(utils::getenv_with_default("TMPDIR", "/tmp"));
|
|
Packit |
209faa |
const fs::path full_template = tmpdir / path_template;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
utils::auto_array< char > buf(new char[full_template.str().length() + 1]);
|
|
Packit |
209faa |
std::strcpy(buf.get(), full_template.c_str());
|
|
Packit |
209faa |
if (::mkstemp(buf.get()) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Cannot create temporary file using template "
|
|
Packit |
209faa |
"%s") % full_template, original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
return fs::path(buf.get());
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Mounts a temporary file system with unlimited size.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param in_mount_point The path on which the file system will be mounted.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the attempt to mount process fails.
|
|
Packit |
209faa |
/// \throw fs::unsupported_operation_error If the code does not know how to
|
|
Packit |
209faa |
/// mount a temporary file system in the current operating system.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::mount_tmpfs(const fs::path& in_mount_point)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
mount_tmpfs(in_mount_point, units::bytes());
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Mounts a temporary file system.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param in_mount_point The path on which the file system will be mounted.
|
|
Packit |
209faa |
/// \param size The size of the tmpfs to mount. If 0, use unlimited.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the attempt to mount process fails.
|
|
Packit |
209faa |
/// \throw fs::unsupported_operation_error If the code does not know how to
|
|
Packit |
209faa |
/// mount a temporary file system in the current operating system.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::mount_tmpfs(const fs::path& in_mount_point, const units::bytes& size)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
// SunOS's mount(8) requires paths to be absolute. To err on the side of
|
|
Packit |
209faa |
// caution, let's make the mount point absolute in all cases.
|
|
Packit |
209faa |
const fs::path mount_point = in_mount_point.is_absolute() ?
|
|
Packit |
209faa |
in_mount_point : in_mount_point.to_absolute();
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const pid_t pid = ::fork();
|
|
Packit |
209faa |
if (pid == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error("Cannot fork to execute mount tool",
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
if (pid == 0)
|
|
Packit |
209faa |
run_mount_tmpfs(mount_point, size);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
int status;
|
|
Packit |
209faa |
retry:
|
|
Packit |
209faa |
if (::waitpid(pid, &status, 0) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
if (errno == EINTR)
|
|
Packit |
209faa |
goto retry;
|
|
Packit |
209faa |
throw fs::system_error("Failed to wait for mount subprocess",
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
if (WIFEXITED(status)) {
|
|
Packit |
209faa |
if (WEXITSTATUS(status) == exit_known_error)
|
|
Packit |
209faa |
throw fs::unsupported_operation_error(
|
|
Packit |
209faa |
"Don't know how to mount a tmpfs on this operating system");
|
|
Packit |
209faa |
else if (WEXITSTATUS(status) == EXIT_SUCCESS)
|
|
Packit |
209faa |
return;
|
|
Packit |
209faa |
else
|
|
Packit |
209faa |
throw fs::error(F("Failed to mount tmpfs on %s; returned exit "
|
|
Packit |
209faa |
"code %s") % mount_point % WEXITSTATUS(status));
|
|
Packit |
209faa |
} else {
|
|
Packit |
209faa |
throw fs::error(F("Failed to mount tmpfs on %s; mount tool "
|
|
Packit |
209faa |
"received signal") % mount_point);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Recursively removes a directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// This operation simulates a "rm -r". No effort is made to forcibly delete
|
|
Packit |
209faa |
/// files and no attention is paid to mount points.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param directory The directory to remove.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::error If there is a problem removing any directory or file.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::rm_r(const fs::path& directory)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
const fs::directory dir(directory);
|
|
Packit |
209faa |
|
|
Packit |
209faa |
for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
|
|
Packit |
209faa |
++iter) {
|
|
Packit |
209faa |
if (iter->name == "." || iter->name == "..")
|
|
Packit |
209faa |
continue;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
const fs::path entry = directory / iter->name;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
if (fs::is_directory(entry)) {
|
|
Packit |
209faa |
LD(F("Descending into %s") % entry);
|
|
Packit |
209faa |
fs::rm_r(entry);
|
|
Packit |
209faa |
} else {
|
|
Packit |
209faa |
LD(F("Removing file %s") % entry);
|
|
Packit |
209faa |
fs::unlink(entry);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
LD(F("Removing empty directory %s") % directory);
|
|
Packit |
209faa |
fs::rmdir(directory);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Removes an empty directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param file The directory to remove.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the call to rmdir(2) fails.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::rmdir(const path& file)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
if (::rmdir(file.c_str()) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Removal of %s failed") % file,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Obtains all the entries in a directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param path The directory to scan.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \return The set of all directory entries in the given directory.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If reading the directory fails for any reason.
|
|
Packit |
209faa |
std::set< fs::directory_entry >
|
|
Packit |
209faa |
fs::scan_directory(const fs::path& path)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
std::set< fs::directory_entry > contents;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
fs::directory dir(path);
|
|
Packit |
209faa |
for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end();
|
|
Packit |
209faa |
++iter) {
|
|
Packit |
209faa |
contents.insert(*iter);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
return contents;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Removes a file.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param file The file to remove.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::system_error If the call to unlink(2) fails.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::unlink(const path& file)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
if (::unlink(file.c_str()) == -1) {
|
|
Packit |
209faa |
const int original_errno = errno;
|
|
Packit |
209faa |
throw fs::system_error(F("Removal of %s failed") % file,
|
|
Packit |
209faa |
original_errno);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
|
|
Packit |
209faa |
|
|
Packit |
209faa |
/// Unmounts a file system.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \param in_mount_point The file system to unmount.
|
|
Packit |
209faa |
///
|
|
Packit |
209faa |
/// \throw fs::error If the unmount fails.
|
|
Packit |
209faa |
void
|
|
Packit |
209faa |
fs::unmount(const fs::path& in_mount_point)
|
|
Packit |
209faa |
{
|
|
Packit |
209faa |
// FreeBSD's unmount(2) requires paths to be absolute. To err on the side
|
|
Packit |
209faa |
// of caution, let's make it absolute in all cases.
|
|
Packit |
209faa |
const fs::path mount_point = in_mount_point.is_absolute() ?
|
|
Packit |
209faa |
in_mount_point : in_mount_point.to_absolute();
|
|
Packit |
209faa |
|
|
Packit |
209faa |
static const int unmount_retries = 3;
|
|
Packit |
209faa |
static const int unmount_retry_delay_seconds = 1;
|
|
Packit |
209faa |
|
|
Packit |
209faa |
int retries = unmount_retries;
|
|
Packit |
209faa |
retry:
|
|
Packit |
209faa |
try {
|
|
Packit |
209faa |
if (have_unmount2) {
|
|
Packit |
209faa |
unmount_with_unmount2(mount_point);
|
|
Packit |
209faa |
} else {
|
|
Packit |
209faa |
unmount_with_umount8(mount_point);
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
} catch (const fs::system_error& error) {
|
|
Packit |
209faa |
if (error.original_errno() == EBUSY && retries > 0) {
|
|
Packit |
209faa |
LW(F("%s busy; unmount retries left %s") % mount_point % retries);
|
|
Packit |
209faa |
retries--;
|
|
Packit |
209faa |
::sleep(unmount_retry_delay_seconds);
|
|
Packit |
209faa |
goto retry;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
throw;
|
|
Packit |
209faa |
}
|
|
Packit |
209faa |
}
|