3 A module to manipulate files on EOS or on the local file system. Intended to have the same interface as castortools.py.
5 from __future__
import print_function
12 eos_select =
'/afs/cern.ch/project/eos/installation/cms/bin/eos.select'
15 """Hack to get the CAF scripts on the PYTHONPATH"""
16 caf =
'/afs/cern.ch/cms/caf/python'
18 if caf
not in sys.path:
24 """Run an xrd command.
26 !!! Will, what is happening in case of problem?
27 ??? At some point, should return a list of lines instead of a string."""
31 tokens = cmsIO.splitPFN(
lfnToPFN(lfn))
33 command = [
'xrd', tokens[1], cmd, tokens[2]]
35 runner = cmsIO.cmsFileManip()
37 return runner.runCommand(command)
40 """Run an eos command.
42 !!! Will, when the EOS command fails, it passes silently...
43 I think we should really try and raise an exception in case of problems.
44 should be possible as the return code is provided in the tuple returned by runner."""
48 tokens = cmsIO.splitPFN(pfn)
51 command = [eos_select, cmd]
53 command.append(tokens[2])
54 runner = cmsIO.cmsFileManip()
55 return runner.runCommand(command)
58 """Tests whether this path is a CMS LFN (name starts with /store...)"""
60 return path.startswith(
'/store')
63 """Tests whether this path is a CMS EOS (name starts with /eos...)"""
64 return path.startswith(
'/eos')
or path.startswith(
'root://eoscms.cern.ch//eos/cms')
67 """Converts a EOS PFN to an LFN.
69 Just strip out /eos/cms from path.
70 If this string is not found, return path.
71 ??? Shouldn't we raise an exception instead?"""
72 return path.replace(
'root://eoscms.cern.ch/',
'').
replace(
'/eos/cms',
'')
75 castorToLFN = eosToLFN
78 """Converts an LFN to a PFN. For example:
79 /store/cmst3/user/cbern/CMG/TauPlusX/Run2011A-03Oct2011-v1/AOD/V2/PAT_CMG_V2_4_0/H2TAUTAU_Nov21
81 root://eoscms//eos/cms/store/cmst3/user/cbern/CMG/TauPlusX/Run2011A-03Oct2011-v1/AOD/V2/PAT_CMG_V2_4_0/H2TAUTAU_Nov21?svcClass=cmst3&stageHost=castorcms
83 This function only checks path, and does not access the storage system.
84 If the path is in /store/cmst3, it assumes that the CMST3 svcClass is to be used.
85 Otherwise, is uses the default one.
87 ??? what is tfcprot? """
89 if path.startswith(
"/store/"):
90 path = path.replace(
"/store/",
"root://eoscms.cern.ch//eos/cms/store/")
91 if path.startswith(
"/pnfs/psi.ch/cms/trivcat/"):
92 path = path.replace(
"/pnfs/psi.ch/cms/trivcat/",
"root://t3se01.psi.ch//")
94 entity = cmsIO.cmsFile( path, tfcProt )
96 pfn =
'%s://%s//%s/' % (entity.protocol,entity.host,entity.path)
99 if tfcProt ==
'rfio' and \
100 entity.path.startswith(
"/eos/cms/")
and \
101 str(entity.stat()).startswith(
"Error 3011: Unable to stat"):
103 pfn.replace(
"/eos/cms",
"/castor/cern.ch/cms")
104 pfn.replace(
"eoscms",
"castorcms")
109 """Converts LFN to EOS.
111 If path is not an LFN in the first place, return path.
112 ??? shouldn't we raise an exception?"""
114 pfn =
'root://eoscms.cern.ch//eos/cms/' + path
115 return pfn.replace(
'//store',
'/store')
120 lfnToCastor = lfnToEOS
123 """Returns True if path is either:
128 root://eoscms.cern.ch//eos/cms/
130 Otherwise, returns False.
132 WARNING!! This function does not check for path existence,
133 and returns true also for plain files.
134 !!! Will, is my summary correct?
136 if os.path.exists( path ):
141 if not path.startswith(
'/eos')
and not path.startswith(
'/store')
and not path.startswith(
'root://eoscms.cern.ch//eos/cms/'):
146 tokens = cmsIO.splitPFN(pfn)
147 return tokens
and tokens[1].lower().startswith(
'eos')
150 isCastorDir = isEOSDir
154 """Returns True if path is a file or directory stored on EOS (checks for path existence)
155 ??? This function does not behave well if passed a non EOS path...
156 returns lots of error messages like:
157 >>> eostools.isEOSFile('/store/asdfasfd')
158 Command (['ls', '/', 's', 't', 'o', 'r', 'e', '/', 'a', 's', 'd', 'f', 'a', 's', 'f', 'd', '/store']) failed with return code: 2
159 ls: s: No such file or directory
160 ls: t: No such file or directory
161 ls: o: No such file or directory
162 ls: r: No such file or directory
163 ls: e: No such file or directory
164 ls: a: No such file or directory
165 ls: s: No such file or directory
166 ls: d: No such file or directory
167 ls: f: No such file or directory
168 ls: a: No such file or directory
169 ls: s: No such file or directory
170 ls: f: No such file or directory
171 ls: d: No such file or directory
172 ls: /store: No such file or directory
174 ls: s: No such file or directory
175 ls: t: No such file or directory
176 ls: o: No such file or directory
177 ls: r: No such file or directory
178 ls: e: No such file or directory
179 ls: a: No such file or directory
180 ls: s: No such file or directory
181 ls: d: No such file or directory
182 ls: f: No such file or directory
183 ls: a: No such file or directory
184 ls: s: No such file or directory
185 ls: f: No such file or directory
186 ls: d: No such file or directory
187 ls: /store: No such file or directory
191 _, _, ret = runEOSCommand( path, 'ls')
194 #also define an alias for backwards compatibility
195 isCastorFile = isEOSFile
198 def fileExists( path ):
199 """Returns true if path is a file or directory stored locally, or on EOS.
201 This function checks for the file or directory existence."""
207 result = isEOSFile(path)
209 # print 'not eos', path
211 result = os.path.exists(path)
216 def eosDirSize(path):
217 '''Returns the size of a directory on EOS in GB.'''
219 res = runEOSCommand(lfn, 'find', '--size')
220 output = res[0].split('\n')
224 size += float(file.split('=')[2])
227 return size/1024/1024/1024
230 def createEOSDir( path ):
231 """Makes a directory in EOS
233 ???Will, I'm quite worried by the fact that if this path already exists, and is
234 a file, everything will 'work'. But then we have a file, and not a directory,
235 while we expect a dir..."""
237 if not isEOSFile(lfn):
238 # if not isDirectory(lfn):
239 runEOSCommand(lfn,'mkdir','-p')
240 # entity = cmsIO.cmsFile( lfn,"stageout")
242 # # print 'created ', path
243 if isDirectory(path):
246 raise OSError('cannot create directory '+ path)
248 #also define an alias for backwards compatibility
249 createCastorDir = createEOSDir
252 """Create a directory, either on EOS or locally"""
253 # print 'mkdir', path
254 if isEOS( path ) or isLFN(path):
257 # recursive directory creation (like mkdir -p)
261 def isDirectory(path):
262 """Returns True if path is a directory on EOS.
264 Tests for file existence.
265 This function returns False for EOS files, and crashes with local paths
267 ???Will, this function also seems to work for paths like:
269 ??? I think that it should work also for local files, see isFile."""
271 out, _, _ = runXRDCommand(path,'existdir')
272 return 'The directory exists' in out
275 """Returns True if a path is a file.
277 Tests for file existence.
278 Returns False for directories.
279 Works on EOS and local paths.
281 ???This function works with local files, so not the same as isDirectory...
282 isFile and isDirectory should behave the same.
285 if not path.startswith('/eos') and not path.startswith('/store'):
286 if( os.path.isfile(path) ):
291 out, _, _ = runXRDCommand(path,'existfile')
292 return 'The file exists' in out
294 def chmod(path, mode):
295 """Does chmod on a file or directory"""
297 return runEOSCommand(path, 'chmod', '-r', str(mode))
300 def listFiles(path, rec = False, full_info = False):
301 """Provides a list of the specified directory
303 # -- listing on the local filesystem --
304 if os.path.isdir( path ):
307 return [ '/'.join([path,file]) for file in os.listdir( path )]
309 # recursive, directories are put in the list first,
310 # followed by the list of all files in the directory tree
313 for root,dirs,files in os.walk(path):
314 result.extend( [ '/'.join([root,dir]) for dir in dirs] )
315 allFiles.extend( [ '/'.join([root,file]) for file in files] )
316 result.extend(allFiles)
318 # -- listing on EOS --
322 files, _, _ = runXRDCommand(path, cmd)
324 for line in files.split('\n'):
325 tokens = [t for t in line.split() if t]
328 # result.append(tuple(tokens))
329 #COLIN need same interface for eos and local fs
331 result.append( tokens)
333 result.append( tokens[4] )
337 command = ['which', cmd]
338 runner = cmsIO.cmsFileManip()
339 out, _, _ = runner.runCommand(command)
341 lines = [line for line in out.split('\n') if line]
344 elif len(lines) == 2:
349 def ls(path, rec = False):
350 """Provides a simple list of the specified directory, works on EOS and locally"""
351 return [eosToLFN(t) for t in listFiles(path, rec)]
353 def ls_EOS(path, rec = False):
354 """Provides a simple list of the specified directory, works on EOS only, but is faster than the xrd version"""
356 stdout, _, ret = runEOSCommand(path,'find','-f')
357 return [eosToLFN(line) for line in stdout.split('\n') if line]
359 stdout, _, ret = runEOSCommand(path,'ls')
361 return [os.path.join(lfn,line) for line in stdout.split('\n') if line]
363 def rm(path, rec=False):
364 """rm, works on EOS and locally.
366 Colin: should implement a -f mode and a confirmation when deleting dirs recursively."""
368 path = lfnToEOS(path)
371 runEOSCommand(path, 'rm', '-r')
373 runEOSCommand(path,'rm')
374 elif os.path.exists(path):
380 raise ValueError(path + ' is not EOS and not local... should not happen!')
382 def remove( files, rec = False):
383 """Remove a list of files and directories, possibly recursively
385 Colin: Is that obsolete? why not use rm?"""
391 #this should be used with care
392 file_list = ls(path, rec = True)
393 file_list.append(lfn)
395 #order the files in depth order - i.e. remove the deepest files first
396 files_rec = sorted([(len([ff for ff in f.split('/') if ff]), f) for f in file_list if f and f.startswith(lfn)], reverse = True)
402 """cat, works on EOS and locally"""
403 path = lfnToEOS(path)
405 #print "the file to cat is:", path
406 out, err, _ = runXRDCommand(path,'cat')
409 pattern = re.compile('cat returned [0-9]+')
410 for line in out.split('\n'):
411 match = pattern.search(line)
412 if line and match is not None:
413 lines.append(line.replace(match.group(0),''))
418 print(out, file=sys.stderr)
419 print(err, file=sys.stderr)
420 allLines = '\n'.join(lines)
421 if allLines and not allLines.endswith('\n'):
425 content = file(path).read()
426 if content and not content.endswith('\n'):
430 def xrdcp(src, dest):
431 """Does a copy of files using xrd.
433 Colin: implement a generic cp interface as done for rm, ls, etc?"""
439 if os.path.exists(src):
442 if os.path.isdir(src):
444 elif fileExists(src):
446 pfn_src = lfnToPFN(src)
450 raise ValueError(src + ' does not exist.')
455 dest = eosToLFN(dest)
456 pfn_dest = lfnToPFN(dest)
457 if isDirectory(dest):
458 tokens = cmsIO.splitPFN(pfn_dest)
459 pfn_dest = '%s://%s//%s/' % (tokens[0],tokens[1],tokens[2])
460 elif os.path.exists(dest):
466 topDir = src.rstrip('/').split('/')[-1]
468 dest = '/'.join([dest, topDir])
469 # print 'mkdir ' + dest
471 files = listFiles(src, rec=True)
472 # pprint.pprint( [file[4] for file in files] )
473 for srcFile in files:
476 if isEOSDir(srcFile):
477 srcFile = eosToLFN(srcFile)
478 pfnSrcFile = lfnToPFN(srcFile)
479 destFile = srcFile.replace( src, '' )
480 destFile = '/'.join([dest,destFile])
481 pfnDestFile = destFile
482 if isEOSDir(destFile):
483 lfnDestFile = eosToLFN(destFile)
484 pfnDestFile = lfnToPFN(lfnDestFile)
485 # print 'srcFile', pfnSrcFile
486 # print 'destFile', pfnDestFile
488 _xrdcpSingleFile( pfnSrcFile, pfnDestFile )
492 _xrdcpSingleFile( pfn_src, pfn_dest )
495 def _xrdcpSingleFile( pfn_src, pfn_dest):
496 """Copies a single file using xrd."""
499 command.append(pfn_src)
500 command.append(pfn_dest)
501 # print ' '.join(command)
504 runner = cmsIO.cmsFileManip()
505 out, err, ret = runner.runCommand(command)
507 print(out, file=sys.stderr)
508 print(err, file=sys.stderr)
512 """Move filename1 to filename2 locally to the same server"""
515 dest = eosToLFN(dest)
517 runXRDCommand(src,'mv', lfnToEOS(dest))
519 def matchingFiles( path, regexp):
520 """Return a list of files matching a regexp"""
523 pattern = re.compile( regexp )
524 #files = ls_EOS(path)
527 return [f for f in files if pattern.match(os.path.basename(f)) is not None]
529 def datasetNotEmpty( path, regexp ):
530 pattern = re.compile( regexp )
534 if pattern.match( os.path.basename(f) ) is not None:
538 def cmsStage( absDestDir, files, force):
539 """Runs cmsStage with LFNs if possible"""
541 destIsEOSDir = isEOSDir(absDestDir)
543 createEOSDir( absDestDir )
546 command = ['cmsStage']
549 command.append(eosToLFN(fname))
550 command.append(eosToLFN(absDestDir))
551 print(' '.join(command))
552 runner = cmsIO.cmsFileManip()
553 runner.runCommand(command)