HWRF  trunk@4391
hwrf_scrub.py
1 #! /usr/bin/env python
2 
3 import logging, os, shutil, sys
5 
6 from produtil.log import jlogger
7 
8 ##@namespace ush.hwrf_scrub
9 # @brief A utility script that deletes directories, but adds safeguards and logging.
10 #
11 # This script deletes directories, and is called as follows:
12 # @code{.sh}
13 # hwrf_scrub.py /directory/one [/directory/two [...]]
14 # @endcode
15 #
16 # It will recursively delete up to thirty directories and will log
17 # messages as it goes. It will refuse to delete the following
18 # directories:
19 # * /
20 # * any filesystem mount point
21 # * $USHhwrf, $EXhwrf, $PARMhwrf, $HOMEhwrf
22 # * $FIXgsi, $FIXhwrf
23 
24 class WillNotDelete(Exception):
25  """!Raised by various safety checks if someone tries to delete
26  something they should not, such as "/"."""
27 
28 class Deleter(object):
29  """!Recursive directory deleter with safeguards to prevent
30  accidental deletion of certain critical directories."""
31  def __init__(self,logger):
32  """!Constructor for Deleter
33  @param logger a logging.Logger for log messages"""
34  self.__logger=logger
35  self.__rmtrees=set()
36  self.__rmdirs=set()
37  self.badflag=False
38  ##@var badflag
39  # If True, then at least one directory had trouble being deleted
40 
41  @property
42  def logger(self):
43  """!Returns the logging.Logger used for log messages"""
44  return self.__logger
45 
46  def validate_path(self,norm):
47  """!Checks to see if the given path is one that should not be
48  deleted.
49 
50  Raises WillNotDelete if the given directory is one of these:
51  * /
52  * a mount point
53  * Any of $HOMEhwrf, $USHhwrf, $EXhwrf, $PARMhwrf, $FIXhwrf, or $FIXgsi
54 
55  @param norm the path to check"""
56  if not os.path.exists(norm):
57  return # cannot validate if it does not exist
58  if os.path.ismount(norm):
59  raise WillNotDelete('%s: is a mount point (fs root)'%(norm,))
60  if os.path.samefile('/',norm):
61  raise WillNotDelete('%s: is same as /'%(norm,))
62  for var in ( 'HOMEhwrf', 'USHhwrf', 'EXhwrf', 'PARMhwrf',
63  'FIXhwrf', 'FIXgsi' ):
64  if var in os.environ:
65  vardir=os.environ[var]
66  if vardir=='': continue
67  if os.path.samefile(os.environ[var],norm):
68  raise WillNotDelete('%s: same as $%s'%(norm,var))
69 
70  def add(self,dirname):
71  """!Adds a directory to the list to be deleted. The directory
72  is passed through various safeguards first.
73  @param dirname the directory to delete"""
74  norm=produtil.fileop.norm_expand_path(dirname,fullnorm=True)
75  self.logger.info('%s: normalizes to %s'%(dirname,norm))
76  self.validate_path(norm)
77  self.logger.info('%s: will recursively delete this'%(norm,))
78 
79  parent=os.path.dirname(dirname)
80  self.logger.info('%s: will rmdir this if it is empty'%(parent,))
81 
82  self.__rmdirs.add(parent)
83  self.__rmtrees.add(dirname)
84 
85  def _rmtree_onerr(self,function,path,exc_info):
86  """!Internal function used to log errors.
87 
88  This is an internal implementation function called by
89  shutil.rmtree when an underlying function call failed. See
90  the Python documentation of shutil.rmtree for details.
91  @param function the funciton that failed
92  @param path the path to the function that caused problems
93  @param exc_info the exception information
94  @post self.badflag=True
95  @protected"""
96  self.logger.warning('%s: %s failed: %s'%(
97  str(path),str(function),str(exc_info)))
98  self.badflag=True
99 
100  def rmtree(self,tree):
101  """!Deletes the tree, if possible.
102  @protected
103  @param tree the directory tree to delete"""
104  try:
105  # If it is a file, special file or symlink we can just
106  # delete it via unlink:
107  os.unlink(tree)
108  return
109  except EnvironmentError as e:
110  pass
111  # We get here for directories.
112  self.logger.info('%s: rmtree'%(tree,))
113  shutil.rmtree(tree,ignore_errors=False,onerror=self._rmtree_onerr)
114 
115  def have_dirs(self):
116  """!Are there any directories to delete (ones passed to add())
117  @returns the number of directories to delete"""
118  return len(self.__rmdirs)>0
119 
120  def swap_dirs(self):
121  """!Returns the list of directories to delete and clears the
122  internal list."""
123  dirs=self.__rmdirs
124  self.__rmdirs=set()
125  return dirs
126 
127  def go(self,max_rmdir_loop=30):
128  """!Deletes all directories sent to add()
129 
130  @param max_rmdir_loop The maximum number of directories to
131  delete before returning. This is a safeguard against
132  accidents."""
133  logger=self.logger
134  logger.info('Delete files: first pass.')
135  self.badflag=False
136  for tree in self.__rmtrees:
137  self.rmtree(tree)
138 
139  if self.badflag:
140  logger.warning('Some deletions failed. Will try again.')
141  logger.info('Delete files: second pass.')
142  self.badflag=False
143  for tree in self.__rmtrees:
144  if os.path.exists(tree):
145  self.rmtree(tree)
146  else:
147  logger.info('%s: already gone.'%(tree,))
148 
149  if self.badflag:
150  logger.error('Could not delete files after two tries.')
151 
152  logger.info('Remove parent and ancestor directories.')
153  iloop=0
154  while iloop<max_rmdir_loop and self.have_dirs():
155  iloop+=1
156  dirs=self.swap_dirs()
157  for dir in dirs:
158  try:
159  logger.info('%s: rmdir'%(dir,))
160  os.rmdir(dir)
161  except EnvironmentError as e:
162  logger.info('%s: cannot remove: %s'%(dir,repr(e)))
163  continue
164  try:
165  parent=os.path.dirname(dir)
166  self.__rmdirs.add(parent)
167  except EnvironmentError as e:
168  logger.warning('%s: cannot determine parent'%(dir,))
169  continue # can ignore this error
170  if iloop>=max_rmdir_loop:
171  logger.warning(
172  'Hit maximum loop count of %d. Some ancestor directories '
173  'may still exist.'%(max_rmdir_loop,))
174 
175 def main():
176  """!Main program: parses arguments, sends them to Deleter.add() and calls Deleter.go()"""
177  logger=logging.getLogger('hwrf_scrub')
178  scrubber=Deleter(logger)
179  for arg in sys.argv[1:]:
180  scrubber.add(arg)
181  scrubber.go()
182 
183 if __name__=='__main__':
184  try:
186  main()
187  except Exception as e:
188  jlogger.critical('HWRF scrubber is aborting: '+str(e),exc_info=True)
189  sys.exit(2)
190 
This module provides a set of utility functions to do filesystem operations.
Definition: fileop.py:1
Contains setup(), which initializes the produtil package.
Definition: setup.py:1
badflag
If True, then at least one directory had trouble being deleted.
Definition: hwrf_scrub.py:37
def validate_path(self, norm)
Checks to see if the given path is one that should not be deleted.
Definition: hwrf_scrub.py:46
def rmtree(self, tree)
Deletes the tree, if possible.
Definition: hwrf_scrub.py:100
Recursive directory deleter with safeguards to prevent accidental deletion of certain critical direct...
Definition: hwrf_scrub.py:28
def setup(ignore_hup=False, dbnalert_logger=None, jobname=None, cluster=None, send_dbn=None, thread_logger=False, thread_stack=2 **24, kwargs)
Initializes the produtil package.
Definition: setup.py:15
def add(self, dirname)
Adds a directory to the list to be deleted.
Definition: hwrf_scrub.py:70
def norm_expand_path
Normalizes path and expand home directories.
Definition: fileop.py:910
def logger(self)
Returns the logging.Logger used for log messages.
Definition: hwrf_scrub.py:42
Configures logging.
Definition: log.py:1
def swap_dirs(self)
Returns the list of directories to delete and clears the internal list.
Definition: hwrf_scrub.py:120
def have_dirs(self)
Are there any directories to delete (ones passed to add())
Definition: hwrf_scrub.py:115
def _rmtree_onerr(self, function, path, exc_info)
Internal function used to log errors.
Definition: hwrf_scrub.py:85
Raised by various safety checks if someone tries to delete something they should not, such as "/".
Definition: hwrf_scrub.py:24
def go
Deletes all directories sent to add()
Definition: hwrf_scrub.py:127
def __init__(self, logger)
Constructor for Deleter.
Definition: hwrf_scrub.py:31