GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/swissknife_info.cc
Date: 2026-05-19 11:45:12
Exec Total Coverage
Lines: 0 138 0.0%
Branches: 0 140 0.0%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 *
4 * This command reads the content of a .cvmfspublished file and exposes it
5 * to the user.
6 */
7
8 #include "swissknife_info.h"
9
10 #include <string>
11
12 #include "crypto/hash.h"
13 #include "manifest.h"
14 #include "network/download.h"
15 #include "network/sink_mem.h"
16 #include "util/logging.h"
17 #include "util/posix.h"
18 #include "util/string.h"
19
20 using namespace std; // NOLINT
21
22 namespace swissknife {
23
24 /**
25 * Checks if the given path looks like a remote path
26 */
27 static bool IsRemote(const string &repository) {
28 return HasPrefix(repository, "http://", false)
29 || HasPrefix(repository, "https://", false);
30 }
31
32 /**
33 * Checks for existence of a file either locally or via HTTP head
34 */
35 bool CommandInfo::Exists(const string &repository, const string &file) const {
36 if (IsRemote(repository)) {
37 const string url = repository + "/" + file;
38 download::JobInfo head(&url, false);
39 return download_manager()->Fetch(&head) == download::kFailOk;
40 } else {
41 return FileExists(file);
42 }
43 }
44
45 ParameterList CommandInfo::GetParams() const {
46 swissknife::ParameterList r;
47 r.push_back(Parameter::Mandatory('r', "repository directory / url"));
48 r.push_back(Parameter::Optional('u', "repository mount point"));
49 r.push_back(Parameter::Optional('l', "log level (0-4, default: 2)"));
50 r.push_back(Parameter::Optional('@', "proxy url"));
51 r.push_back(Parameter::Switch('c', "show root catalog hash"));
52 r.push_back(Parameter::Switch('C', "show mounted root catalog hash"));
53 r.push_back(Parameter::Switch('n', "show fully qualified repository name"));
54 r.push_back(Parameter::Switch('t', "show time stamp"));
55 r.push_back(Parameter::Switch('m',
56 "check if repository is marked as "
57 "replication master copy"));
58 r.push_back(Parameter::Switch('v', "repository revision number"));
59 r.push_back(Parameter::Switch('g',
60 "check if repository is garbage "
61 "collectable"));
62 r.push_back(Parameter::Switch('o',
63 "check if the repository maintains a "
64 "reference log file"));
65 r.push_back(Parameter::Switch('h', "print results in human readable form"));
66 r.push_back(Parameter::Switch('L', "follow HTTP redirects"));
67 r.push_back(Parameter::Switch('X',
68 "show whether external data is supported "
69 "in the root catalog."));
70 r.push_back(Parameter::Switch('M', "print repository meta info."));
71 r.push_back(Parameter::Switch('R', "print raw manifest."));
72 r.push_back(Parameter::Switch('e', "check if the repository is empty"));
73 return r;
74 }
75
76 int swissknife::CommandInfo::Main(const swissknife::ArgumentList &args) {
77 if (args.find('l') != args.end()) {
78 const unsigned log_level = kLogLevel0
79 << String2Uint64(*args.find('l')->second);
80 if (log_level > kLogNone) {
81 LogCvmfs(kLogCvmfs, kLogStderr, "invalid log level");
82 return 1;
83 }
84 SetLogVerbosity(static_cast<LogLevels>(log_level));
85 }
86 const string mount_point = (args.find('u') != args.end())
87 ? *args.find('u')->second
88 : "";
89 const string repository = MakeCanonicalPath(*args.find('r')->second);
90
91 // sanity check
92 if (args.count('C') > 0 && mount_point.empty()) {
93 LogCvmfs(kLogCvmfs, kLogStderr, "need a CernVM-FS mountpoint (-u) for -C");
94 return 1;
95 }
96
97 if (IsRemote(repository)) {
98 const bool follow_redirects = args.count('L') > 0;
99 const string proxy = (args.find('@') != args.end())
100 ? *args.find('@')->second
101 : "";
102 if (!this->InitDownloadManager(follow_redirects, proxy)) {
103 return 1;
104 }
105 }
106
107 // Check if we should be human readable
108 const bool human_readable = (args.count('h') > 0);
109
110 if (args.count('e') > 0) {
111 const string manifest_path = IsRemote(repository)
112 ? ".cvmfspublished"
113 : repository + "/.cvmfspublished";
114 const bool is_empty = !Exists(repository, manifest_path);
115 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
116 (human_readable) ? "Empty Repository: " : "",
117 StringifyBool(is_empty).c_str());
118 if (is_empty)
119 return 0;
120 }
121
122 // Load manifest file
123 // Repository can be HTTP address or on local file system
124 // TODO(jblomer): do this using Manifest::Fetch
125 // currently this is not possible, since Manifest::Fetch asks for the
126 // repository name... Which we want to figure out with the tool at hand.
127 // Possible Fix: Allow for a Manifest::Fetch with an empty name.
128 UniquePtr<manifest::Manifest> manifest;
129 if (IsRemote(repository)) {
130 const string url = repository + "/.cvmfspublished";
131 cvmfs::MemSink manifest_memsink;
132 download::JobInfo download_manifest(&url, false, false, NULL,
133 &manifest_memsink);
134 const download::Failures retval = download_manager()->Fetch(
135 &download_manifest);
136 if (retval != download::kFailOk) {
137 LogCvmfs(kLogCvmfs, kLogStderr, "failed to download manifest (%d - %s)",
138 retval, download::Code2Ascii(retval));
139 return 1;
140 }
141
142 manifest = manifest::Manifest::LoadMem(manifest_memsink.data(),
143 manifest_memsink.pos());
144 } else {
145 if (chdir(repository.c_str()) != 0) {
146 LogCvmfs(kLogCvmfs, kLogStderr, "failed to switch to directory %s",
147 repository.c_str());
148 return 1;
149 }
150 manifest = manifest::Manifest::LoadFile(".cvmfspublished");
151 }
152
153 if (!manifest.IsValid()) {
154 LogCvmfs(kLogCvmfs, kLogStderr, "failed to load repository manifest");
155 return 1;
156 }
157
158 // Validate Manifest
159 const string certificate_path = "data/" + manifest->certificate().MakePath();
160 if (!Exists(repository, certificate_path)) {
161 LogCvmfs(kLogCvmfs, kLogStderr, "failed to find certificate (%s)",
162 certificate_path.c_str());
163 return 1;
164 }
165
166 // Get information from the mount point
167 if (args.count('C') > 0) {
168 assert(!mount_point.empty());
169 const std::string root_hash_xattr = "user.root_hash";
170 std::string root_hash;
171 const bool success = platform_getxattr(mount_point, root_hash_xattr,
172 &root_hash);
173 if (!success) {
174 LogCvmfs(kLogCvmfs, kLogStderr,
175 "failed to retrieve extended attribute "
176 " '%s' from '%s' (errno: %d)",
177 root_hash_xattr.c_str(), mount_point.c_str(), errno);
178 return 1;
179 }
180 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
181 (human_readable) ? "Mounted Root Hash: " : "",
182 root_hash.c_str());
183 }
184
185 // Get information about external data
186 if (args.count('X') > 0) {
187 assert(!mount_point.empty());
188 const std::string external_data_xattr = "user.external_data";
189 std::string external_data;
190 const bool success = platform_getxattr(mount_point, external_data_xattr,
191 &external_data);
192 if (!success) {
193 LogCvmfs(kLogCvmfs, kLogStderr,
194 "failed to retrieve extended attribute "
195 " '%s' from '%s' (errno: %d)",
196 external_data_xattr.c_str(), mount_point.c_str(), errno);
197 return 1;
198 }
199 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
200 (human_readable) ? "External data enabled: " : "",
201 external_data.c_str());
202 }
203
204 // Get information from the Manifest
205 if (args.count('c') > 0) {
206 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
207 (human_readable) ? "Root Catalog Hash: " : "",
208 manifest->catalog_hash().ToString().c_str());
209 }
210
211 if (args.count('n') > 0) {
212 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
213 (human_readable) ? "Fully Qualified Repository Name: " : "",
214 manifest->repository_name().c_str());
215 }
216
217 if (args.count('t') > 0) {
218 LogCvmfs(kLogCvmfs, kLogStdout, "%s%lu",
219 (human_readable) ? "Time Stamp: " : "",
220 manifest->publish_timestamp());
221 }
222
223 if (args.count('m') > 0) {
224 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
225 (human_readable) ? "Replication Master Copy: " : "",
226 (Exists(repository, ".cvmfs_master_replica")) ? "true" : "false");
227 }
228
229 if (args.count('v') > 0) {
230 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
231 (human_readable) ? "Revision: " : "",
232 (StringifyInt(manifest->revision())).c_str());
233 }
234
235 if (args.count('g') > 0) {
236 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
237 (human_readable) ? "Garbage Collectable: " : "",
238 (StringifyBool(manifest->garbage_collectable())).c_str());
239 }
240
241 if (args.count('o') > 0) {
242 LogCvmfs(kLogCvmfs, kLogStdout, "%s%s",
243 (human_readable) ? "Maintains Reference Log: " : "",
244 (Exists(repository, ".cvmfsreflog")) ? "true" : "false");
245 }
246
247 if (args.count('M') > 0) {
248 const shash::Any meta_info(manifest->meta_info());
249 if (meta_info.IsNull()) {
250 if (human_readable)
251 LogCvmfs(kLogCvmfs, kLogStderr, "no meta info available");
252 return 0;
253 }
254 const string url = repository + "/data/" + meta_info.MakePath();
255 cvmfs::MemSink metainfo_memsink;
256 download::JobInfo download_metainfo(&url, true, false, &meta_info,
257 &metainfo_memsink);
258 const download::Failures retval = download_manager()->Fetch(
259 &download_metainfo);
260 if (retval != download::kFailOk) {
261 if (human_readable)
262 LogCvmfs(kLogCvmfs, kLogStderr,
263 "failed to download meta info (%d - %s)", retval,
264 download::Code2Ascii(retval));
265 return 1;
266 }
267 const string info(reinterpret_cast<char *>(metainfo_memsink.data()),
268 metainfo_memsink.pos());
269 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s", info.c_str());
270 }
271
272 if (args.count('R') > 0) {
273 LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s",
274 manifest->ExportString().c_str());
275 }
276
277 return 0;
278 }
279
280 //------------------------------------------------------------------------------
281
282 int CommandVersion::Main(const ArgumentList &args) {
283 LogCvmfs(kLogCvmfs, kLogStdout, "%s", CVMFS_VERSION);
284 return 0;
285 }
286
287 } // namespace swissknife
288