+def security_check_homedir(location):
+ """
+ Performs a check against a directory to determine if current
+ directory's owner has a home directory that is a parent directory.
+ This protects against malicious mountpoints, and is roughly equivalent
+ to the suexec checks.
+ """
+ try:
+ uid = util.get_dir_uid(location)
+ real = os.path.realpath(location)
+ if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
+ logging.error("Security check failed, owner of deployment and "
+ "owner of home directory mismatch for %s" % location)
+ return False
+ except KeyError:
+ logging.error("Security check failed, could not look up "
+ "owner of %s (uid %d)" % (location, uid))
+ return False
+ except OSError as e:
+ logging.error("OSError: %s" % str(e))
+ return False
+ return True
+
+def calculate_log_name(log_dir, i):
+ """
+ Calculates a log entry given a numeric identifier, and
+ directory under operation.
+ """
+ return os.path.join(log_dir, "%04d.log" % i)
+
+def create_logdir(log_dir):
+ """
+ Creates a log directory and chmods it 777 to enable de-priviledged
+ processes to create files.
+ """
+ try:
+ os.mkdir(log_dir)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+ #if create_subdirs:
+ # log_dir = os.path.join(log_dir, str(int(time.time())))
+ # os.mkdir(log_dir) # if fails, be fatal
+ # # XXX: update last symlink
+ os.chmod(log_dir, 0o777)
+
+class Report(object):
+ #: Set of indices that should be skipped
+ skip = None
+ #: Dict of append names to counts. You should manually increment these as necessary
+ fails = None
+ #: Names of the files objects
+ names = None
+ def __init__(self, names, fobjs, skip, fails):
+ self.skip = skip
+ self.names = names
+ self.fails = fails
+ for name, fobj in zip(names, fobjs):
+ setattr(self, name, fobj)
+ def flush(self):
+ for n in self.names:
+ getattr(self, n).flush()
+
+def report_files(log_dir, names):
+ return [os.path.join(os.path.join(log_dir, "%s.txt" % x)) for x in names]
+
+def read_reports(log_dir, names):
+ """
+ Reads a number of reports files. The return value is a :class:`Report`
+ object with attributes that are open file objects that correspond to ``names``.
+ """
+ return Report(names, [(os.path.exists(f) and open(f, "r") or cStringIO.StringIO()) for f in report_files(log_dir, names)], set(), {})
+
+def open_reports(log_dir, names=('warnings', 'errors'), redo=False, append_names=()):
+ """
+ Returns a :class:`Report` object configured appropriately for the
+ parameters passed. This object has attributes names + append_names which
+ contain file objects opened as "w". ``names`` report files are cleared unconditionally
+ when they are opened (i.e. are not preserved from run to run.) ``append_names``
+ report files are not cleared unless ``redo`` is True, and persist over
+ runs: assuming the convention that [0001] is the index of the deployment,
+ the ``skip`` attribute on the returned report object contains indexes that
+ should be skipped.
+ """
+ skip = set()
+ fails = {}
+ if not redo:
+ rr = read_reports(log_dir, append_names)
+ def build_set(skip, fails, name, fobj):
+ lines = fobj.read().strip().splitlines()
+ skip |= set(int(l[1:5]) for l in lines)
+ fails[name] = len(lines)
+ fobj.close()
+ for name in append_names:
+ build_set(skip, fails, name, getattr(rr, name))
+ else:
+ names += append_names
+ for name in append_names:
+ fails[name] = 0
+ append_names = ()
+ files = report_files(log_dir, names)
+ append_files = report_files(log_dir, append_names)
+ # backup old reports
+ old_reports = os.path.join(log_dir, "old-reports")
+ rundir = os.path.join(old_reports, "run")
+ if not os.path.exists(old_reports):
+ os.mkdir(old_reports)
+ else:
+ util.safe_unlink(rundir)
+ for f in files:
+ if os.path.exists(f):
+ os.rename(f, rundir)
+ for f in append_files:
+ if os.path.exists(f):
+ shutil.copy(f, rundir)
+ return Report(names + append_names, [open(f, "w") for f in files] + [open(f, "a") for f in append_files], skip, fails)
+