/******************************************************************************/
/* */
/* X r d X m l M e t a L i n k . h h */
/* */
/* (c) 2015 by the Board of Trustees of the Leland Stanford, Jr., University */
/* Produced by Andrew Hanushevsky for Stanford University under contract */
/* DE-AC02-76-SFO0515 with the Department of Energy */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/******************************************************************************/
#include
#include
#include
#include
#include
#include "XrdSys/XrdSysAtomics.hh"
#include "XrdSys/XrdSysFD.hh"
#include "XrdSys/XrdSysPthread.hh"
#include "XrdXml/XrdXmlMetaLink.hh"
/******************************************************************************/
/* L o c a l D e f i n i t i o n s */
/******************************************************************************/
#define SizeOfVec(x) sizeof(x)/sizeof(x[0])
namespace
{
char tmpPath[40];
unsigned int GenTmpPath()
{
// The below will not generate a result more than 31 characters.
//
snprintf(tmpPath, sizeof(tmpPath), "/tmp/.MetaLink%8x.%d.",
static_cast(time(0)), static_cast(getpid()));
return 0;
}
XrdSysMutex xMutex;
unsigned int seqNo = GenTmpPath();
}
/******************************************************************************/
/* L o c a l C l a s s e s */
/******************************************************************************/
namespace
{
class CleanUp
{
public:
XrdXmlReader **delRDR;
char *delTFN;
CleanUp() : delRDR(0), delTFN(0) {}
~CleanUp() {if (delRDR) {delete *delRDR; *delRDR = 0;}
if (delTFN) unlink(delTFN);
}
};
class vecMon
{
public:
vecMon(char **vec, int vecn)
: theVec(vec), vecNum(vecn) {}
~vecMon() {if (theVec)
for (int i = 0; i < vecNum; i++)
if (theVec[i]) free(theVec[i]);
}
private:
char **theVec;
int vecNum;
};
}
/******************************************************************************/
/* C o n v e r t */
/******************************************************************************/
XrdOucFileInfo *XrdXmlMetaLink::Convert(const char *fname, int blen)
{
static const char *mlV3NS = "http://www.metalinker.org/";
static const char *mlV4NS = "urn:ietf:params:xml:ns:metalink";
static const char *mlV3[] = {"metalink", "files", 0};
static const char *mTag[] = {"", "metalink", 0};
static const char *mAtr[] = {"xmlns", 0};
const char *scope = "metalink";
char *mVal[] = {0};
CleanUp onReturn;
XrdOucFileInfo *fP;
const char *gLFN;
char *colon, gHdr[272];
bool chkG;
// If we are converting a buffer, then generate the file
//
if (blen > 0)
{if (!PutFile(fname, blen)) return 0;
onReturn.delTFN = tmpFn;
fname = tmpFn;
}
// Check if we should add a global file entry
//
if (rdHost && (rdProt || (prots && (colon = index(prots,':')))))
{if (!rdProt) {rdProt = prots; *(colon+1) = 0;}
else colon = 0;
snprintf(gHdr, sizeof(gHdr), "%s//%s/", rdProt, rdHost);
if (colon) *(colon+1) = ':';
chkG = true;
} else chkG = false;
// Get a file reader
//
if (!(reader = XrdXmlReader::GetReader(fname, encType)))
{eCode = errno;
snprintf(eText, sizeof(eText), "%s trying to read %s",
(errno ? strerror(errno) : "Unknow error"), fname);
return 0;
}
// Make sure we delete the reader should we return
//
onReturn.delRDR = &reader;
// We must find the metalink tag
//
if (!reader->GetElement(mTag, true))
{GetRdrError("looking for 'metalink' tag");
return 0;
}
// The input can be in metalink 3 or metalink 4 format. The metalink tag will
// tell us which one it is. It better be in the document with the xmlns attribute
//
if (!reader->GetAttributes(mAtr, mVal))
{strcpy(eText, "Required metalink tag attribute 'xmlns' not found");
eCode = ENOMSG;
return 0;
}
// The namespace tells us what format we are using here. For v3 formt we must
// alignh ourselves on the "files" tag. There can only be one of those present.
//
if (!strcmp(mVal[0], mlV3NS))
{if (!reader->GetElement(mlV3, true))
GetRdrError("looking for 'files' tag");
scope = "files";
}
else if ( strcmp((const char *)mVal[0], mlV4NS))
{strcpy(eText, "Metalink format not supported");
eCode = EPFNOSUPPORT;
}
// Check if can continue
//
free(mVal[0]);
if (eCode) return 0;
// Get one or more files
//
currFile = 0; fileCnt = 0; noUrl = true;
do{if (!GetFile(scope)) break;
currFile = new XrdOucFileInfo;
if (GetFileInfo("file"))
{if (lastFile) lastFile ->nextFile = currFile;
else fileList = currFile;
lastFile = currFile;
if (chkG && (gLFN = currFile->GetLfn()))
{char lfnBuff[2048];
snprintf(lfnBuff, sizeof(lfnBuff), "%s%s", gHdr, gLFN);
currFile->AddUrl(lfnBuff, 0, INT_MAX);
currFile->AddProtocol(rdProt);
}
currFile = 0;
fileCnt++; noUrl = true;
}
} while(doAll);
// The loop ends when we cannot find a file tag. So, the current file is invalid
//
if (currFile) {delete currFile; currFile = 0;}
// Check if we have any files at all
//
if (!fileCnt)
{strcpy(eText, "No applicable urls specified for the file entry");
eCode = EDESTADDRREQ;
}
// If this is an all call then return to execute the postantem
//
fP = fileList; lastFile = fileList = 0;
if (doAll) return fP;
// Check if we have clean status. If not, undo all we have and return failure
//
if (!eCode) return fP;
if (fP) delete fP;
return 0;
}
/******************************************************************************/
/* C o n v e r t A l l */
/******************************************************************************/
XrdOucFileInfo **XrdXmlMetaLink::ConvertAll(const char *fname, int &count,
int blen)
{
CleanUp onReturn;
XrdOucFileInfo *fP, **fvP;
// Indicate this is a call from here
//
doAll = true;
count = 0;
// If we are converting a buffer, then generate the file
//
if (blen > 0)
{if (!PutFile(fname, blen)) return 0;
onReturn.delTFN = tmpFn;
fname = tmpFn;
}
// Perform the conversion
//
if (!(fP = Convert(fname))) return 0;
// Check if we have clean status, if not return nothing
//
if (eCode)
{XrdOucFileInfo *fnP = fP->nextFile;
while((fP = fnP))
{fnP = fP->nextFile;
delete fP;
}
return 0;
}
// Return a vector of the file info objects
//
fvP = new XrdOucFileInfo* [fileCnt];
for (int i = 0; i < fileCnt; i++) {fvP[i] = fP; fP = fP->nextFile;}
count = fileCnt;
return fvP;
}
/******************************************************************************/
/* D e l e t e A l l */
/******************************************************************************/
void XrdXmlMetaLink::DeleteAll(XrdOucFileInfo ** vecp, int vecn)
{
// Delete each object in the vector
//
for (int i = 0; i < vecn; i++)
delete vecp[i];
// Now delete the vector
//
delete []vecp;
}
/******************************************************************************/
/* Private: G e t F i l e */
/******************************************************************************/
bool XrdXmlMetaLink::GetFile(const char *scope)
{
const char *fileElem[] = {scope, "file", 0};
const char *etext;
bool needFile = fileCnt == 0;
// We align on "file" this is true at this point regardless of version.
//
if (!reader->GetElement(fileElem, needFile))
{if ((etext = reader->GetError(eCode)))
{size_t len = strlen(etext);
if(len > sizeof(eText)-1) len=sizeof(eText)-1;
memcpy(eText, etext, len);
eText[len]=0;
}
return false;
}
// We are now aligned on a file tag
//
return true;
}
/******************************************************************************/
/* Private: G e t F i l e I n f o */
/******************************************************************************/
bool XrdXmlMetaLink::GetFileInfo(const char *scope)
{
static const char *fileScope = "file";
const char *fsubElem[] = {scope, "url", "hash", "size",
"verification", "resources", "glfn", 0};
int ePos;
if(strncmp(scope, fileScope, 4) == 0) GetName();
// Process the elements in he file section. Both formats have the same tags,
// though not the same attributes. We will take care of the differences later.
//
while((ePos = reader->GetElement(fsubElem)))
switch(ePos)
{case 1: if (!GetUrl()) return false;
break;
case 2: if (!GetHash()) return false;
break;
case 3: if (!GetSize()) return false;
break;
case 4: GetFileInfo("verification");
if (eCode) return false;
break;
case 5: GetFileInfo("resources");
if (eCode) return false;
break;
case 6: if (!GetGLfn()) return false;
break;
default: break;
}
// Return success if we had at least one url
//
return !noUrl;
}
/******************************************************************************/
/* Private: G e t G L f n */
/******************************************************************************/
bool XrdXmlMetaLink::GetGLfn()
{
static const char *gAttr[] = {"name", 0};
char *gAVal[] = {0};
vecMon monVec(gAVal, SizeOfVec(gAVal));
// Get the name
//
if (!reader->GetAttributes(gAttr, gAVal))
{strcpy(eText, "Required glfn tag name attribute not found");
eCode = ENOMSG;
return false;
}
// Add the the glfn
//
currFile->AddLfn(gAVal[0]);
// All done
//
return true;
}
/******************************************************************************/
/* Private: G e t H a s h */
/******************************************************************************/
bool XrdXmlMetaLink::GetHash()
{
static const char *hAttr[] = {"type", 0};
char *hAVal[] = {0};
vecMon monVec(hAVal, SizeOfVec(hAVal));
char *value;
// Get the hash type
//
if (!reader->GetAttributes(hAttr, hAVal))
{strcpy(eText, "Required hash tag type attribute not found");
eCode = ENOMSG;
return false;
}
// Now get the hash value
//
if (!(value = reader->GetText("hash", true))) return false;
// Add a new digest
//
currFile->AddDigest(hAVal[0], value);
// All done
//
free(value);
return true;
}
/******************************************************************************/
/* G e t R d r E r r o r */
/******************************************************************************/
void XrdXmlMetaLink::GetRdrError(const char *why)
{
const char *etext = reader->GetError(eCode);
if (etext)
{size_t len = strlen(etext);
if(len > sizeof(eText)-1) len = sizeof(eText)-1;
memcpy(eText, etext, len);
eText[len]=0;
}
else {snprintf(eText, sizeof(eText), "End of xml while %s", why);
eCode = EIDRM;
}
}
/******************************************************************************/
/* Private: G e t S i z e */
/******************************************************************************/
bool XrdXmlMetaLink::GetSize()
{
char *eP, *value;
long long fsz;
// Now get the size value
//
if (!(value = reader->GetText("size", true))) return false;
// Convert size, it must convert clean and be non-negatie
//
fsz = strtoll(value, &eP, 10);
if (fsz < 0 || *eP != 0)
{snprintf(eText,sizeof(eText), "Size tag value '%s' is invalid", value);
eCode = EINVAL;
free(value);
return false;
}
// Set the size and return
//
currFile->SetSize(fsz);
free(value);
return true;
}
/******************************************************************************/
/* Private: G e t U r l */
/******************************************************************************/
bool XrdXmlMetaLink::GetUrl()
{
static const char *uAttr[] = {"location", "priority", "preference", 0};
char *uAVal[] = {0, 0, 0};
vecMon monVec(uAVal, SizeOfVec(uAVal));
char *value;
int prty = 0;
// Get the optional attributes
//
reader->GetAttributes(uAttr, uAVal);
// Now get the url value. There might be one, that is valid and we ignore it.
//
if (!(value = reader->GetText("url"))) return true;
// Check if we need to screen url protocols
//
if (!UrlOK(value))
{free(value);
return true;
}
// Process priority or preference (we ignore errors here)
//
if (uAVal[1]) prty = atoi(uAVal[1]);
else if (uAVal[2])
{prty = 100 - atoi(uAVal[2]);
if (prty < 0) prty = 0;
}
// Add the url to the flle
//
currFile->AddUrl(value, uAVal[0], prty);
free(value);
// All done
//
noUrl = false;
return true;
}
/******************************************************************************/
/* Private: G e t N a m e */
/******************************************************************************/
void XrdXmlMetaLink::GetName()
{
static const char *mAtr[] = {"name", 0};
char *mVal[] = {0};
reader->GetAttributes(mAtr, mVal);
currFile->AddFileName(mVal[0]);
free(mVal[0]);
}
/******************************************************************************/
/* Private: P u t F i l e */
/******************************************************************************/
bool XrdXmlMetaLink::PutFile(const char *buff, int blen)
{
static const int oFlags = O_EXCL | O_CREAT | O_TRUNC | O_WRONLY;
const char *what = "opening";
unsigned int fSeq;
int fd;
// Get a unique sequence number
//
AtomicBeg(xMutex);
fSeq = AtomicInc(seqNo);
AtomicEnd(xMutex);
// Generate a unique filepath. Unfortunately, mktemp is unsafe and mkstemp may
// leak a file descriptor. So, we roll our own using above sequence number.
// Note that the target buffer is 64 characters which is suffcient for us.
//
snprintf(tmpFn, sizeof(tmpFn), "%s%u", tmpPath, fSeq);
// Open the file for output, write out the buffer, and close the file
//
if ((fd = XrdSysFD_Open(tmpFn, oFlags, S_IRUSR|S_IWUSR)) > 0)
{what = "writing";
if (write(fd, buff, blen) == blen)
{what = "closing";
if (!close(fd)) return true;
}
}
// We failed
//
eCode = errno;
snprintf(eText, sizeof(eText), "%s %s %s", strerror(eCode), what, tmpFn);
unlink(tmpFn);
return false;
}
/******************************************************************************/
/* Private: U r l O K */
/******************************************************************************/
bool XrdXmlMetaLink::UrlOK(char *url)
{
char *colon, pBuff[16];
int n;
// Find the colon and get the length of the protocol
//
if (!(colon = index(url, ':'))) return false;
n = colon - url + 1;
if (n >= (int)sizeof(pBuff)) return false;
strncpy(pBuff, url, n);
pBuff[n] = 0;
// Add this protocol to the list we found
//
currFile->AddProtocol(pBuff);
// Return whether or not this os one of the acceptable protocols
//
if (prots) return (strstr(prots, pBuff) != 0);
return true;
}