XRootD
XrdOssArcCompose.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d O s s A r c C o m p o s e . c c */
4 /* */
5 /* (c) 2025 by the Board of Trustees of the Leland Stanford, Jr., University */
6 /* All Rights Reserved */
7 /* Produced by Andrew Hanushevsky for Stanford University under contract */
8 /* DE-AC02-76-SFO0515 with the Department of Energy */
9 /* */
10 /* This file is part of the XRootD software suite. */
11 /* */
12 /* XRootD is free software: you can redistribute it and/or modify it under */
13 /* the terms of the GNU Lesser General Public License as published by the */
14 /* Free Software Foundation, either version 3 of the License, or (at your */
15 /* option) any later version. */
16 /* */
17 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
18 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
19 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
20 /* License for more details. */
21 /* */
22 /* You should have received a copy of the GNU Lesser General Public License */
23 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
24 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
25 /* */
26 /* The copyright holder's institutional names and contributor's names may not */
27 /* be used to endorse or promote products derived from this software without */
28 /* specific prior written permission of the institution or contributor. */
29 /******************************************************************************/
30 
31 #include <errno.h>
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include <sys/stat.h>
36 
40 
41 #include "XrdOuc/XrdOucECMsg.hh"
42 #include "XrdOuc/XrdOucEnv.hh"
43 #include "XrdOuc/XrdOucProg.hh"
44 #include "XrdOuc/XrdOucStream.hh"
45 
46 #include "XrdSys/XrdSysE2T.hh"
47 
48 #ifndef ENOANO
49 #define ENOANO 555
50 #endif
51 
52 /******************************************************************************/
53 /* S t a t i c O b j e c t s */
54 /******************************************************************************/
55 
56 namespace XrdOssArcGlobals
57 {
58 extern XrdOssArcConfig Config;
59 extern XrdSysError Elog;
60 extern thread_local XrdOucECMsg ecMsg;
61 }
62 using namespace XrdOssArcGlobals;
63 
64 /******************************************************************************/
65 
66 int XrdOssArcCompose::minLenDSN = 4;
67 int XrdOssArcCompose::minLenFN = 4;
68 
69 /******************************************************************************/
70 /* C o n s t r u c t o r */
71 /******************************************************************************/
72 
74  int& rc, bool isW, bool optfn)
75 {
76  TraceInfo("Compose",0);
77 
78 // By conventioon, the env["ossarc.fn"] holds the file did and the path is
79 // the dataset did both represented as paths. We only support two path
80 // prefixes: 1) the archive prefix, and 2) the backup prefix. These are
81 // removed but we track which one it was as they have different semantics.
82 //
83 
84 // Determine the path type being referenced
85 //
86  if (!strncmp(Config.arcvPathLFN, path, Config.arcvPathLEN))
87  {didType = isARC;
88  path += Config.arcvPathLEN;
89  }
90  else if (!strncmp(Config.arcvPathLFN, path, Config.arcvPathLEN-1)
91  && strlen(path) < (size_t)Config.arcvPathLEN)
92  {didType = isARC;
93  path += Config.arcvPathLEN-1;
94  }
95  else if (!strncmp(Config.bkupPathLFN, path, Config.bkupPathLEN))
96  {didType = isBKP;
97  path += Config.bkupPathLEN;
98  optfn = false; // ossarc.fn is required for /backup
99  }
100  else {rc = EDOM; return;} // This will forward the request
101 
102 // Get the dataset name and if there is no ENV, we can return as this is
103 // only a construction for datasets.
104 //
105  if ((rc = getDSN(path))) return;
106  if (!env) {rc = 0; return;}
107 
108 // Prepare for full construction (note: we know we have an env pointer)
109 //
110  const char* theFN = 0;
111  bool fl2arc = false;
112  bool fscpDS = false;
113 
114 // This is our domain and that domain is strictly read/only
115 //
116  if (isW) {rc = EROFS; return;}
117 
118 // Make sure we have an env file name to work with. This must come from the CGI
119 // if /backup and from either the CGI path for /archive. We support optional
120 // path specification to allow recursive copies of all dataset archive files.
121 // Noe that we ignore the CGI if specified via the path.
122 //
123  if (didType == isARC && isArcFile(path))
124  {const char* fn = rindex(path, '/');
125  if (fn && fn != path)
126  {theFN = fn + 1;
127  dsName.erase(dsName.find_last_of("/"));
128  }
129  }
130 
131  if (!theFN && !(theFN = env->Get("ossarc.fn")))
132  {if (optfn) rc = 0;
133  else {rc = EINVAL;
134  ecMsg.Msg("Compose","CGI ossarc.fn=<target_fname> not specified");
135  }
136  return;
137  }
138 
139 // If the archive if the target then env filename may refer to a specific
140 // archive or be a file that is to be used to determine the target archive.
141 // Otherwise, it should refer to a single file backups so an arc file suffix
142 // is disallowed as it makes to sense.
143 //
144  if (isArcFile(theFN))
145  {if (didType == isBKP)
146  {rc = EINVAL;
147  ecMsg.Msg("Compose","Backup filename cannot refer to an archive");
148  return;
149  }
150  arName = theFN;
151  } else fl2arc = true;
152 
153 
154 // Pick apart the filename if this is a backup reference or an indirect
155 // archive reference as we will need the scope and file.
156 //
157  if ((didType == isBKP) || fl2arc)
158  {const char* colon = index(theFN, ':');
159  if (!colon)
160  {flScope = dsScope;
161  flName = theFN;
162  fscpDS = true; // For tracing purposes
163  } else {
164  if (*theFN == ':')
165  {rc = EINVAL;
166  ecMsg.Msg("Compose","File scope not specified though implied");
167  return;
168  }
169  flScope.assign(theFN, colon - theFN);
170  flName = colon+1;
171  }
172  if ((int)flName.length() < minLenDSN)
173  {rc = EINVAL;
174  ecMsg.Msg("Compose", "Dataset name is too short");
175  return;
176  }
177  }
178 
179 // Do some debugging
180 //
181  const char* atName[] = {"Archive", "Backup"};
182 
183  DEBUG("Type="<<atName[didType]<<" f2a="<<fl2arc<<" fscpDF="<<fscpDS
184  <<" dsScope="<<dsScope<<" dsName="<<dsName
185  <<" flScope="<<flScope<<" flName="<<flName<<" arName="<<arName);
186 
187 // Set the archive name if we need to
188 //
189  rc = (fl2arc ? SetarName() : 0);
190 }
191 
192 /******************************************************************************/
193 /* A r c M e m b e r */
194 /******************************************************************************/
195 
196 int XrdOssArcCompose::ArcMember(char* buff, int blen)
197 {
198  if (snprintf(buff, blen, "%s:%s", flScope.c_str(), flName.c_str()) >= blen)
199  {std::string fn = flScope + ":" + flName;
200  Elog.Emsg("Compose", ENAMETOOLONG, "generate archive member name "
201  "in dataset", dsName.c_str());
202  ecMsg.Msg("Compose", "Archive member name", fn.c_str(), "is too long");
203  return ENAMETOOLONG;
204  }
205  return 0;
206 }
207 
208 /******************************************************************************/
209 /* A r c P a t h */
210 /******************************************************************************/
211 
212 int XrdOssArcCompose::ArcPath(char* buff, int blen, bool addafn)
213 {
214  int n;
215 
216 // The path to the archive is <tape_path>/<scope>/<dataset_did>/<arc_fname>
217 //
218  if (addafn) n = snprintf(buff, blen, "%s/%s/%s/%s", Config.tapePath,
219  dsScope.c_str(), dsName.c_str(), arName.c_str());
220  else n = snprintf(buff, blen, "%s/%s/%s", Config.tapePath,
221  dsScope.c_str(), dsName.c_str());
222 
223 // Verify that we did not truncate the path
224 //
225  if (n >= blen)
226  {std::string dsn = dsScope + ":" + dsName;
227  Elog.Emsg("Compose", ENAMETOOLONG, "generate archive path for dataset",
228  dsn.c_str());
229  return ENAMETOOLONG;
230  }
231 
232 // All done
233 //
234  return 0;
235 }
236 
237 /******************************************************************************/
238 /* D S N 2 D i r */
239 /******************************************************************************/
240 
241 std::string XrdOssArcCompose::DSN2Dir(const char* dsn)
242 {
243  std::string retdir(dsn);
244  int n = retdir.length();
245 
246  for (int i = 0; i < n; i++) if (retdir[i] == '/') retdir[i] = '%';
247 
248  return retdir;
249 }
250 
251 /******************************************************************************/
252 /* D i r 2 D S N */
253 /******************************************************************************/
254 
255 std::string XrdOssArcCompose::Dir2DSN(const char* dir)
256 {
257  std::string retdsn(dir);
258  int n = retdsn.length();
259 
260  for (int i = 0; i < n; i++) if (retdsn[i] == '%') retdsn[i] = '/';
261 
262  return retdsn;
263 }
264 
265 /******************************************************************************/
266 /* Private: g e t D S N */
267 /******************************************************************************/
268 
269 int XrdOssArcCompose::getDSN(const char *path)
270 {
271 
272 // At this point we must have a dataset name containing a scope name
273 //
274  const char* colon = index(path, ':');
275  if (!colon || *path == ':')
276  {ecMsg.Msg("Compose", "Dataset scope not specified");
277  return EINVAL;
278  }
279  dsScope.assign(path, colon - path);
280 
281 // Verify that the dataset name is long enough
282 //
283  if ((int)strlen(colon+1) < minLenDSN)
284  {ecMsg.Msg("Compose", "dataset name is too short");
285  return EINVAL;
286  }
287 
288 // The dataset name must not end with the arc file suffix
289 //
290 // if (isArcFile(colon+1))
291 // {ecMsg.Msg("Compose", "Dataset name cannot refer to an archive");
292 // return EINVAL;
293 // }
294 
295 // Assign the dataset name
296 //
297  dsName = colon+1;
298  return 0;
299 }
300 
301 /******************************************************************************/
302 /* i s A r c F i l e */
303 /******************************************************************************/
304 
305 bool XrdOssArcCompose::isArcFile(const char *path)
306 {
307  int n = strlen(path);
308 
309 // Is it's too short, it cannot end with ".zip" (or what it the suffix is)
310 //
311  if (n <= Config.arfSfxLen) return false;
312 
313 // Verify the ending
314 //
315  return !strcmp(Config.arfSfx, path+n-Config.arfSfxLen);
316 }
317 
318 /******************************************************************************/
319 /* i s A r c P a t h */
320 /******************************************************************************/
321 
322 bool XrdOssArcCompose::isArcPath(const char *path)
323 {
324  return !strncmp(Config.arcvPathLFN, path, Config.arcvPathLEN);
325 }
326 
327 /******************************************************************************/
328 /* i s B k p P a t h */
329 /******************************************************************************/
330 
331 bool XrdOssArcCompose::isBkpPath(const char *path)
332 {
333  return !strncmp(Config.bkupPathLFN, path, Config.bkupPathLEN);
334 }
335 
336 /******************************************************************************/
337 /* i s M i n e */
338 /******************************************************************************/
339 
340 bool XrdOssArcCompose::isMine(const char *path)
341 {
342  return !strncmp(Config.arcvPathLFN, path, Config.arcvPathLEN) ||
343  !strncmp(Config.bkupPathLFN, path, Config.bkupPathLEN);
344 }
345 
346 /******************************************************************************/
347 /* Private: S e t a r N a m e */
348 /******************************************************************************/
349 
350 int XrdOssArcCompose::SetarName()
351 {
352  TraceInfo("SetarName", 0);
353 
354 // Which is applicable to any type of object as it only wants to know
355 // which archive file contains a particular dataset file. So, construct
356 // the argument list to ask.
357 //
358  std::string fName = flScope + (std::string)":" + flName;
359  const char* argV[] = {"which", Config.arFName, fName.c_str(),
360  dsScope.c_str(), dsName.c_str()};
361  int argC = sizeof(argV)/sizeof(char*);
362  int rc;
363 
364 // Do some tracing
365 //
366  DEBUG("Running "<<Config.BkpUtilName<<' '<<argV[0]<<' '
367  <<argV[1]<<' '<<argV[2]<<' '<<argV[3]<<' '<<argV[4]);
368 
369 // Execute command, it should return a single line of output
370 //
371  XrdOucStream cmdSup;
372  bool aOK = false;
373 
374  if (!(rc = Config.BkpUtilProg->Run(&cmdSup, argV, argC)))
375  {char* resp;
376  if (cmdSup.GetLine() && (resp = cmdSup.GetToken()) && *resp)
377  {arName = resp;
378  aOK = *resp != '!';
379  DEBUG(resp<<" holds "<<dsScope<<':'<<dsName<<'['<<fName<<"]")
380  }
381  rc = Config.BkpUtilProg->RunDone(cmdSup);
382  }
383 
384 // Check if we have a result, that implies all went well enough
385 //
386  if (aOK) return 0;
387 
388 // Diagnose the error
389 //
390  if (arName == "!ENOENT") rc = ENOENT;
391  else if (arName == "!ENOANO") rc = ENOANO;
392  else if (!rc) rc = EPROTO;
393 
394  fName += ';';
395  std::string dName = dsScope +':'+ dsName;
396 
397  ecMsg.Msg("Compose", "Unable to determine name of the dataset",
398  dName.c_str(), "archive for", fName.c_str(),
399  (rc == ENOANO ? "dataset not backed up" : XrdSysE2T(rc)));
400  return rc;
401 }
402 
403 /******************************************************************************/
404 /* S t a t */
405 /******************************************************************************/
406 
407 int XrdOssArcCompose::Stat(const char* Scope, const char* Name,
408  struct stat* Stat)
409 {
410  TraceInfo("Stat", 0);
411 
412 // Setup argument list for a stat call
413 //
414  const char* argV[] = {"stat", "cgi", Scope, Name};
415  int argC = sizeof(argV)/sizeof(char*);
416  int rc, rc2 = 0;
417 
418 // Do some tracing
419 //
420  DEBUG("Running "<<Config.BkpUtilName<<' '<<argV[0]<<' '
421  <<argV[1]<<' '<<argV[2]<<' '<<argV[3]);
422 
423 // Execute command, it should return a single line of output
424 //
425  XrdOucStream cmdSup;
426  bool aOK = false;
427 
428  if (!(rc = Config.BkpUtilProg->Run(&cmdSup, argV, argC)))
429  {char* resp;
430  if (cmdSup.GetLine() && (resp = cmdSup.GetToken()) && *resp)
431  {if (*resp != '!')
432  {if (!(rc = StatDecode(*Stat, resp))) aOK = true;
433  else if (!strcmp("!ENOENT", resp)) rc = ENOENT;
434  else rc = EINVAL;
435  }
436  }
437  rc2 = Config.BkpUtilProg->RunDone(cmdSup);
438  }
439 
440 // Check if we have a result, that implies all went well enough
441 //
442  if (aOK) return 0;
443  if (rc) return rc;
444  if (rc2) return rc2;
445  return EPROTO;
446 }
447 
448 /******************************************************************************/
449 /* Private: S t a t D e c o d e */
450 /******************************************************************************/
451 
452 int XrdOssArcCompose::StatDecode(struct stat& Stat, const char* resp)
453 {
454  TraceInfo("StatDecode", 0);
455  XrdOucEnv env(resp);
456  char* infoP;
457  long long val;
458  int n;
459 
460  memset(&Stat, 0, sizeof(struct stat));
461 
462  if (StatGet("uid", env, val)) Stat.st_uid = int(val);
463  else return EINVAL;
464 
465  if (StatGet("gid", env, val)) Stat.st_gid = int(val);
466  else return EINVAL;
467 
468  if (StatGet("size", env, val)) Stat.st_size = val;
469  else return EINVAL;
470 
471  if (StatGet("atime", env, val)) Stat.st_atime = time_t(val);
472  else return EINVAL;
473  if (StatGet("mtime", env, val)) Stat.st_mtime = time_t(val);
474  else return EINVAL;
475  if (StatGet("ctime", env, val)) Stat.st_ctime = time_t(val);
476  else return EINVAL;
477 
478 // Set the mode
479 //
480  if (!(infoP = env.Get("mode")))
481  {DEBUG("Missing 'mode' in stat resp '"<<env.Env(n)<<"'");
482  return EINVAL;
483  }
484 
485 // Set the correct mode
486 //
487  Stat.st_mode = S_IRUSR | S_IRGRP;
488  if (!strcmp(infoP, "FILE")) Stat.st_mode |= S_IFREG;
489  else Stat.st_mode = S_IFDIR | S_IXUSR | S_IXGRP;
490 
491 // All done
492 //
493  return 0;
494 }
495 
496 /******************************************************************************/
497 /* Private: S t a t G e t */
498 /******************************************************************************/
499 
500 bool XrdOssArcCompose::StatGet(const char* var, XrdOucEnv& env, long long& val)
501 {
502  TraceInfo("StatGet", 0);
503  const char* varval;
504  char* endval;
505  int n;
506 
507 // Fetch the value
508 //
509  if (!(varval = env.Get(var)))
510  {DEBUG("Missing '"<<var<<"' in stat resp '"<<env.Env(n)<<"'");
511  return false;
512  }
513 
514 // Convert it to a long long
515 //
516  val = strtoll(varval, &endval, 10);
517  if (*endval == 0) return true;
518 
519 // Document failure
520 //
521  DEBUG("Invalid '"<<var<<'='<<varval<<"' in stat resp '"<<env.Env(n)<<"'");
522  return false;
523 }
#define DEBUG(x)
Definition: XrdBwmTrace.hh:54
struct stat Stat
Definition: XrdCks.cc:49
#define ENOANO
#define TraceInfo(x, y)
#define stat(a, b)
Definition: XrdPosix.hh:105
const char * XrdSysE2T(int errcode)
Definition: XrdSysE2T.cc:104
static bool isArcPath(const char *path)
static int Stat(const char *Scope, const char *Name, struct stat *Stat)
static bool isArcFile(const char *path)
XrdOssArcCompose(const char *path, XrdOucEnv *env, int &retc, bool isW=true, bool optfn=false)
static std::string Dir2DSN(const char *dir)
static std::string DSN2Dir(const char *dsn)
static bool isMine(const char *path)
static bool isBkpPath(const char *path)
int ArcMember(char *buff, int blen)
int ArcPath(char *buff, int blen, bool addafn=false)
const char * BkpUtilName
XrdOucProg * BkpUtilProg
std::string Msg()
Definition: XrdOucECMsg.hh:83
char * Get(const char *varname)
Definition: XrdOucEnv.hh:69
char * Env(int &envlen)
Definition: XrdOucEnv.hh:48
int RunDone(XrdOucStream &cmd) const
Definition: XrdOucProg.cc:257
int Run(XrdOucStream *Sp, const char *argV[], int argc=0, const char *envV[]=0) const
Definition: XrdOucProg.cc:108
char * GetLine()
char * GetToken(int lowcase=0)
int Emsg(const char *esfx, int ecode, const char *text1, const char *text2=0)
Definition: XrdSysError.cc:116
thread_local XrdOucECMsg ecMsg
XrdOssArcConfig Config
Definition: XrdOssArc.cc:68
XrdSysError Elog(0, "OssArc_")