| Line |
Branch |
Exec |
Source |
| 1 |
|
|
/** |
| 2 |
|
|
* This file is part of the CernVM File System. |
| 3 |
|
|
*/ |
| 4 |
|
|
|
| 5 |
|
|
|
| 6 |
|
|
#include "cmd_enter.h" |
| 7 |
|
|
|
| 8 |
|
|
#include <dirent.h> |
| 9 |
|
|
#include <errno.h> |
| 10 |
|
|
#include <fcntl.h> |
| 11 |
|
|
#include <linux/limits.h> |
| 12 |
|
|
#include <poll.h> |
| 13 |
|
|
#include <sched.h> |
| 14 |
|
|
#include <signal.h> |
| 15 |
|
|
#include <sys/mount.h> |
| 16 |
|
|
#include <sys/wait.h> |
| 17 |
|
|
#include <unistd.h> |
| 18 |
|
|
|
| 19 |
|
|
#include <algorithm> |
| 20 |
|
|
#include <cassert> |
| 21 |
|
|
#include <cstdio> |
| 22 |
|
|
#include <map> |
| 23 |
|
|
#include <set> |
| 24 |
|
|
#include <string> |
| 25 |
|
|
#include <vector> |
| 26 |
|
|
|
| 27 |
|
|
#include "backoff.h" |
| 28 |
|
|
#include "options.h" |
| 29 |
|
|
#include "publish/except.h" |
| 30 |
|
|
#include "publish/repository.h" |
| 31 |
|
|
#include "publish/settings.h" |
| 32 |
|
|
#include "sanitizer.h" |
| 33 |
|
|
#include "util/logging.h" |
| 34 |
|
|
#include "util/namespace.h" |
| 35 |
|
|
#include "util/platform.h" |
| 36 |
|
|
#include "util/posix.h" |
| 37 |
|
|
#include "util/string.h" |
| 38 |
|
|
|
| 39 |
|
|
using namespace std; // NOLINT |
| 40 |
|
|
|
| 41 |
|
|
namespace { |
| 42 |
|
|
|
| 43 |
|
|
/** |
| 44 |
|
|
* The parent pid and init pid in the outer PID namespace |
| 45 |
|
|
*/ |
| 46 |
|
|
struct AnchorPid { |
| 47 |
|
|
pid_t parent_pid; |
| 48 |
|
|
pid_t init_pid; |
| 49 |
|
|
}; |
| 50 |
|
|
|
| 51 |
|
✗ |
static AnchorPid EnterRootContainer() { |
| 52 |
|
✗ |
const uid_t euid = geteuid(); |
| 53 |
|
✗ |
const uid_t egid = getegid(); |
| 54 |
|
✗ |
const NamespaceFailures failure = CreateUserNamespace(0, 0); |
| 55 |
|
✗ |
if (failure != kFailNsOk) { |
| 56 |
|
✗ |
throw publish::EPublish( |
| 57 |
|
✗ |
"cannot create root user namespace (" + StringifyInt(failure) + " / " |
| 58 |
|
✗ |
+ StringifyInt(errno) + ") [euid=" + StringifyInt(euid) |
| 59 |
|
✗ |
+ ", egid=" + StringifyInt(egid) + "]"); |
| 60 |
|
|
} |
| 61 |
|
|
|
| 62 |
|
✗ |
bool rvb = CreateMountNamespace(); |
| 63 |
|
✗ |
if (!rvb) |
| 64 |
|
✗ |
throw publish::EPublish("cannot create mount namespace"); |
| 65 |
|
|
int fd; |
| 66 |
|
✗ |
rvb = CreatePidNamespace(&fd); |
| 67 |
|
✗ |
if (!rvb) |
| 68 |
|
✗ |
throw publish::EPublish("cannot create pid namespace"); |
| 69 |
|
|
AnchorPid anchor_pid; |
| 70 |
|
✗ |
int rvi = SafeRead(fd, &anchor_pid.parent_pid, sizeof(pid_t)); |
| 71 |
|
✗ |
if (rvi != sizeof(pid_t)) |
| 72 |
|
✗ |
throw publish::EPublish("cannot initialize pid namespace"); |
| 73 |
|
✗ |
rvi = SafeRead(fd, &anchor_pid.init_pid, sizeof(pid_t)); |
| 74 |
|
✗ |
if (rvi != sizeof(pid_t)) |
| 75 |
|
✗ |
throw publish::EPublish("cannot initialize pid namespace"); |
| 76 |
|
✗ |
close(fd); |
| 77 |
|
✗ |
return anchor_pid; |
| 78 |
|
|
} |
| 79 |
|
|
|
| 80 |
|
✗ |
static void EnsureDirectory(const std::string &path) { |
| 81 |
|
✗ |
const bool rv = MkdirDeep(path, 0700, true /* veryfy_writable */); |
| 82 |
|
✗ |
if (!rv) |
| 83 |
|
✗ |
throw publish::EPublish("cannot create directory " + path); |
| 84 |
|
|
} |
| 85 |
|
|
|
| 86 |
|
✗ |
static void RemoveSingle(const std::string &path) { |
| 87 |
|
|
platform_stat64 info; |
| 88 |
|
✗ |
const int retval = platform_lstat(path.c_str(), &info); |
| 89 |
|
✗ |
if (retval != 0) { |
| 90 |
|
✗ |
if (errno == ENOENT) |
| 91 |
|
✗ |
return; |
| 92 |
|
✗ |
throw publish::EPublish("cannot remove " + path); |
| 93 |
|
|
} |
| 94 |
|
|
|
| 95 |
|
✗ |
int rv = 0; |
| 96 |
|
✗ |
if (S_ISDIR(info.st_mode)) |
| 97 |
|
✗ |
rv = rmdir(path.c_str()); |
| 98 |
|
|
else |
| 99 |
|
✗ |
rv = unlink(path.c_str()); |
| 100 |
|
|
|
| 101 |
|
✗ |
if (rv == 0 || errno == ENOENT) |
| 102 |
|
✗ |
return; |
| 103 |
|
|
|
| 104 |
|
✗ |
throw publish::EPublish("cannot remove " + path + " (" + StringifyInt(errno) |
| 105 |
|
✗ |
+ ")"); |
| 106 |
|
|
} |
| 107 |
|
|
|
| 108 |
|
|
|
| 109 |
|
✗ |
static void RemoveUnderlay(const std::string &path, |
| 110 |
|
|
const std::vector<std::string> &new_paths) { |
| 111 |
|
✗ |
std::vector<std::string> all_mounts = platform_mountlist(); |
| 112 |
|
✗ |
std::vector<std::string> umount_targets; |
| 113 |
|
✗ |
for (unsigned i = 0; i < all_mounts.size(); ++i) { |
| 114 |
|
✗ |
if (HasPrefix(all_mounts[i], path + "/", false /* ignore_case */)) |
| 115 |
|
✗ |
umount_targets.push_back(all_mounts[i]); |
| 116 |
|
|
} |
| 117 |
|
|
|
| 118 |
|
✗ |
std::sort(umount_targets.begin(), umount_targets.end()); |
| 119 |
|
✗ |
std::vector<std::string>::reverse_iterator iter = umount_targets.rbegin(); |
| 120 |
|
✗ |
std::vector<std::string>::reverse_iterator iend = umount_targets.rend(); |
| 121 |
|
✗ |
for (; iter != iend; ++iter) { |
| 122 |
|
✗ |
const bool rvb = platform_umount_lazy(iter->c_str()); |
| 123 |
|
|
// Some sub mounts, e.g. /sys/kernel/tracing, cannot be unmounted |
| 124 |
|
✗ |
if (!rvb && errno != EINVAL && errno != EACCES) { |
| 125 |
|
✗ |
throw publish::EPublish("cannot unmount " + *iter + " (" |
| 126 |
|
✗ |
+ StringifyInt(errno) + ")"); |
| 127 |
|
|
} |
| 128 |
|
|
} |
| 129 |
|
|
|
| 130 |
|
✗ |
std::vector<std::string> sorted_new_paths(new_paths); |
| 131 |
|
✗ |
std::sort(sorted_new_paths.begin(), sorted_new_paths.end()); |
| 132 |
|
✗ |
iter = sorted_new_paths.rbegin(); |
| 133 |
|
✗ |
iend = sorted_new_paths.rend(); |
| 134 |
|
✗ |
for (; iter != iend; ++iter) { |
| 135 |
|
✗ |
std::string p = *iter; |
| 136 |
|
✗ |
while (p.length() > path.length()) { |
| 137 |
|
✗ |
assert(HasPrefix(p, path, false /*ignore_case*/)); |
| 138 |
|
|
// The parent path might not be empty until all children are visited |
| 139 |
|
|
try { |
| 140 |
|
✗ |
RemoveSingle(p); |
| 141 |
|
✗ |
} catch (const publish::EPublish &e) { |
| 142 |
|
✗ |
if (errno != ENOTEMPTY) |
| 143 |
|
✗ |
throw; |
| 144 |
|
|
} |
| 145 |
|
✗ |
p = GetParentPath(p); |
| 146 |
|
|
} |
| 147 |
|
|
} |
| 148 |
|
✗ |
RemoveSingle(path); |
| 149 |
|
|
} |
| 150 |
|
|
|
| 151 |
|
|
} // anonymous namespace |
| 152 |
|
|
|
| 153 |
|
|
|
| 154 |
|
|
namespace publish { |
| 155 |
|
|
|
| 156 |
|
✗ |
void CmdEnter::CreateUnderlay(const std::string &source_dir, |
| 157 |
|
|
const std::string &dest_dir, |
| 158 |
|
|
const std::vector<std::string> &empty_dirs, |
| 159 |
|
|
std::vector<std::string> *new_paths) { |
| 160 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: entry %s --> %s", |
| 161 |
|
|
source_dir.c_str(), dest_dir.c_str()); |
| 162 |
|
|
|
| 163 |
|
|
// For an empty directory /cvmfs/atlas.cern.ch, we are going to store "/cvmfs" |
| 164 |
|
✗ |
std::vector<std::string> empty_toplevel_dirs; |
| 165 |
|
✗ |
for (unsigned i = 0; i < empty_dirs.size(); ++i) { |
| 166 |
|
✗ |
std::string toplevel_dir = empty_dirs[i]; |
| 167 |
|
✗ |
while (!GetParentPath(toplevel_dir).empty()) |
| 168 |
|
✗ |
toplevel_dir = GetParentPath(toplevel_dir); |
| 169 |
|
✗ |
empty_toplevel_dirs.push_back(toplevel_dir); |
| 170 |
|
|
|
| 171 |
|
|
// We create $DEST/cvmfs (top-level dir) |
| 172 |
|
✗ |
const std::string dest_empty_dir = dest_dir + toplevel_dir; |
| 173 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: mkdir %s", |
| 174 |
|
|
dest_empty_dir.c_str()); |
| 175 |
|
✗ |
EnsureDirectory(dest_empty_dir); |
| 176 |
|
✗ |
new_paths->push_back(dest_empty_dir); |
| 177 |
|
|
} |
| 178 |
|
|
|
| 179 |
|
✗ |
std::vector<std::string> names; |
| 180 |
|
✗ |
std::vector<mode_t> modes; |
| 181 |
|
|
// In a recursive call, the source directory might not exist, which is fine |
| 182 |
|
✗ |
const std::string d = source_dir.empty() ? "/" : source_dir; |
| 183 |
|
✗ |
if (DirectoryExists(d)) { |
| 184 |
|
✗ |
const bool rv = ListDirectory(d, &names, &modes); |
| 185 |
|
✗ |
if (!rv) |
| 186 |
|
✗ |
throw EPublish("cannot list directory " + d); |
| 187 |
|
|
} |
| 188 |
|
|
// We need to order creating the bindmounts such that the destination itself |
| 189 |
|
|
// is handled first; otherwise, the recursive bind mount to the session dir |
| 190 |
|
|
// creates all other sub bind mounts again. |
| 191 |
|
✗ |
for (unsigned i = 0; i < names.size(); ++i) { |
| 192 |
|
✗ |
const std::string source = source_dir + "/" + names[i] + "/"; |
| 193 |
|
✗ |
const std::string dest = dest_dir + "/" + names[i] + "/"; |
| 194 |
|
✗ |
if (HasPrefix(dest, source, false /* ignore_case */)) { |
| 195 |
|
✗ |
iter_swap(names.begin(), names.begin() + i); |
| 196 |
|
✗ |
iter_swap(modes.begin(), modes.begin() + i); |
| 197 |
|
✗ |
break; |
| 198 |
|
|
} |
| 199 |
|
|
} |
| 200 |
|
|
|
| 201 |
|
|
// List the contents of the source directory |
| 202 |
|
|
// 1. Symlinks are created as they are |
| 203 |
|
|
// 2. Directories become empty directories and are bind-mounted |
| 204 |
|
|
// 3. File become empty regular files and are bind-mounted |
| 205 |
|
✗ |
for (unsigned i = 0; i < names.size(); ++i) { |
| 206 |
|
✗ |
if (std::find(empty_toplevel_dirs.begin(), empty_toplevel_dirs.end(), |
| 207 |
|
✗ |
std::string("/") + names[i]) |
| 208 |
|
✗ |
!= empty_toplevel_dirs.end()) { |
| 209 |
|
✗ |
continue; |
| 210 |
|
|
} |
| 211 |
|
|
|
| 212 |
|
✗ |
const std::string source = source_dir + "/" + names[i]; |
| 213 |
|
✗ |
const std::string dest = dest_dir + "/" + names[i]; |
| 214 |
|
✗ |
new_paths->push_back(dest); |
| 215 |
|
✗ |
if (S_ISLNK(modes[i])) { |
| 216 |
|
|
char buf[PATH_MAX + 1]; |
| 217 |
|
✗ |
const ssize_t nchars = readlink(source.c_str(), buf, PATH_MAX); |
| 218 |
|
✗ |
if (nchars < 0) |
| 219 |
|
✗ |
throw EPublish("cannot read symlink " + source); |
| 220 |
|
✗ |
buf[nchars] = '\0'; |
| 221 |
|
✗ |
SymlinkForced(std::string(buf), dest); |
| 222 |
|
|
} else { |
| 223 |
|
✗ |
if (S_ISDIR(modes[i])) { |
| 224 |
|
✗ |
EnsureDirectory(dest); |
| 225 |
|
|
} else { |
| 226 |
|
✗ |
CreateFile(dest, 0600, false /* ignore_failure */); |
| 227 |
|
|
} |
| 228 |
|
|
LogCvmfs(kLogCvmfs, kLogDebug, "underlay: %s --> %s", source.c_str(), |
| 229 |
|
|
dest.c_str()); |
| 230 |
|
✗ |
const bool rv = BindMount(source, dest); |
| 231 |
|
✗ |
if (!rv) { |
| 232 |
|
✗ |
throw EPublish("cannot bind mount " + source + " --> " + dest + " (" |
| 233 |
|
✗ |
+ StringifyInt(errno) + ")"); |
| 234 |
|
|
} |
| 235 |
|
|
} |
| 236 |
|
|
} |
| 237 |
|
|
|
| 238 |
|
|
// Recurse into the directory trees containing empty directories |
| 239 |
|
|
// CreateUnderlay($SOURCE/cvmfs, $DEST/cvmfs, /atlas.cern.ch) |
| 240 |
|
✗ |
for (unsigned i = 0; i < empty_toplevel_dirs.size(); ++i) { |
| 241 |
|
✗ |
const std::string toplevel_dir = empty_toplevel_dirs[i]; |
| 242 |
|
✗ |
std::vector<std::string> empty_sub_dir; |
| 243 |
|
✗ |
empty_sub_dir.push_back(empty_dirs[i].substr(toplevel_dir.length())); |
| 244 |
|
✗ |
if (!empty_sub_dir[0].empty()) { |
| 245 |
|
✗ |
CreateUnderlay(source_dir + toplevel_dir, |
| 246 |
|
✗ |
dest_dir + toplevel_dir, |
| 247 |
|
|
empty_sub_dir, |
| 248 |
|
|
new_paths); |
| 249 |
|
|
} |
| 250 |
|
|
} |
| 251 |
|
|
} |
| 252 |
|
|
|
| 253 |
|
✗ |
void CmdEnter::WriteCvmfsConfig(const std::string &extra_config) { |
| 254 |
|
✗ |
BashOptionsManager options_manager(new DefaultOptionsTemplateManager(fqrn_)); |
| 255 |
|
✗ |
options_manager.ParseDefault(fqrn_); |
| 256 |
|
✗ |
if (!extra_config.empty()) |
| 257 |
|
✗ |
options_manager.ParsePath(extra_config, false /* external */); |
| 258 |
|
|
|
| 259 |
|
✗ |
options_manager.SetValue("CVMFS_MOUNT_DIR", |
| 260 |
|
✗ |
settings_spool_area_.readonly_mnt()); |
| 261 |
|
✗ |
options_manager.SetValue("CVMFS_AUTO_UPDATE", "no"); |
| 262 |
|
✗ |
options_manager.SetValue("CVMFS_NFS_SOURCE", "no"); |
| 263 |
|
✗ |
options_manager.SetValue("CVMFS_HIDE_MAGIC_XATTRS", "yes"); |
| 264 |
|
✗ |
options_manager.SetValue("CVMFS_SERVER_CACHE_MODE", "yes"); |
| 265 |
|
✗ |
options_manager.SetValue("CVMFS_CLAIM_OWNERSHIP", "yes"); |
| 266 |
|
✗ |
options_manager.SetValue("CVMFS_USYSLOG", settings_spool_area_.client_log()); |
| 267 |
|
✗ |
options_manager.SetValue("CVMFS_RELOAD_SOCKETS", |
| 268 |
|
✗ |
settings_spool_area_.cache_dir()); |
| 269 |
|
✗ |
options_manager.SetValue("CVMFS_WORKSPACE", settings_spool_area_.cache_dir()); |
| 270 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_PRIMARY", "private"); |
| 271 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_TYPE", "posix"); |
| 272 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_BASE", |
| 273 |
|
✗ |
settings_spool_area_.cache_dir()); |
| 274 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_SHARED", "on"); |
| 275 |
|
✗ |
options_manager.SetValue("CVMFS_CACHE_private_QUOTA_LIMIT", "4000"); |
| 276 |
|
|
|
| 277 |
|
✗ |
const bool rv = SafeWriteToFile(options_manager.Dump(), |
| 278 |
|
✗ |
settings_spool_area_.client_config(), |
| 279 |
|
|
kPrivateFileMode); |
| 280 |
|
✗ |
if (!rv) { |
| 281 |
|
✗ |
throw EPublish("cannot write client config to " |
| 282 |
|
✗ |
+ settings_spool_area_.client_config()); |
| 283 |
|
|
} |
| 284 |
|
|
} |
| 285 |
|
|
|
| 286 |
|
|
|
| 287 |
|
✗ |
void CmdEnter::MountCvmfs() { |
| 288 |
|
✗ |
if (FindExecutable(cvmfs2_binary_).empty()) { |
| 289 |
|
✗ |
throw EPublish("cannot find executable " + cvmfs2_binary_, |
| 290 |
|
✗ |
EPublish::kFailMissingDependency); |
| 291 |
|
|
} |
| 292 |
|
|
|
| 293 |
|
✗ |
const int fd_stdout = open(stdout_path_.c_str(), |
| 294 |
|
✗ |
O_CREAT | O_APPEND | O_WRONLY, kPrivateFileMode); |
| 295 |
|
✗ |
const int fd_stderr = open(stderr_path_.c_str(), |
| 296 |
|
✗ |
O_CREAT | O_APPEND | O_WRONLY, kPrivateFileMode); |
| 297 |
|
|
|
| 298 |
|
✗ |
const std::string cvmfs_config = settings_spool_area_.client_config(); |
| 299 |
|
|
|
| 300 |
|
✗ |
std::vector<std::string> cmdline; |
| 301 |
|
✗ |
cmdline.push_back(cvmfs2_binary_); |
| 302 |
|
✗ |
cmdline.push_back("-o"); |
| 303 |
|
✗ |
cmdline.push_back("allow_other,config=" + cvmfs_config); |
| 304 |
|
✗ |
cmdline.push_back(fqrn_); |
| 305 |
|
✗ |
cmdline.push_back(settings_spool_area_.readonly_mnt()); |
| 306 |
|
✗ |
std::set<int> preserved_fds; |
| 307 |
|
✗ |
preserved_fds.insert(0); |
| 308 |
|
✗ |
preserved_fds.insert(1); |
| 309 |
|
✗ |
preserved_fds.insert(2); |
| 310 |
|
✗ |
std::map<int, int> map_fds; |
| 311 |
|
✗ |
map_fds[fd_stdout] = 1; |
| 312 |
|
✗ |
map_fds[fd_stderr] = 2; |
| 313 |
|
|
pid_t pid_child; |
| 314 |
|
✗ |
bool rvb = ManagedExec(cmdline, preserved_fds, map_fds, |
| 315 |
|
|
false /* drop_credentials */, false /* clear_env */, |
| 316 |
|
|
false /* double_fork */, &pid_child); |
| 317 |
|
✗ |
if (!rvb) { |
| 318 |
|
✗ |
close(fd_stdout); |
| 319 |
|
✗ |
close(fd_stderr); |
| 320 |
|
✗ |
throw EPublish("cannot run " + cvmfs2_binary_); |
| 321 |
|
|
} |
| 322 |
|
✗ |
const int exit_code = WaitForChild(pid_child); |
| 323 |
|
✗ |
close(fd_stdout); |
| 324 |
|
✗ |
close(fd_stderr); |
| 325 |
|
✗ |
if (exit_code != 0) { |
| 326 |
|
✗ |
throw EPublish("cannot mount cvmfs read-only branch (" |
| 327 |
|
✗ |
+ StringifyInt(exit_code) + ")\n" + " command: `" |
| 328 |
|
✗ |
+ JoinStrings(cmdline, " ").c_str() + "`"); |
| 329 |
|
|
} |
| 330 |
|
|
|
| 331 |
|
✗ |
rvb = BindMount(settings_spool_area_.readonly_mnt(), |
| 332 |
|
✗ |
rootfs_dir_ + settings_spool_area_.readonly_mnt()); |
| 333 |
|
✗ |
if (!rvb) |
| 334 |
|
✗ |
throw EPublish("cannot map CernVM-FS mount point"); |
| 335 |
|
|
} |
| 336 |
|
|
|
| 337 |
|
|
|
| 338 |
|
✗ |
void CmdEnter::MountOverlayfs() { |
| 339 |
|
✗ |
if (FindExecutable(overlayfs_binary_).empty()) { |
| 340 |
|
✗ |
throw EPublish("cannot find executable " + overlayfs_binary_, |
| 341 |
|
✗ |
EPublish::kFailMissingDependency); |
| 342 |
|
|
} |
| 343 |
|
|
|
| 344 |
|
✗ |
const int fd_stdout = open(stdout_path_.c_str(), |
| 345 |
|
✗ |
O_CREAT | O_APPEND | O_WRONLY, kPrivateFileMode); |
| 346 |
|
✗ |
const int fd_stderr = open(stderr_path_.c_str(), |
| 347 |
|
✗ |
O_CREAT | O_APPEND | O_WRONLY, kPrivateFileMode); |
| 348 |
|
|
|
| 349 |
|
✗ |
std::vector<std::string> cmdline; |
| 350 |
|
✗ |
cmdline.push_back(overlayfs_binary_); |
| 351 |
|
✗ |
cmdline.push_back("-o"); |
| 352 |
|
✗ |
cmdline.push_back(string("lowerdir=") + settings_spool_area_.readonly_mnt() |
| 353 |
|
✗ |
+ ",upperdir=" + settings_spool_area_.scratch_dir() |
| 354 |
|
✗ |
+ ",workdir=" + settings_spool_area_.ovl_work_dir()); |
| 355 |
|
✗ |
cmdline.push_back(rootfs_dir_ + settings_spool_area_.union_mnt()); |
| 356 |
|
✗ |
std::set<int> preserved_fds; |
| 357 |
|
✗ |
preserved_fds.insert(0); |
| 358 |
|
✗ |
preserved_fds.insert(1); |
| 359 |
|
✗ |
preserved_fds.insert(2); |
| 360 |
|
✗ |
std::map<int, int> map_fds; |
| 361 |
|
✗ |
map_fds[fd_stdout] = 1; |
| 362 |
|
✗ |
map_fds[fd_stderr] = 2; |
| 363 |
|
|
pid_t pid_child; |
| 364 |
|
✗ |
const bool rvb = ManagedExec( |
| 365 |
|
|
cmdline, preserved_fds, map_fds, true /* drop_credentials */, |
| 366 |
|
|
false /* clear_env */, false /* double_fork */, &pid_child); |
| 367 |
|
✗ |
if (!rvb) { |
| 368 |
|
✗ |
close(fd_stdout); |
| 369 |
|
✗ |
close(fd_stderr); |
| 370 |
|
✗ |
throw EPublish("cannot run " + overlayfs_binary_); |
| 371 |
|
|
} |
| 372 |
|
✗ |
const int exit_code = WaitForChild(pid_child); |
| 373 |
|
✗ |
close(fd_stdout); |
| 374 |
|
✗ |
close(fd_stderr); |
| 375 |
|
✗ |
if (exit_code != 0) { |
| 376 |
|
✗ |
throw EPublish("cannot mount overlay file system (" |
| 377 |
|
✗ |
+ StringifyInt(exit_code) + ")\n" + " command: `" |
| 378 |
|
✗ |
+ JoinStrings(cmdline, " ").c_str() + "`"); |
| 379 |
|
|
} |
| 380 |
|
|
} |
| 381 |
|
|
|
| 382 |
|
|
|
| 383 |
|
✗ |
std::string CmdEnter::GetCvmfsXattr(const std::string &name) { |
| 384 |
|
✗ |
std::string xattr; |
| 385 |
|
✗ |
const bool rvb = platform_getxattr(settings_spool_area_.readonly_mnt(), |
| 386 |
|
✗ |
std::string("user.") + name, &xattr); |
| 387 |
|
✗ |
if (!rvb) { |
| 388 |
|
✗ |
throw EPublish("cannot get extrended attribute " + name + " from " |
| 389 |
|
✗ |
+ settings_spool_area_.readonly_mnt()); |
| 390 |
|
|
} |
| 391 |
|
✗ |
return xattr; |
| 392 |
|
|
} |
| 393 |
|
|
|
| 394 |
|
|
|
| 395 |
|
✗ |
void CmdEnter::CleanupSession(bool keep_logs, |
| 396 |
|
|
const std::vector<std::string> &new_paths) { |
| 397 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
| 398 |
|
|
"Cleaning out session directory... "); |
| 399 |
|
|
|
| 400 |
|
✗ |
std::string pid_xattr; |
| 401 |
|
✗ |
bool rvb = platform_getxattr(settings_spool_area_.readonly_mnt(), "user.pid", |
| 402 |
|
|
&pid_xattr); |
| 403 |
|
✗ |
if (!rvb) |
| 404 |
|
✗ |
throw EPublish("cannot find CernVM-FS process"); |
| 405 |
|
✗ |
const pid_t pid_cvmfs = String2Uint64(GetCvmfsXattr("pid")); |
| 406 |
|
|
|
| 407 |
|
✗ |
const std::string union_mnt = rootfs_dir_ + settings_spool_area_.union_mnt(); |
| 408 |
|
✗ |
rvb = platform_umount_lazy(union_mnt.c_str()); |
| 409 |
|
✗ |
if (!rvb) |
| 410 |
|
✗ |
throw EPublish("cannot unmount overlayfs on " + union_mnt); |
| 411 |
|
✗ |
rvb = platform_umount_lazy( |
| 412 |
|
✗ |
(rootfs_dir_ + settings_spool_area_.readonly_mnt()).c_str()); |
| 413 |
|
✗ |
if (!rvb) { |
| 414 |
|
✗ |
throw EPublish("cannot unmount mapped CernVM-FS on " + rootfs_dir_ |
| 415 |
|
✗ |
+ settings_spool_area_.readonly_mnt()); |
| 416 |
|
|
} |
| 417 |
|
✗ |
rvb = platform_umount_lazy(settings_spool_area_.readonly_mnt().c_str()); |
| 418 |
|
✗ |
if (!rvb) { |
| 419 |
|
✗ |
throw EPublish("cannot unmount CernVM-FS on " |
| 420 |
|
✗ |
+ settings_spool_area_.readonly_mnt()); |
| 421 |
|
|
} |
| 422 |
|
|
|
| 423 |
|
✗ |
BackoffThrottle backoff; |
| 424 |
|
✗ |
while (ProcessExists(pid_cvmfs)) { |
| 425 |
|
✗ |
backoff.Throttle(); |
| 426 |
|
|
} |
| 427 |
|
|
|
| 428 |
|
✗ |
RemoveSingle(settings_spool_area_.readonly_mnt()); |
| 429 |
|
✗ |
RemoveSingle(settings_spool_area_.client_config()); |
| 430 |
|
✗ |
RemoveSingle(env_conf_); |
| 431 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.ovl_work_dir()); |
| 432 |
|
✗ |
if (!rvb) |
| 433 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.ovl_work_dir()); |
| 434 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.scratch_base()); |
| 435 |
|
✗ |
if (!rvb) |
| 436 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.scratch_base()); |
| 437 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.cache_dir()); |
| 438 |
|
✗ |
if (!rvb) |
| 439 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.cache_dir()); |
| 440 |
|
✗ |
RemoveUnderlay(rootfs_dir_, new_paths); |
| 441 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.tmp_dir()); |
| 442 |
|
✗ |
if (!rvb) |
| 443 |
|
✗ |
throw EPublish("cannot remove " + settings_spool_area_.tmp_dir()); |
| 444 |
|
|
|
| 445 |
|
✗ |
if (keep_logs) { |
| 446 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "[logs available in %s]", |
| 447 |
|
|
settings_spool_area_.log_dir().c_str()); |
| 448 |
|
✗ |
return; |
| 449 |
|
|
} |
| 450 |
|
|
|
| 451 |
|
✗ |
rvb = RemoveTree(settings_spool_area_.log_dir()); |
| 452 |
|
✗ |
RemoveSingle(session_dir_ + "/session_pid"); |
| 453 |
|
✗ |
RemoveSingle(session_dir_ + "/" + fqrn_ + "/server.conf"); |
| 454 |
|
✗ |
RemoveSingle(session_dir_ + "/" + fqrn_); |
| 455 |
|
✗ |
RemoveSingle(session_dir_ + "/session_token"); |
| 456 |
|
✗ |
RemoveSingle(session_dir_ + "/in_transaction.lock"); |
| 457 |
|
✗ |
RemoveSingle(session_dir_ + "/shellaction.marker"); |
| 458 |
|
✗ |
RemoveSingle(session_dir_); |
| 459 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "[done]"); |
| 460 |
|
|
} |
| 461 |
|
|
|
| 462 |
|
|
|
| 463 |
|
✗ |
int CmdEnter::Main(const Options &options) { |
| 464 |
|
✗ |
fqrn_ = options.plain_args()[0].value_str; |
| 465 |
|
✗ |
const sanitizer::RepositorySanitizer sanitizer; |
| 466 |
|
✗ |
if (!sanitizer.IsValid(fqrn_)) { |
| 467 |
|
✗ |
throw EPublish("malformed repository name: " + fqrn_); |
| 468 |
|
|
} |
| 469 |
|
|
|
| 470 |
|
|
bool rvb; |
| 471 |
|
|
|
| 472 |
|
|
// We cannot have any capabilities or else we are not allowed to write |
| 473 |
|
|
// to /proc/self/setgroups and /proc/self/[u|g]id_map when creating a user |
| 474 |
|
|
// namespace |
| 475 |
|
✗ |
Env::DropCapabilities(); |
| 476 |
|
|
|
| 477 |
|
✗ |
if (options.Has("cvmfs2")) { |
| 478 |
|
✗ |
cvmfs2_binary_ = options.GetString("cvmfs2"); |
| 479 |
|
|
// Lucky guess: library in the same directory than the binary, |
| 480 |
|
|
// but don't overwrite an explicit setting |
| 481 |
|
✗ |
setenv("CVMFS_LIBRARY_PATH", GetParentPath(cvmfs2_binary_).c_str(), 0); |
| 482 |
|
|
} |
| 483 |
|
|
|
| 484 |
|
|
// Prepare the session directory |
| 485 |
|
✗ |
const std::string workspace = GetHomeDirectory() + "/.cvmfs/" + fqrn_; |
| 486 |
|
✗ |
EnsureDirectory(workspace); |
| 487 |
|
✗ |
session_dir_ = CreateTempDir(workspace + "/session"); |
| 488 |
|
✗ |
if (session_dir_.empty()) |
| 489 |
|
✗ |
throw EPublish("cannot create session directory in " + workspace); |
| 490 |
|
✗ |
settings_spool_area_.SetUnionMount(std::string("/cvmfs/") + fqrn_); |
| 491 |
|
✗ |
settings_spool_area_.SetSpoolArea(session_dir_); |
| 492 |
|
✗ |
settings_spool_area_.EnsureDirectories(); |
| 493 |
|
✗ |
rootfs_dir_ = session_dir_ + "/rootfs"; |
| 494 |
|
✗ |
EnsureDirectory(rootfs_dir_); |
| 495 |
|
✗ |
stdout_path_ = settings_spool_area_.log_dir() + "/stdout.log"; |
| 496 |
|
✗ |
stderr_path_ = settings_spool_area_.log_dir() + "/stderr.log"; |
| 497 |
|
|
|
| 498 |
|
|
// Save process context information before switching namespaces |
| 499 |
|
✗ |
const string cwd = GetCurrentWorkingDirectory(); |
| 500 |
|
✗ |
const uid_t uid = geteuid(); |
| 501 |
|
✗ |
const gid_t gid = getegid(); |
| 502 |
|
|
|
| 503 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 504 |
|
|
"*** NOTE: This is currently an experimental CernVM-FS feature\n"); |
| 505 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 506 |
|
|
"Entering ephemeral writable shell for %s... ", |
| 507 |
|
|
settings_spool_area_.union_mnt().c_str()); |
| 508 |
|
|
|
| 509 |
|
|
// Create root user namespace and rootfs underlay |
| 510 |
|
✗ |
const AnchorPid anchor_pid = EnterRootContainer(); |
| 511 |
|
✗ |
std::vector<std::string> empty_dirs; |
| 512 |
|
✗ |
empty_dirs.push_back(settings_spool_area_.union_mnt()); |
| 513 |
|
✗ |
empty_dirs.push_back("/proc"); |
| 514 |
|
✗ |
std::vector<std::string> new_paths; |
| 515 |
|
✗ |
CreateUnderlay("", rootfs_dir_, empty_dirs, &new_paths); |
| 516 |
|
|
// The .cvmfsenter marker file helps when attaching to the mount namespace |
| 517 |
|
|
// to distinguish it from the system root |
| 518 |
|
✗ |
rvb = SymlinkForced(session_dir_, rootfs_dir_ + "/.cvmfsenter"); |
| 519 |
|
✗ |
if (!rvb) |
| 520 |
|
✗ |
throw EPublish("cannot create marker file " + rootfs_dir_ + "/.cvmfsenter"); |
| 521 |
|
✗ |
new_paths.push_back(rootfs_dir_ + "/.cvmfsenter"); |
| 522 |
|
✗ |
rvb = ProcMount(rootfs_dir_ + "/proc"); |
| 523 |
|
✗ |
if (!rvb) |
| 524 |
|
✗ |
throw EPublish("cannot mount " + rootfs_dir_ + "/proc"); |
| 525 |
|
✗ |
setenv("CVMFS_ENTER_SESSION_DIR", session_dir_.c_str(), 1 /* overwrite */); |
| 526 |
|
|
|
| 527 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
| 528 |
|
|
"Mounting CernVM-FS read-only layer... "); |
| 529 |
|
|
// We mount in a sub process in order to avoid tainting the main process |
| 530 |
|
|
// with the CVMFS_* environment variables |
| 531 |
|
✗ |
pid_t pid = fork(); |
| 532 |
|
✗ |
if (pid < 0) |
| 533 |
|
✗ |
throw EPublish("cannot fork"); |
| 534 |
|
✗ |
if (pid == 0) { |
| 535 |
|
✗ |
WriteCvmfsConfig(options.GetStringDefault("cvmfs-config", "")); |
| 536 |
|
✗ |
MountCvmfs(); |
| 537 |
|
✗ |
_exit(0); |
| 538 |
|
|
} |
| 539 |
|
✗ |
int exit_code = WaitForChild(pid); |
| 540 |
|
✗ |
if (exit_code != 0) |
| 541 |
|
✗ |
_exit(exit_code); |
| 542 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
| 543 |
|
|
|
| 544 |
|
✗ |
env_conf_ = session_dir_ + "/env.conf"; |
| 545 |
|
✗ |
rvb = SafeWriteToFile(std::string("CVMFS_FQRN=") + fqrn_ + "\n", env_conf_, |
| 546 |
|
|
kPrivateFileMode); |
| 547 |
|
✗ |
if (!rvb) |
| 548 |
|
✗ |
throw EPublish("cannot create session environment file"); |
| 549 |
|
|
|
| 550 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, |
| 551 |
|
|
"Mounting union file system... "); |
| 552 |
|
✗ |
MountOverlayfs(); |
| 553 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
| 554 |
|
|
|
| 555 |
|
|
// Fork the inner process, the outer one is used later for cleanup |
| 556 |
|
✗ |
pid = fork(); |
| 557 |
|
✗ |
if (pid < 0) |
| 558 |
|
✗ |
throw EPublish("cannot create subshell"); |
| 559 |
|
|
|
| 560 |
|
✗ |
if (pid == 0) { |
| 561 |
|
✗ |
if (!options.Has("root")) { |
| 562 |
|
✗ |
const NamespaceFailures failure = CreateUserNamespace(uid, gid); |
| 563 |
|
✗ |
if (failure != kFailNsOk) { |
| 564 |
|
✗ |
throw publish::EPublish( |
| 565 |
|
✗ |
"cannot create user namespace for " + StringifyInt(uid) + ":" |
| 566 |
|
✗ |
+ StringifyInt(gid) + " (" + StringifyInt(failure) + " / " |
| 567 |
|
✗ |
+ StringifyInt(errno) + ") [euid=" + StringifyInt(geteuid()) |
| 568 |
|
✗ |
+ ", egid=" + StringifyInt(getegid()) + "]"); |
| 569 |
|
|
} |
| 570 |
|
|
} |
| 571 |
|
|
|
| 572 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "Switching to %s... ", |
| 573 |
|
|
rootfs_dir_.c_str()); |
| 574 |
|
✗ |
int rvi = chroot(rootfs_dir_.c_str()); |
| 575 |
|
✗ |
if (rvi != 0) |
| 576 |
|
✗ |
throw EPublish("cannot chroot to " + rootfs_dir_); |
| 577 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "done"); |
| 578 |
|
|
|
| 579 |
|
✗ |
SettingsBuilder builder; |
| 580 |
|
✗ |
UniquePtr<Publisher> publisher; |
| 581 |
|
|
|
| 582 |
|
✗ |
if (options.Has("transaction")) { |
| 583 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 584 |
|
|
"Starting a transaction inside the enter shell"); |
| 585 |
|
|
|
| 586 |
|
✗ |
if (options.Has("repo-config")) { |
| 587 |
|
✗ |
repo_config_ = options.GetString("repo-config"); |
| 588 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 589 |
|
|
"Parsing the external configuration for the repository at %s", |
| 590 |
|
|
repo_config_.c_str()); |
| 591 |
|
|
|
| 592 |
|
✗ |
std::string config; |
| 593 |
|
✗ |
const std::string config_file = repo_config_ + "/" + fqrn_ |
| 594 |
|
✗ |
+ "/server.conf"; |
| 595 |
|
✗ |
const std::string folderpath = session_dir_ + "/" + fqrn_; |
| 596 |
|
✗ |
MkdirDeep(folderpath.c_str(), 0700, true /* veryfy_writable */); |
| 597 |
|
|
|
| 598 |
|
✗ |
const std::string session_config_file = folderpath + "/server.conf"; |
| 599 |
|
✗ |
const int fd_config = open(config_file.c_str(), O_RDONLY); |
| 600 |
|
✗ |
SafeReadToString(fd_config, &config); |
| 601 |
|
✗ |
SafeWriteToFile(config, session_config_file, 0600); |
| 602 |
|
|
|
| 603 |
|
✗ |
builder.SetConfigPath(session_dir_); |
| 604 |
|
|
} |
| 605 |
|
|
|
| 606 |
|
✗ |
SettingsPublisher *settings_publisher = builder.CreateSettingsPublisher( |
| 607 |
|
✗ |
fqrn_, false); |
| 608 |
|
✗ |
publisher = new Publisher(*settings_publisher); |
| 609 |
|
✗ |
publisher->Transaction(); |
| 610 |
|
|
} |
| 611 |
|
|
|
| 612 |
|
|
// May fail if the working directory was invalid to begin with |
| 613 |
|
✗ |
rvi = chdir(cwd.c_str()); |
| 614 |
|
✗ |
if (rvi != 0) { |
| 615 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Warning: cannot chdir to %s", |
| 616 |
|
|
cwd.c_str()); |
| 617 |
|
|
} |
| 618 |
|
|
|
| 619 |
|
✗ |
LogCvmfs( |
| 620 |
|
|
kLogCvmfs, kLogStdout, |
| 621 |
|
|
"\n" |
| 622 |
|
|
"You can attach to this shell from another another terminal with\n"); |
| 623 |
|
✗ |
if (options.Has("root")) { |
| 624 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 625 |
|
|
" nsenter --preserve-credentials -U -p -m -t %u\n" |
| 626 |
|
|
" chroot %s\n", |
| 627 |
|
|
anchor_pid.init_pid, rootfs_dir_.c_str()); |
| 628 |
|
|
} else { |
| 629 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
| 630 |
|
|
" nsenter --preserve-credentials -U -m -t %u\n" |
| 631 |
|
|
" chroot %s\n" |
| 632 |
|
|
" nsenter --preserve-credentials -S %u -G %u -U -p -t 1\n", |
| 633 |
|
|
anchor_pid.parent_pid, rootfs_dir_.c_str(), uid, gid); |
| 634 |
|
|
} |
| 635 |
|
|
|
| 636 |
|
✗ |
std::vector<std::string> cmdline; |
| 637 |
|
|
// options.plain_args()[0] is the repository name |
| 638 |
|
✗ |
for (unsigned i = 1; i < options.plain_args().size(); ++i) { |
| 639 |
|
✗ |
cmdline.push_back(options.plain_args()[i].value_str); |
| 640 |
|
|
} |
| 641 |
|
✗ |
if (cmdline.empty()) |
| 642 |
|
✗ |
cmdline.push_back(GetShell()); |
| 643 |
|
✗ |
std::set<int> preserved_fds; |
| 644 |
|
✗ |
preserved_fds.insert(0); |
| 645 |
|
✗ |
preserved_fds.insert(1); |
| 646 |
|
✗ |
preserved_fds.insert(2); |
| 647 |
|
✗ |
pid_t pid_child = 0; |
| 648 |
|
✗ |
rvb = ManagedExec(cmdline, preserved_fds, std::map<int, int>(), |
| 649 |
|
|
false /* drop_credentials */, false /* clear_env */, |
| 650 |
|
|
false /* double_fork */, &pid_child); |
| 651 |
|
✗ |
const std::string s = StringifyInt(pid_child); |
| 652 |
|
✗ |
SafeWriteToFile(s, session_dir_ + "/session_pid", 0600); |
| 653 |
|
|
|
| 654 |
|
✗ |
std::vector<int> sigs; |
| 655 |
|
✗ |
sigs.push_back(SIGUSR1); |
| 656 |
|
✗ |
exit_code = WaitForChild(pid_child, sigs); |
| 657 |
|
|
|
| 658 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Closing CernVM-FS shell..."); |
| 659 |
|
✗ |
if (options.Has("transaction") |
| 660 |
|
✗ |
&& !FileExists(session_dir_ + "/shellaction.marker")) { |
| 661 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Closing current transaction..."); |
| 662 |
|
✗ |
publisher->session()->SetKeepAlive(false); |
| 663 |
|
|
} |
| 664 |
|
|
|
| 665 |
|
✗ |
return exit_code; |
| 666 |
|
|
} |
| 667 |
|
|
|
| 668 |
|
✗ |
exit_code = WaitForChild(pid); |
| 669 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "Leaving CernVM-FS shell..."); |
| 670 |
|
|
|
| 671 |
|
✗ |
if (!options.Has("keep-session")) |
| 672 |
|
✗ |
CleanupSession(options.Has("keep-logs"), new_paths); |
| 673 |
|
|
|
| 674 |
|
✗ |
return exit_code; |
| 675 |
|
|
} |
| 676 |
|
|
|
| 677 |
|
|
} // namespace publish |
| 678 |
|
|
|