import repository from arizona
[raven.git] / lib / arizona-lib / arizonatransfer.py
1 #! /usr/bin/env python
2
3 """
4 Stork Project (http://www.cs.arizona.edu/stork/)
5 Module: arizonatransfer
6 Description:   Provides file transferring from host and synchronizing two
7                different directories.
8 """
9
10 """arizonaconfig
11    options=[["",     "--transfermethod",  "transfermethod", "append", "string", ["http", "ftp"], "program", "use this method to transfer files (default http, ftp)"],
12             ["",     "--metatransfermethod", "metatransfermethod", "append", "string", ["http", "ftp"], "program", "use this method to transfer metadata (default http, ftp)"],
13             ["",     "--transfertempdir", "transfertempdir", "store", "string", "/tmp/stork_trans_tmp", "PATH", "use this path to save transferd files temporary (default is /tmp/stork_trans_tmp)"],
14             ["",     "--metafilecachetime", "metafilecachetime", "store", "int", 60, None, "seconds to cache metafile without re-requesting"],
15             ["",     "--disablesignedmetafile", "disablesignedmetafile", "store_true", None, False, None, "disable signed metafile"],
16             ["",     "--disabletransferhashcheck", "disabletransferhashcheck", "store_true", None, False, None, "disable transfer hash checking"]]
17    includes=["$MAIN/transfer/*"]
18 """
19
20 import sys
21 import os
22 import arizonareport
23 import arizonaconfig
24 import securerandom
25 import arizonageneral
26 import ravenlib.stats
27 import shutil
28 import traceback
29 import arizonacrypt
30 import time
31 import signal
32 import fnmatch
33
34 from stat import *
35
36 # metafile holds the names of files which need to sync
37 METAFILE_FN = "metafile"
38 SIGNED_METAFILE_FN = METAFILE_FN + ".signed"
39
40 # it holds what transfer method is imported
41 global arizonafetch
42 arizonafetch = None
43
44 # indicates importing status. If init_status is -1, then no transfer module has been imported.
45 init_status = -1
46 glo_prioritylist = []
47 modules_failed_install = []
48
49 # pass this in a file tuple when the size is unknown
50 SIZE_UNKNOWN = None
51
52
53
54
55
56 class TransferTimeOutExc(Exception):
57     def __init__(self, value = "Timed Out"):
58         self.value = value\r
59     def __str__(self):\r
60         return repr(self.value)\r
61
62
63
64
65
66 def TransferAlarmHandler(signum, frame):
67    raise TransferTimeOutExc
68
69
70
71
72
73 glo_oldsignal = None
74 def __enable_timeout(seconds):
75    """
76       <Purpose>
77           Enable the alarm signal
78       <Arguments>
79           seconds - number of seconds when alarm will go off
80    """
81    global glo_oldsignal
82    glo_oldsignal = signal.signal(signal.SIGALRM, TransferAlarmHandler)
83    signal.alarm(seconds)
84
85
86
87
88
89 def __disable_timeout():
90    """
91       <Purpose>
92           disable the alarm signal
93    """
94    global glo_oldsignal
95    signal.alarm(0)
96    if glo_oldsignal!=None:
97        signal.signal(signal.SIGALRM, glo_oldsignal)
98        glo_oldsignal = None
99
100
101
102
103
104 def __compute_timeout(filelist):
105    """
106       <Purpose>
107           compute a timeout for a file list.
108       <Arguments>
109           filelist - a list of file tuples (name, hash, size)
110       <Returns>
111           timeout in seconds
112    """
113    total_size = 0
114    unknown_size = True
115
116    for file in filelist:
117       size = file.get('size', SIZE_UNKNOWN)
118       if size != SIZE_UNKNOWN:
119          unknown_size = False
120          total_size = total_size + size
121
122    if unknown_size:
123       # if the size is unknown, return 60 minutes
124       return 60*60
125    else:
126       # otherwise, return 10 minutes + 1 minute per megabyte
127       return 60*10 + total_size / (1024*1024/60)
128
129
130
131
132
133 def reset_transfer_method():
134    """
135       <Purpose>
136           reset the init_status variable. This will cause arizonatransfer to
137           start transfering with the highest priority method again
138    """
139    global init_status
140    init_status = -1
141
142
143
144
145 def default_hashfunc(filename):
146    return arizonacrypt.get_fn_hash(filename, "sha1")
147
148
149
150
151 def initialize_transfer_method(method):
152    global arizonafetch
153    global glo_prioritylist
154    global init_status
155    global modules_failed_install;
156
157    if method in modules_failed_install:
158        arizonareport.send_error(2, "WARNING: method '" + method + "' previously tried to initialize and failed; skipping...") 
159        arizonafetch = None
160        return
161
162    try:
163       # import a certain transfer method
164       # TODO possible security problem?   For example, method="nest as arizonafetch; hostile code...; #"
165       exec("import transfer.arizonatransfer_" + method + " as arizonafetch")    
166
167       # crazy and only way to use 'exec'   :)
168       globals()['arizonafetch'] = locals()['arizonafetch']
169
170       arizonareport.send_syslog(arizonareport.INFO, "\n" + arizonafetch.transfer_name() + " starting...")
171
172       # initialize the transfer method
173       try:
174          arizonafetch.init_transfer_program()
175       except:
176          arizonareport.send_syslog(arizonareport.ERR, "getfiles(): Initializing : Initialization error.")
177          arizonareport.send_error(2, "WARNING: Could not initialize " + method + " transfer...")
178          arizonafetch = None
179          return
180
181       # init_status is set by index number so that it will indicate that something is imported
182       init_status = glo_prioritylist.index(method)
183    # if module name doesn't exist
184    except ImportError, (errno):
185       modules_failed_install.append(method)
186       arizonareport.send_syslog(arizonareport.ERR, "getfiles(): Initializing : Import error(" + str(errno) + ")")
187       arizonafetch = None
188       arizonareport.send_error(2, "WARNING: Could not import " + method + " transfer: " + str(errno))
189    except NameError, (errno):
190       # BitTorrent 4.4.0-5.fc7 on python 2.5 is throwing this error
191       # This is what my Fedora8 machine has installed
192       modules_failed_install.append(method)
193       arizonareport.send_syslog(arizonareport.ERR, "getfiles(): Initializing : Name error(" + str(errno) + ")")
194       arizonafetch = None
195       arizonareport.send_error(2, "WARNING: Could not import " + method + " transfer: " + str(errno))
196
197
198 def getfiles1(host, filelist, destdir, prog_indicator=0, createhashfile=False, ignoreHash=False, prioritylist=None):
199     """
200         DEPRECATED - calls to getfiles1() should be renamed getfiles()
201     """
202     return getfiles(host, filelist, destdir, prog_indicator, createhashfile, ignoreHash, prioritylist)
203
204 def getfiles(host, filelist, destdir, prog_indicator=0, createhashfile=False, ignoreHash=False, prioritylist=None):
205    """
206    <Purpose>
207       This fetches files from given host, using prioritylist which holds
208       transfer methods to fetch files.
209       It tries to get files by one method, and if it fails it uses next
210       possible method until it gets all files needed.
211
212    <Arguments>
213       host:
214          'host' holds two things, a server name and download directory.
215          For example, if you want to retrieve files from '/tmp/' directory
216          in 'quadrus.cs.arizona.edu' server, the 'host' will be
217          'quadrus.cs.arizona.edu/tmp'.
218          'host' should be a string.
219
220       filelist:
221          'filelist' is a list of files which need to be retrieved.
222          'filelist' should be a list of dictionaties of the format:
223              {"filename": filename,
224               "hash": hash,
225               "size": size,
226               "hashfuncs": list of hashfuncs to try}
227              the hash and size parameters can be None if unavailable
228
229       destdir:
230          'destdir' is a destination directory where retrieved files will
231          be placed. A user should have 'destdir' exist before retrieving
232          files. 'destdir' should be a string.
233
234       prog_indicator:
235          If it is non-zero, this program will show a progress bar while
236          downloading, with the given width. Default value is 0 (no
237          indicator is shown).
238
239       prioritylist:
240          If it is not none, then it is a list of the names of transfer stubs
241          to use when downloading the file. Otherwise, the value is retrieved
242          from arizonaconfig.
243
244    <Exceptions>
245       None.
246
247    <Side Effects>
248       Messes with SIGALRM
249       Set init_status
250
251    <Returns>
252       True or False to indicate success, and a list of downloaded files
253    """
254
255    global init_status
256    global arizonafetch
257    global glo_prioritylist
258
259    arizonareport.send_out(4, "[DEBUG] getfiles started")
260    arizonareport.send_out(4, "host = " + str(host) + ", filelist = " + str(filelist))
261
262    # downloaded files list
263    downloaded_files = []
264
265    # check if host is a string
266    arizonageneral.check_type_simple(host, "host", str, "arizonatransfer.getfiles")
267
268    # check if destdir is a string
269    arizonageneral.check_type_simple(destdir, "destdir", str, "arizonatransfer.getfiles")
270
271    # get username
272    username = arizonageneral.getusername()
273
274    # check that the destination directory exists
275    if not os.path.isdir(destdir):
276       arizonareport.send_syslog(arizonareport.ERR, "\ngetfiles(): The destination directory '" + destdir + "' does not exist...   Aborting...")
277       # return false and empty list
278       return (False, downloaded_files)
279
280    if prioritylist == None:
281       # transfer method list set by arizonaconfig
282       prioritylist = arizonaconfig.get_option("transfermethod")
283
284    # check the method list
285    # if prioritylist is None, there's something wrong with configuration
286    if prioritylist == None :
287       arizonareport.send_syslog(arizonareport.ERR, "getfiles(): No transfer method was given.")
288       return (False, downloaded_files)
289
290    if prioritylist != glo_prioritylist:
291       glo_prioritylist = prioritylist
292       # make sure if the priority list changed, that we start over again with
293       # the protocols
294       init_status = -1
295
296    # create a temporary directory for the transfer
297    arizonareport.send_out(4, "[DEBUG] getfiles creating temp dir")
298    try:
299       temp_dir = arizonaconfig.get_option("transfertempdir") + str(securerandom.SecureRandom().random())
300    except TypeError:
301       arizonareport.send_syslog(arizonareport.ERR, "getfiles(): No transfer temp dir is given.")
302       return (False, downloaded_files)
303
304    # in the case of destdir has '/' at the end
305    # last '/' should go away to make result list(downloaded_files) match
306    if len(destdir) > 1 and destdir.endswith('/'):
307       destdir = destdir[:len(destdir) - 1]
308
309    # if there is empty strings in the filelist, those will be taken away
310    arizonareport.send_out(4, "[DEBUG] checking file list")
311    filelist = __checkFileList(filelist)
312
313    arizonareport.send_out(4, "[DEBUG] creating directories")
314    for item in filelist:
315       filename = item['filename']
316       dirname = os.path.dirname(filename)
317       if dirname != "":
318          arizonageneral.makedirs_existok(os.path.join(temp_dir, dirname))
319          arizonageneral.makedirs_existok(os.path.join(destdir, dirname))
320
321    filenames = [item['filename'] for item in filelist]
322
323    # keep the number of the list to compare how many files are downloaded at the end.
324    numoflist = len(filelist)
325
326    # if prog_indicator is True, then set it as download_indicator so that it is passed in the transfer method program
327    arizonareport.send_out(4, "[DEBUG] importing download_indicator")
328    if (prog_indicator > 0):
329       try:
330          import download_indicator
331          prog_indicator_module = download_indicator
332          prog_indicator_module.set_width(prog_indicator)
333       # if importing fails
334       except ImportError:
335          arizonareport.send_syslog(arizonareport.ERR, "getfiles(): Error on importing download_indicator.")
336          prog_indicator_module = None
337    else:
338       prog_indicator_module = None
339
340    # if there is no file needing to be fetched
341    if filelist == []:
342       arizonareport.send_syslog(arizonareport.ERR, "getfiles(): No files needed to be downloaded.")
343       return (False, downloaded_files)
344
345    if not os.path.exists(temp_dir):
346       arizonageneral.makedirs_existok(temp_dir)
347
348    # With prioritylist provided from configuration, go through transfer method list
349    # until download every files requested
350    arizonareport.send_out(4, "[DEBUG] prioritylist = " + str(prioritylist))
351    for element in prioritylist:
352       arizonareport.send_out(3, "[" + username + "] Attempting download via: " + str(element))
353       # if no transfer method is initialized
354       if init_status == -1:
355          initialize_transfer_method(element)
356
357       if arizonafetch == None:
358          init_status = -1
359          continue
360
361       # try to retrieve files using retrieve_files func of each module
362       arizonareport.send_out(3, "[" + username + "] Downloading via " + arizonafetch.transfer_name() + ": " + ", ".join(filenames))
363       try:
364          __enable_timeout(__compute_timeout(filelist))
365
366          (check, retrieved_files) = arizonafetch.retrieve_files(host, filelist, temp_dir, prog_indicator_module)
367
368          if len(retrieved_files) > 0:
369             retrieved_names = [item['filename'] for item in retrieved_files]
370             arizonareport.send_out(3, "[" + username + "] Retrieved: " + ", ".join(retrieved_names))
371             ravenlib.stats.update("azsucc_" + arizonafetch.transfer_name())
372          else:
373             ravenlib.stats.update("azfail_" + arizonafetch.transfer_name())
374             arizonareport.send_out(3, "[" + username + "] "+str(element)+" failed to retrieve any files, trying next method.")
375
376          __disable_timeout()
377       except:
378          __disable_timeout()
379          ravenlib.stats.update("azerr_" + arizonafetch.transfer_name())
380          arizonareport.send_out(3, "[" +username+"] "+str(element)+": error: "+ str( sys.exc_info()[0] ) )
381          check = False
382          retrieved_files = []
383
384       #print "[DEBUG] (arizonatransfer.py) (", check, ",", retrieved_files, ")"
385
386       # check indicates if retrieve_files call succeeded or not
387       if not check:
388          arizonareport.send_syslog(arizonareport.INFO, "getfiles(): Transfer method failed.")
389          # if it fails, move to the next method.
390          init_status = -1
391          continue
392       # now a file has been downloaded
393       else :
394          # from the list of retrieved files
395          for thefile in retrieved_files:
396             dest_filename = os.path.join(destdir, thefile['filename'])
397             filename = os.path.join(temp_dir, thefile['filename'])
398             expected_hash = thefile.get('hash', None)
399             hashfuncs = thefile.get('hashfuncs', [default_hashfunc])
400
401             if arizonaconfig.get_option("disabletransferhashcheck") or ignoreHash:
402                expected_hash = None
403
404             # default to assuming the hash check is ok, reset to false if we
405             # find otherwise
406             hash_check_flag = 0
407
408             arizonareport.send_error(4, "[DEBUG] hashcode = " + str(expected_hash))
409             if (not expected_hash) and (not ignoreHash):
410                # TWH: don't emit this line; we already know this.
411                # arizonareport.send_out(3, "[" + username + "] File doesn't have a hash: " + str(filename))
412                hash_check_flag = 1
413                pass
414             else:
415                arizonareport.send_out(4, "transferhashcheck start")
416
417                expected_hash = expected_hash.strip()
418
419                # hashfuncs is a list of possible hash functions, so try them all
420
421                actual_hash = None
422                for hashfunc in hashfuncs:
423                   if not hash_check_flag:
424                      try:
425                         actual_hash = hashfunc(filename).strip()
426                      except:
427                         arizonareport.send_error(4, "[DEBUG] arizonatransfer.getfiles: hash func " + str(hashfunc) + " failed: " + "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])))
428
429                      # if we want to ignore the hash, then just make sure the next condition passes
430                      if ignoreHash:
431                         actual_hash = expected_hash
432
433                      # if we have a match, then break out of the loop
434                      if actual_hash == expected_hash:
435                         arizonareport.send_out(4, "[DEBUG] hash check succeeded")
436                         hash_check_flag = 1
437
438                if not hash_check_flag:
439                   arizonareport.send_error(2, "\n   Hash of the downloaded file " + str(filename) + " (" + str(actual_hash) + ") did not match the expected hash (" + str(expected_hash) + ")")
440                   hash_check_flag = 0
441                   try:
442                      os.unlink(filename)
443                   except:
444                      pass
445
446                arizonareport.send_out(4, "transferhashcheck stop")
447
448             if hash_check_flag:
449                arizonareport.send_syslog(arizonareport.INFO, '\n"' + filename.split(temp_dir + '/')[1] + '" transferred by ' + arizonafetch.transfer_name())
450
451                # remove downloaded files from fetch_list
452                filelist.remove(thefile)
453
454                # remove the destination file, so we can overwrite it
455                try:
456                   os.remove(dest_filename)
457                except:
458                   pass
459
460                arizonareport.send_out(4, "[DEBUG] shutil.move(" + filename +  ", " +  dest_filename + ")")
461                try:
462                   shutil.move(filename, dest_filename)
463                except OSError:
464                   arizonareport.send_syslog(arizonareport.ERR, "getfiles(): error moving `" + filename + "' to `" + dest + "'")
465                   return (False, downloaded_files)
466
467                if createhashfile and actual_hash:
468                   try:
469                      f = open(dest_filename + ".metahash", "w")
470                      f.write(actual_hash)
471                      f.close()
472                   except:  # TODO: better exception handling
473                      arizonareport.send_error(3, "failed to create .metahash file")
474
475                # add downloaded files to downloaded_files
476                downloaded_files.append(dest_filename)
477
478       # check whether every file is downloaded
479       if filelist == []:
480          arizonareport.send_syslog(arizonareport.INFO, "File = " + ", ".join(downloaded_files) + "\ntransfered")
481          shutil.rmtree(temp_dir)
482          return (True, downloaded_files)
483       # still there are files haven't yet downloaded, so move to next method
484       elif not len(downloaded_files) == numoflist:
485          arizonareport.send_syslog(arizonareport.INFO, "Some files have not been downloaded yet.  Trying the next transfer method.")
486          __close_transfer()
487          continue
488       else:
489          arizonareport.send_syslog(arizonareport.ERR, "getfiles(): " + element +
490                   " file transfer has failed.  Trying the next transfer method.")
491          __close_transfer()
492          continue
493
494    # after trying every transfer method
495    arizonareport.send_syslog(arizonareport.ERR, "\ngetfiles(): Every transfer method has failed.\n")
496    __close_transfer()
497    shutil.rmtree(temp_dir)
498    return (False, downloaded_files)
499
500
501
502
503
504 def sync_remote_dir(host, destdir, prog_indicator=0, metafile_signature_key=None, hashfuncs=[default_hashfunc], maskList=[]):
505    """
506    <Purpose>
507       This synchronizes files between target directory in host and
508       destination directory.
509
510    <Arguments>
511       host:
512          'host' holds two things, a server name and target directory.
513          For example, if you want to retrieve files from '/tmp/' directory
514          in 'quadrus.cs.arizona.edu' server, the 'host' will be
515          'quadrus.cs.arizona.edu/tmp'.  'host' should be a string.
516          *** The host directory must contain a metafile ***
517
518       destdir:
519          'destdir' is a destination directory which will be synchronized.
520
521       prog_indicator:
522          If it is non-zero, this program will show a progress bar while
523          downloading, with the given width. Default value is 0 (no
524          indicator is shown).
525
526       metafile_signature_key:
527          The key that is expected to have signed the metafile for this repository.
528          If None, then the metafile will not be required to be signed.
529
530    <Exceptions>
531       None.
532
533    <Side Effects>
534       None
535
536    <Returns>
537       A tuple: (result, grabbed_files, all_files)
538
539       True or False to indicate success, a list of downloaded files, and a list
540       of all files on the server.
541
542       If the metafile_signature_key was provided but the signature is invalid
543       or was not signed with this key, then no files will be downloaded and
544       result will be False.
545    """
546
547    # check to see if we have an existing metafile that is within the cache
548    # time limit. If we do, then do not bother to retrieve a new one.
549    useCachedMetaFile = False
550    metafile_path = os.path.join(destdir, METAFILE_FN)
551    if os.path.exists(metafile_path):
552       mtime = os.stat(metafile_path)[ST_MTIME]
553       elapsed = abs(int(mtime - time.time()))
554       if elapsed < arizonaconfig.get_option("metafilecachetime"):
555          arizonareport.send_out(3, "Using cached metafile (" + str(elapsed) + ") seconds old")
556          useCachedMetaFile = True
557
558    if not useCachedMetaFile:
559       # Fetch a metafile...
560       # getfile will check that the validity of host, metafile, and destdir
561       # if any of them is incorrect, return_value will be false
562       if metafile_signature_key and (not arizonaconfig.get_option("disablesignedmetafile")):
563          metafile_dict = {"filename": SIGNED_METAFILE_FN, "hashfuncs": hashfuncs}
564          # when getting the metafile, only use http or ftp. enforce this by
565          # passing a custom prioritylist to getfiles1.
566          metaprioritylist = arizonaconfig.get_option("metatransfermethod")
567          (return_value, file_list) = getfiles(host, [metafile_dict], destdir, prog_indicator=prog_indicator, prioritylist=metaprioritylist)
568          if not return_value:
569             arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): Unable to retrieve " + SIGNED_METAFILE_FN + " from " + host)
570             return (False, [], [])
571
572          signedmetafile_path = os.path.join(destdir, SIGNED_METAFILE_FN)
573
574          try:
575              arizonareport.send_out(4, "rootsigcheck start")
576
577              # verify signature in the sig file
578              try:
579                 # SMB: ignoredisablexmlsigcheck is set to true, because we are
580                 # toggling this signature check with --disablesignedmetafile
581                 arizonacrypt.XML_validate_file(signedmetafile_path, None, publickey_string=metafile_signature_key, ignoredisablexmlsigcheck=True)
582              except TypeError:
583                 arizonareport.send_out(1, "Invalid signature in " + SIGNED_METAFILE_FN + " from " + host)
584                 arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): Invalid signature in " + SIGNED_METAFILE_FN + " from " + host)
585                 return (False, [], [])
586
587              arizonareport.send_out(4, "rootsigcheck stop")
588
589              # extract the metafile
590              try:
591                 metafile_tmp_fn = arizonacrypt.XML_retrieve_originalfile_from_signedfile(signedmetafile_path)
592              except TypeError:
593                 arizonareport.send_out(1, "Unable to extract metafile from " + SIGNED_METAFILE_FN + " from " + host)
594                 arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): Unable to extract metafile from " + SIGNED_METAFILE_FN + " from " + host)
595                 return (False, [], [])
596
597              f = file(metafile_tmp_fn, 'r')
598              sig_file_contents = f.read()
599              f.close
600
601          except IOError, (errno, strerror):
602              arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): I/O error(" + str(errno) + "): " + str(strerror))
603              return (False, [], [])
604
605          arizonareport.send_out(3, "[DEBUG] signed metafile validated from host " + host)
606
607          shutil.copy(metafile_tmp_fn, metafile_path)
608
609       else:
610          arizonareport.send_out(2, "No metafile signature key, metafile signature not being checked for " + host)
611          metafile_dict = {"filename": METAFILE_FN, "hashfuncs": hashfuncs}
612          metaprioritylist = arizonaconfig.get_option("metatransfermethod")
613          (return_value, file_list) = getfiles(host, [metafile_dict], destdir, prog_indicator=prog_indicator, prioritylist=metaprioritylist)
614          if not return_value:
615             arizonareport.send_syslog(arizonareport.ERR, 'sync_remote_dir(): Error in retrieving metafile')
616             return (False, [], [])
617
618    (result, remote_list) = determine_remote_files(host, destdir, hashfuncs, maskList)
619    if not result:
620        return (result, [], [])
621
622    fetch_list = []
623    grabbed_files = []
624    all_files = []
625    for file in remote_list:
626        if file['need_dl']:
627            fetch_list.append(file)
628        all_files.append(file['localfilename'])
629
630    # if nothing needs to be downloaded
631    if fetch_list == []:
632       arizonareport.send_syslog(arizonareport.INFO, "\nNo files need to be fetched.")
633    else :
634       # get the files which needs to be downloaded
635       #TODO pass the expected file sizes and limit the downloads by those sizes
636       (return_value, grabbed_files) = getfiles(host, fetch_list, destdir, prog_indicator, True)
637       # fails to get files from host
638       if not return_value and grabbed_files == []:
639          arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): Failed to retrieve files.")
640          return (False, grabbed_files, all_files)
641
642    # if we retrieve every file needed
643    if len(fetch_list) == len(grabbed_files):
644       return (True, grabbed_files, all_files)
645    # if we retrieve some of files
646    else:
647       arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): Failed to retrieve all files.")
648       return (False, grabbed_files, all_files)
649
650
651
652
653 def determine_remote_files(name, destdir, hashfuncs=[default_hashfunc], maskList=[]):
654    """
655    <Purpose>
656       Cracks open a metafile, determines the names of the files referenced
657       from that metafile, and checks to make sure they are signed
658       correctly.
659
660       An unsigned metafile is assumed to exist at destdir/METAFILE_FN
661
662    <Arguments>
663       name:
664          'name' of the remote thing we're synchronizing. The only purpose
665          of this parameter is as text info for the user; It's suggested to
666          use the same name as the 'host' parameter that is supplied to
667          sync_remote_files(), but not absoletely necessary.
668
669       destdir:
670          'destdir' is a destination directory which will be synchronized.
671
672    <Exceptions>
673       None.
674
675    <Side Effects>
676       None
677
678    <Returns>
679       A tuple: (result, file_list)
680
681       True or False to indicate success, a list of downloaded files, and a list
682       of all files on the server.
683    """
684
685    metafile_path = os.path.join(destdir, METAFILE_FN)
686
687    fetch_list = []
688
689    if not os.path.exists(metafile_path):
690        arizonareport.send_error(arizonareport.ERR, "determine_remote_files(): file " + str(metafile_path) + " does not exist")
691        arizonareport.send_syslog(arizonareport.ERR, "determine_remote_files(): file " + str(metafile_path) + " does not exist")
692        return (False, fetch_list)
693
694    mtime = os.stat(metafile_path)[ST_MTIME]
695    arizonareport.send_out(1, "Using metadata " + name + ", timestamp " + time.ctime(mtime))
696
697    # Open the file we just retrieved
698    arizonareport.send_out(4, "[DEBUG] opening " + metafile_path)
699    try:
700       dir_file = open(metafile_path)
701    # if a file cannot be opened
702    except IOError, (errno, strerror):
703       arizonareport.send_error(arizonareport.ERR, "determine_remote_files(): I/O error(" + str(errno) + "): " + str(strerror))
704       arizonareport.send_syslog(arizonareport.ERR, "determine_remote_files(): I/O error(" + str(errno) + "): " + str(strerror))
705       return (False, fetch_list)
706
707    # for each line in the metafile, check to make sure the local file is okay
708    # each line has two string; the first one is filename, and second one is hash
709    # go through every file and check if each file exist in the destdir
710    # and the hash of files in the destdir match the hash from metafile
711    # if it doesn't satisfy, then add the file to fetch_list to be retrieved
712    for line in dir_file:
713       # TWH: ignore blank lines
714       if len(line.strip()) == 0:
715          continue
716       # Split the file's line into pieces
717       line_dat = line.split()
718       if len(line_dat) < 2:
719          # invalid line in the meta file
720          arizonareport.send_syslog(arizonareport.ERR, "sync_remote_dir(): The format of metafile is incorrect")
721          return (False, fetch_list)
722
723       # split a line into filename, filehash, and filesize
724       filename = line_dat[0]
725       expectedhash = line_dat[1].strip()
726       if len(line_dat) >= 3:
727           filesize = line_dat[2]
728       else:
729           filesize = None
730       localfilename = os.path.join(destdir, filename)
731       arizonareport.send_out(4, "[DEBUG] file: " + localfilename)
732       arizonareport.send_out(4, "[DEBUG] expected hash: " + expectedhash)
733
734       if maskList:
735           matched = False
736           for mask in maskList:
737               if fnmatch.fnmatch(filename, mask):
738                   matched = True
739           if (not matched):
740               continue
741
742       status = None
743
744       dict = {'filename': filename,
745               'hash': expectedhash,
746               'hashfuncs': hashfuncs,
747               'need_dl': True,
748               'localfilename': localfilename}
749
750       # if this file has already been downloaded and checked, it will have
751       # a filename.metahash file.. look for it
752       if dict['need_dl']:
753           if os.path.isfile(localfilename + ".metahash"):
754              # open it and compare the hash
755              f = open(localfilename + ".metahash")
756              precomputedhash = f.read().strip()
757              f.close()
758              arizonareport.send_out(4, "[DEBUG] precomputed hash: " + precomputedhash)
759              if precomputedhash == expectedhash:
760                 arizonareport.send_out(4, "[DEBUG] precomputed hash matched")
761                 # The hash matched so try the next file...
762                 dict["pre_hash_matched"] = True
763                 dict["need_dl"] = False
764              else:
765                 dict["pre_hash_matched"] = False
766
767       # if dict['need_dl'] is still set, then we didn't match a .metahash file,
768       # so now we need to compute and check hashes
769       if dict['need_dl']:
770           # if a file looking for is in the destination directory
771           if os.path.isfile(localfilename):
772              # check our hash functions one at a time, until we find one that
773              # matches
774              dict["hash_matched"] = False
775              for hashfunc in hashfuncs:
776                  actualhash = hashfunc(localfilename)
777                  if actualhash == expectedhash:
778                     arizonareport.send_out(4, "[DEBUG] hash matched")
779
780                     # The hash matched, so mark this file as not needing download
781                     dict["hash_matched"] = True
782                     dict["need_dl"] = False
783
784                     # Create a metahash file, so we don't have to check again next time
785                     open(filename + ".metahash", "w").write(actualhash + "\n")
786
787                     # stop checking hashes; we have succeeded
788                     break
789
790       fetch_list.append(dict)
791
792    return (True, fetch_list)
793
794
795
796
797
798
799 def __checkFileList(checklist):
800    """
801    <Purpose>
802       This checks the given list and removes empty elements from the list.
803
804    <Arguments>
805       checklist:
806          The list to be checked, should contain file tuples.
807
808    <Exceptions>
809       None.
810
811    <Side Effects>
812       None.
813
814    <Returns>
815       The list that empty elements are removed
816    """
817
818    checked_list = []
819    for item in checklist:
820       if (item != None) and ('filename' in item) and (item['filename']):
821          checked_list.append(item)
822
823    return checked_list
824
825
826
827
828
829 def __close_transfer() :
830    """
831    <Purpose>
832       This closes the currently using transfer method
833    
834    <Arguments>
835       None
836
837    <Exceptions>
838       None
839
840    <Side Effects>
841       set init_status as -1
842
843    <Returns>
844       None
845    """
846
847    global init_status  
848    if arizonafetch != None: 
849       arizonafetch.close_transfer_program()
850    init_status = -1
851
852
853
854