GCC Code Coverage Report


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