GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/publish/cmd_enter.cc
Date: 2025-06-22 02:36:02
Exec Total Coverage
Lines: 0 409 0.0%
Branches: 0 1166 0.0%

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 =
278 SafeWriteToFile(options_manager.Dump(),
279 settings_spool_area_.client_config(), 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 =
365 ManagedExec(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 =
594 repo_config_ + "/" + fqrn_ + "/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