Exemple #1
0
def S_ISGITLINK(m):
    return (stat.S_IFMT(m) == S_IFGITLINK)
Exemple #2
0
 def match(self, dirname, filename, fstat):
     return stat.S_IFMT(fstat[stat.ST_MODE]) in self.ftypes
 def _isdir_attr(self, item):
     # Return whether an item in sftp.listdir_attr results is a directory
     if item.st_mode is not None:
         return stat.S_IFMT(item.st_mode) == stat.S_IFDIR
     else:
         return False
Exemple #4
0
        self.fd = os.open(path, os.O_RDONLY|O_LARGEFILE|O_NOFOLLOW|os.O_NDELAY)
        
    def __del__(self):
        if self.fd:
            fd = self.fd
            self.fd = None
            os.close(fd)

    def fchdir(self):
        os.fchdir(self.fd)

    def stat(self):
        return xstat.fstat(self.fd)


_IFMT = stat.S_IFMT(0xffffffff)  # avoid function call in inner loop
def _dirlist():
    l = []
    for n in os.listdir('.'):
        try:
            st = xstat.lstat(n)
        except OSError as e:
            add_error(Exception('%s: %s' % (resolve_parent(n), str(e))))
            continue
        if (st.st_mode & _IFMT) == stat.S_IFDIR:
            n += '/'
        l.append((n,st))
    l.sort(reverse=True)
    return l

Exemple #5
0
	def fileModeStr(mode):
		modestr = stat.S_IFMT(mode) and stat_info.filetypeChr(mode) or ''
		modestr += stat_info.permissionGroupStr((mode >> 6) & stat.S_IRWXO, mode & stat.S_ISUID, 's')
		modestr += stat_info.permissionGroupStr((mode >> 3) & stat.S_IRWXO, mode & stat.S_ISGID, 's')
		modestr += stat_info.permissionGroupStr(mode & stat.S_IRWXO, mode & stat.S_ISVTX, 't')
		return modestr
Exemple #6
0
class dircmp:
    """A class that manages the comparison of 2 directories.

    dircmp(a,b,ignore=None,hide=None)
      A and B are directories.
      IGNORE is a list of names to ignore,
        defaults to ['RCS', 'CVS', 'tags'].
      HIDE is a list of names to hide,
        defaults to [os.curdir, os.pardir].

    High level usage:
      x = dircmp(dir1, dir2)
      x.report() -> prints a report on the differences between dir1 and dir2
       or
      x.report_partial_closure() -> prints report on differences between dir1
            and dir2, and reports on common immediate subdirectories.
      x.report_full_closure() -> like report_partial_closure,
            but fully recursive.

    Attributes:
     left_list, right_list: The files in dir1 and dir2,
        filtered by hide and ignore.
     common: a list of names in both dir1 and dir2.
     left_only, right_only: names only in dir1, dir2.
     common_dirs: subdirectories in both dir1 and dir2.
     common_files: files in both dir1 and dir2.
     common_funny: names in both dir1 and dir2 where the type differs between
        dir1 and dir2, or the name is not stat-able.
     same_files: list of identical files.
     diff_files: list of filenames which differ.
     funny_files: list of files which could not be compared.
     subdirs: a dictionary of dircmp objects, keyed by names in common_dirs.
     """
    def __init__(self, a, b, ignore=None, hide=None):  # Initialize
        self.left = a
        self.right = b
        if hide is None:
            self.hide = [os.curdir, os.pardir]  # Names never to be shown
        else:
            self.hide = hide
        if ignore is None:
            self.ignore = ['RCS', 'CVS', 'tags']  # Names ignored in comparison
        else:
            self.ignore = ignore

    def phase0(self):  # Compare everything except common subdirectories
        self.left_list = _filter(os.listdir(self.left),
                                 self.hide + self.ignore)
        self.right_list = _filter(os.listdir(self.right),
                                  self.hide + self.ignore)
        self.left_list.sort()
        self.right_list.sort()

    __p4_attrs = ('subdirs', )
    __p3_attrs = ('same_files', 'diff_files', 'funny_files')
    __p2_attrs = ('common_dirs', 'common_files', 'common_funny')
    __p1_attrs = ('common', 'left_only', 'right_only')
    __p0_attrs = ('left_list', 'right_list')

    def __getattr__(self, attr):
        if attr in self.__p4_attrs:
            self.phase4()
        elif attr in self.__p3_attrs:
            self.phase3()
        elif attr in self.__p2_attrs:
            self.phase2()
        elif attr in self.__p1_attrs:
            self.phase1()
        elif attr in self.__p0_attrs:
            self.phase0()
        else:
            raise AttributeError, attr
        return getattr(self, attr)

    def phase1(self):  # Compute common names
        a_only, b_only = [], []
        common = {}
        b = {}
        for fnm in self.right_list:
            b[fnm] = 1
        for x in self.left_list:
            if b.get(x, 0):
                common[x] = 1
            else:
                a_only.append(x)
        for x in self.right_list:
            if common.get(x, 0):
                pass
            else:
                b_only.append(x)
        self.common = common.keys()
        self.left_only = a_only
        self.right_only = b_only

    def phase2(self):  # Distinguish files, directories, funnies
        self.common_dirs = []
        self.common_files = []
        self.common_funny = []

        for x in self.common:
            a_path = os.path.join(self.left, x)
            b_path = os.path.join(self.right, x)

            ok = 1
            try:
                a_stat = statcache.stat(a_path)
            except os.error, why:
                # print 'Can\'t stat', a_path, ':', why[1]
                ok = 0
            try:
                b_stat = statcache.stat(b_path)
            except os.error, why:
                # print 'Can\'t stat', b_path, ':', why[1]
                ok = 0

            if ok:
                a_type = stat.S_IFMT(a_stat[stat.ST_MODE])
                b_type = stat.S_IFMT(b_stat[stat.ST_MODE])
                if a_type != b_type:
                    self.common_funny.append(x)
                elif stat.S_ISDIR(a_type):
                    self.common_dirs.append(x)
                elif stat.S_ISREG(a_type):
                    self.common_files.append(x)
                else:
                    self.common_funny.append(x)
            else:
                self.common_funny.append(x)
Exemple #7
0
def _filetype(v):
    return stat.S_IFMT(v.stat.st_mode)
def compare_files(file1, file2, skipped_types=()):
	"""
	Compare metadata and contents of two files.

	@param file1: File 1
	@type file1: str
	@param file2: File 2
	@type file2: str
	@param skipped_types: Tuple of strings specifying types of properties excluded from comparison.
		Supported strings: type, mode, owner, group, device_number, xattr, atime, mtime, ctime, size, content
	@type skipped_types: tuple of str
	@rtype: tuple of str
	@return: Tuple of strings specifying types of properties different between compared files
	"""

	file1_stat = os.lstat(_unicode_encode(file1, encoding=_encodings["fs"], errors="strict"))
	file2_stat = os.lstat(_unicode_encode(file2, encoding=_encodings["fs"], errors="strict"))

	differences = []

	if (file1_stat.st_dev, file1_stat.st_ino) == (file2_stat.st_dev, file2_stat.st_ino):
		return ()

	if "type" not in skipped_types and stat.S_IFMT(file1_stat.st_mode) != stat.S_IFMT(file2_stat.st_mode):
		differences.append("type")
	if "mode" not in skipped_types and stat.S_IMODE(file1_stat.st_mode) != stat.S_IMODE(file2_stat.st_mode):
		differences.append("mode")
	if "owner" not in skipped_types and file1_stat.st_uid != file2_stat.st_uid:
		differences.append("owner")
	if "group" not in skipped_types and file1_stat.st_gid != file2_stat.st_gid:
		differences.append("group")
	if "device_number" not in skipped_types and file1_stat.st_rdev != file2_stat.st_rdev:
		differences.append("device_number")

	if "xattr" not in skipped_types and sorted(xattr.get_all(file1, nofollow=True)) != sorted(xattr.get_all(file2, nofollow=True)):
		differences.append("xattr")

	if "atime" not in skipped_types and file1_stat.st_atime_ns != file2_stat.st_atime_ns:
		differences.append("atime")
	if "mtime" not in skipped_types and file1_stat.st_mtime_ns != file2_stat.st_mtime_ns:
		differences.append("mtime")
	if "ctime" not in skipped_types and file1_stat.st_ctime_ns != file2_stat.st_ctime_ns:
		differences.append("ctime")

	if "type" in differences:
		pass
	elif file1_stat.st_size != file2_stat.st_size:
		if "size" not in skipped_types:
			differences.append("size")
		if "content" not in skipped_types:
			differences.append("content")
	else:
		if "content" not in skipped_types:
			if stat.S_ISLNK(file1_stat.st_mode):
				file1_stream = io.BytesIO(os.readlink(_unicode_encode(file1,
									encoding=_encodings["fs"],
									errors="strict")))
			else:
				file1_stream = open(_unicode_encode(file1,
							encoding=_encodings["fs"],
							errors="strict"), "rb")
			if stat.S_ISLNK(file2_stat.st_mode):
				file2_stream = io.BytesIO(os.readlink(_unicode_encode(file2,
									encoding=_encodings["fs"],
									errors="strict")))
			else:
				file2_stream = open(_unicode_encode(file2,
							encoding=_encodings["fs"],
							errors="strict"), "rb")
			while True:
				file1_content = file1_stream.read(4096)
				file2_content = file2_stream.read(4096)
				if file1_content != file2_content:
					differences.append("content")
					break
				if not file1_content or not file2_content:
					break
			file1_stream.close()
			file2_stream.close()

	return tuple(differences)
Exemple #9
0
 def _check_type(self, type):
     st = self.stat(False)
     return stat.S_IFMT(st.st_mode) == type
Exemple #10
0
def get_file_metadata(path, hashes):
    """
    Get a generator for the metadata of the file at system path @path.

    The generator yields, in order:
    1. A boolean indicating whether the file exists.
    2. st_dev, if the file exists.
    3. Tuple of (S_IFMT(st_mode), file type as string), if the file
       exists.
    4. st_size, if the file exists and is a regular file. Note that
       it may be 0 on some filesystems, so treat the value with caution.
    5. st_mtime, if the file exists and is a regular file.
    6. A dict of @hashes and their values, if the file exists and is
       a regular file. Special __size__ member is added unconditionally.

    Note that the generator acquires resources, and does not release
    them until terminated. Always make sure to pull it until
    StopIteration, or close it explicitly.
    """

    try:
        # we want O_NONBLOCK to avoid blocking when opening pipes
        fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
    except OSError as err:
        if err.errno == errno.ENOENT:
            exists = False
            opened = False
        elif err.errno == errno.ENXIO:
            # unconnected device or socket
            exists = True
            opened = False
        else:
            raise
    else:
        exists = True
        opened = True

    try:
        # 1. does it exist?
        yield exists

        # we can't provide any more data for a file that does not exist
        if not exists:
            return

        if opened:
            st = os.fstat(fd)
        else:
            st = os.stat(path)

        # 2. st_dev
        yield st.st_dev

        # 3. file type tuple
        if stat.S_ISREG(st.st_mode):
            ftype = 'regular file'
        elif stat.S_ISDIR(st.st_mode):
            ftype = 'directory'
        elif stat.S_ISCHR(st.st_mode):
            ftype = 'character device'
        elif stat.S_ISBLK(st.st_mode):
            ftype = 'block device'
        elif stat.S_ISFIFO(st.st_mode):
            ftype = 'named pipe'
        elif stat.S_ISSOCK(st.st_mode):
            ftype = 'UNIX socket'
        else:
            ftype = 'unknown'
        yield (stat.S_IFMT(st.st_mode), ftype)

        if not stat.S_ISREG(st.st_mode):
            if opened:
                os.close(fd)
            return

        # 4. st_size
        yield st.st_size

        # 5. st_mtime
        yield st.st_mtime

        f = io.open(fd, 'rb')
    except:
        if opened:
            os.close(fd)
        raise

    with f:
        # open() might have left the file as O_NONBLOCK
        # make sure to fix that
        fcntl.fcntl(fd, fcntl.F_SETFL, 0)

        # 5. checksums
        e_hashes = sorted(hashes)
        hashes = list(gemato.manifest.manifest_hashes_to_hashlib(e_hashes))
        e_hashes.append('__size__')
        hashes.append('__size__')
        checksums = gemato.hash.hash_file(f, hashes)

        ret = {}
        for ek, k in zip(e_hashes, hashes):
            ret[ek] = checksums[k]
        yield ret
Exemple #11
0
def _cp(source,
        target,
        force,
        recursive,
        preserve,
        log,
        verbose,
        __recursion=0):
    # '__recursion' is an internal var used to track if this is a recursive
    # call to this function or not
    DEBUG = False
    if DEBUG:
        print("_cp(source=%r, target=%r, force=%r, recursive=%r, "\
              "preserve=%r, log, verbose=%r)"\
              % (source[0], target[0], force, recursive, preserve, verbose))
    spath, sstat = source
    smode = sstat.st_mode
    tpath, tstat = target

    if stat.S_ISREG(smode):
        if not __recursion and tstat and stat.S_ISDIR(tstat.st_mode):
            tpath = os.path.join(tpath, _basename(spath))
            try:
                tstat = os.stat(tpath)
            except OSError:
                tstat = None
            target = (tpath, tstat)
        if tstat:
            if _samefile(spath, tpath):
                raise OSError(
                    0, "`%s' and `%s' are the same file" % (spath, tpath),
                    tpath)
            elif stat.S_ISDIR(tstat.st_mode):
                raise OSError(
                    0, "cannot overwrite directory `%s' with non-directory" %
                    tpath, spath)
        if not os.access(spath, os.R_OK):
            raise OSError(0,
                          "cannot open source for reading: permission denied",
                          spath)
        if tstat and not os.access(tpath, os.W_OK):
            # Note: There is where GNU 'cp -i ...' would catch
            # "Permission denied" and offer:
            #   cp: overwrite `<target>', overriding mode 0444?
            if force:
                os.chmod(tpath, 511)  # octal 777
                os.remove(tpath)
                tstat = None
                target = (tpath, tstat)
            else:
                raise OSError(
                    0, "cannot open target for writing: permission denied",
                    tpath)

        if log and verbose:
            log("`%s' -> `%s'", spath, tpath)
        fsrc = open(spath, 'rb')
        try:
            ftarget = open(tpath, 'wb')
            try:
                #XXX Should this be done in chunks?
                ftarget.write(fsrc.read())
            finally:
                ftarget.close()
        finally:
            fsrc.close()

        # Rules for setting permissions:
        # - if preserve is true: then preserve
        # - if target already existed: don't change permissions
        # - otherwise: set perms to perm(source) & ~umask
        if preserve:
            os.chmod(tpath, stat.S_IMODE(smode))
            os.utime(tpath, (sstat.st_atime, sstat.st_mtime))
        elif not tstat:  # i.e. the target did not exist before the copy
            perm = stat.S_IMODE(smode) & ~_getumask()
            os.chmod(tpath, perm)

    elif stat.S_ISDIR(smode):
        if not recursive:
            raise OSError(0, "must specify 'recursive' to copy a directory",
                          spath)
        if not __recursion and tstat and stat.S_ISDIR(tstat.st_mode):
            tpath = os.path.join(tpath, _basename(spath))
            try:
                tstat = os.stat(tpath)
            except OSError:
                tstat = None
            target = (tpath, tstat)

        # Get list of files to copy over before creation of target dir
        # to avoid infinite loop if copying dir into itself.
        subfiles = os.listdir(spath)

        if not tstat:
            if log and verbose:
                log("`%s' -> `%s'", spath, tpath)
            os.mkdir(tpath)

        # Set attributes properly.
        if preserve:
            os.chmod(tpath, stat.S_IMODE(smode))
            os.utime(tpath, (sstat.st_atime, sstat.st_mtime))
        elif not tstat:  # i.e. the target did not exist before the copy
            perm = stat.S_IMODE(smode) & ~_getumask()
            os.chmod(tpath, perm)

        for subfile in subfiles:
            subsource_path = os.path.join(spath, subfile)
            subsource = (subsource_path, os.stat(subsource_path))
            subtarget_path = os.path.join(tpath, subfile)
            try:
                subtarget_stat = os.stat(subtarget_path)
            except OSError:
                subtarget_stat = None
            subtarget = (subtarget_path, subtarget_stat)
            _cp(subsource,
                subtarget,
                force,
                recursive,
                preserve,
                log,
                verbose,
                __recursion=1)

    elif stat.S_ISLNK(smode):
        raise NotImplementedError(
            "don't yet know how to copy symbolic links: `%s'" % spath)
    elif stat.S_ISCHR(smode):
        raise NotImplementedError(
            "don't yet know how to copy character special device files: `%s'" %
            spath)
    elif stat.S_ISBLK(smode):
        raise NotImplementedError(
            "don't yet know how to copy block special device files: `%s'" %
            spath)
    elif stat.S_ISFIFO(smode):
        raise NotImplementedError(
            "don't yet know how to copy a FIFO (named pipe): `%s'" % spath)
    elif stat.S_ISSOCK(smode):
        raise NotImplementedError("don't yet know how to copy a socket: `%s'" %
                                  spath)
    else:
        raise NotImplementedError("unknown file type: `%s' (mode bits: %s)" %
                                  (spath, oct(stat.S_IFMT(smode))))
Exemple #12
0
 def _get_st_mode(path):
     # stat the path for file type, or None if path doesn't exist
     try:
         return stat.S_IFMT(os.stat(path).st_mode)
     except OSError:
         return None
Exemple #13
0
 def _is_dir(self, check_dir):
     return stat.S_IFMT(self.ftp.stat(check_dir).st_mode) == stat.S_IFDIR
Exemple #14
0
 def unpack_mode(mode):
     return types_inv.get(stat.S_IFMT(mode), 'file'), stat.S_IMODE(mode)
Exemple #15
0
def _dict_to_sig(dct):
    return (stat.S_IFMT(dct['st_mode']), dct['st_size'], dct['st_mtime'])
Exemple #16
0
def rpm_verify_file(fileinfo, rpmlinktos, omitmask):
    """
        Verify all the files in a package.

        Returns a list of error flags, the file type and file name.  The list
        entries are strings that are the same as the labels for the bitwise
        flags used in the C code.

    """
    (fname, fsize, fmode, fmtime, fflags, frdev, finode, fnlink, fstate, \
            vflags, fuser, fgroup, fmd5) = fileinfo

    # 1. rpmtsRootDir stuff.  What does it do and where to I get it from?

    file_results = []
    flags = vflags

    # Check to see if the file was installed - if not pretend all is ok.
    # This is what the rpm C code does!
    if fstate != rpm.RPMFILE_STATE_NORMAL:
        return file_results

    # Get the installed files stats
    try:
        lstat = os.lstat(fname)
    except OSError:
        if not (fflags & (rpm.RPMFILE_MISSINGOK | rpm.RPMFILE_GHOST)):
            file_results.append('RPMVERIFY_LSTATFAIL')
            #file_results.append(fname)
        return file_results

    # 5. Contexts?  SELinux stuff?

    # Setup what checks to do.  This is straight out of the C code.
    if stat.S_ISDIR(lstat.st_mode):
        flags &= DIR_FLAGS
    elif stat.S_ISLNK(lstat.st_mode):
        flags &= LINK_FLAGS
    elif stat.S_ISFIFO(lstat.st_mode):
        flags &= FIFO_FLAGS
    elif stat.S_ISCHR(lstat.st_mode):
        flags &= CHR_FLAGS
    elif stat.S_ISBLK(lstat.st_mode):
        flags &= BLK_FLAGS
    else:
        flags &= REG_FLAGS

    if (fflags & rpm.RPMFILE_GHOST):
        flags &= GHOST_FLAGS

    flags &= ~(omitmask | RPMVERIFY_FAILURES)

    # 8. SELinux stuff.

    prelink_size = 0
    if flags & RPMVERIFY_MD5:
        prelink_md5, prelink_size = prelink_md5_check(fname)
        if prelink_md5 == False:
            file_results.append('RPMVERIFY_MD5')
            file_results.append('RPMVERIFY_READFAIL')
        elif prelink_md5 != fmd5:
            file_results.append('RPMVERIFY_MD5')

    if flags & RPMVERIFY_LINKTO:
        linkto = os.readlink(fname)
        if not linkto:
            file_results.append('RPMVERIFY_READLINKFAIL')
            file_results.append('RPMVERIFY_LINKTO')
        else:
            if len(rpmlinktos) == 0 or linkto != rpmlinktos:
                file_results.append('RPMVERIFY_LINKTO')

    if flags & RPMVERIFY_FILESIZE:
        if not (flags & RPMVERIFY_MD5):  # prelink check hasn't been done.
            prelink_size = prelink_size_check(fname)
        if (prelink_size != 0):  # This is a prelinked file.
            if (prelink_size != fsize):
                file_results.append('RPMVERIFY_FILESIZE')
        elif lstat.st_size != fsize:  # It wasn't a prelinked file.
            file_results.append('RPMVERIFY_FILESIZE')

    if flags & RPMVERIFY_MODE:
        metamode = fmode
        filemode = lstat.st_mode

        # Comparing the type of %ghost files is meaningless, but perms are ok.
        if fflags & rpm.RPMFILE_GHOST:
            metamode &= ~0xf000
            filemode &= ~0xf000

        if (stat.S_IFMT(metamode) != stat.S_IFMT(filemode)) or \
           (stat.S_IMODE(metamode) != stat.S_IMODE(filemode)):
            file_results.append('RPMVERIFY_MODE')

    if flags & RPMVERIFY_RDEV:
        if (stat.S_ISCHR(fmode) != stat.S_ISCHR(lstat.st_mode)
                or stat.S_ISBLK(fmode) != stat.S_ISBLK(lstat.st_mode)):
            file_results.append('RPMVERIFY_RDEV')
        elif (s_isdev(fmode) & s_isdev(lstat.st_mode)):
            st_rdev = lstat.st_rdev
            if frdev != st_rdev:
                file_results.append('RPMVERIFY_RDEV')

    if flags & RPMVERIFY_MTIME:
        if lstat.st_mtime != fmtime:
            file_results.append('RPMVERIFY_MTIME')

    if flags & RPMVERIFY_USER:
        try:
            user = pwd.getpwuid(lstat.st_uid)[0]
        except KeyError:
            user = None
        if not user or not fuser or (user != fuser):
            file_results.append('RPMVERIFY_USER')

    if flags & RPMVERIFY_GROUP:
        try:
            group = grp.getgrgid(lstat.st_gid)[0]
        except KeyError:
            group = None
        if not group or not fgroup or (group != fgroup):
            file_results.append('RPMVERIFY_GROUP')

    return file_results
Exemple #17
0
def _sig(st):
    return (stat.S_IFMT(st[stat.ST_MODE]), st[stat.ST_SIZE], st[stat.ST_MTIME])
Exemple #18
0
        def install(self, pkgplan, orig):
                """Client-side method that installs a directory."""

                mode = None
                try:
                        mode = int(self.attrs.get("mode", None), 8)
                except (TypeError, ValueError):
                        # Mode isn't valid, so let validate raise a more
                        # informative error.
                        self.validate(fmri=pkgplan.destination_fmri)

                omode = oowner = ogroup = None
                owner, group = self.get_fsobj_uid_gid(pkgplan,
                        pkgplan.destination_fmri)
                if orig:
                        try:
                                omode = int(orig.attrs.get("mode", None), 8)
                        except (TypeError, ValueError):
                                # Mode isn't valid, so let validate raise a more
                                # informative error.
                                orig.validate(fmri=pkgplan.origin_fmri)
                        oowner, ogroup = orig.get_fsobj_uid_gid(pkgplan,
                            pkgplan.origin_fmri)

                path = os.path.normpath(os.path.sep.join((
                    pkgplan.image.get_root(), self.attrs["path"])))

                # Don't allow installation through symlinks.
                self.fsobj_checkpath(pkgplan, path)

                # XXX Hack!  (See below comment.)
                if not portable.is_admin():
                        mode |= stat.S_IWUSR

                if not orig:
                        try:
                                self.makedirs(path, mode=mode,
                                    fmri=pkgplan.destination_fmri)
                        except OSError, e:
                                if e.filename != path:
                                        # makedirs failed for some component
                                        # of the path.
                                        raise

                                fs = os.lstat(path)
                                fs_mode = stat.S_IFMT(fs.st_mode)
                                if e.errno == errno.EROFS:
                                        # Treat EROFS like EEXIST if both are
                                        # applicable, since we'll end up with
                                        # EROFS instead.
                                        if stat.S_ISDIR(fs_mode):
                                                return
                                        raise
                                elif e.errno != errno.EEXIST:
                                        raise

                                if stat.S_ISLNK(fs_mode):
                                        # User has replaced directory with a
                                        # link, or a package has been poorly
                                        # implemented.  It isn't safe to
                                        # simply re-create the directory as
                                        # that won't restore the files that
                                        # are supposed to be contained within.
                                        err_txt = _("Unable to create "
                                            "directory %s; it has been "
                                            "replaced with a link.  To "
                                            "continue, please remove the "
                                            "link or restore the directory "
                                            "to its original location and "
                                            "try again.") % path
                                        raise apx.ActionExecutionError(
                                            self, details=err_txt, error=e,
                                            fmri=pkgplan.destination_fmri)
                                elif stat.S_ISREG(fs_mode):
                                        # User has replaced directory with a
                                        # file, or a package has been poorly
                                        # implemented.  Salvage what's there,
                                        # and drive on.
                                        pkgplan.image.salvage(path)
                                        os.mkdir(path, mode)
                                elif stat.S_ISDIR(fs_mode):
                                        # The directory already exists, but
                                        # ensure that the mode matches what's
                                        # expected.
                                        os.chmod(path, mode)
Exemple #19
0
 def test_stat_dir(self):
     if not hasattr(self.ch, 'sftp'):
         return
     self.assertEqual(stat.S_IFMT(self.ch.sftp.stat(self.rtd).st_mode),
                      stat.S_IFDIR)
Exemple #20
0
def lsLine(name, s):
    """
    Build an 'ls' line for a file ('file' in its generic sense, it
    can be of any type).
    """
    mode = s.st_mode
    perms = array.array("B", b"-" * 10)
    ft = stat.S_IFMT(mode)
    if stat.S_ISDIR(ft):
        perms[0] = ord("d")
    elif stat.S_ISCHR(ft):
        perms[0] = ord("c")
    elif stat.S_ISBLK(ft):
        perms[0] = ord("b")
    elif stat.S_ISREG(ft):
        perms[0] = ord("-")
    elif stat.S_ISFIFO(ft):
        perms[0] = ord("f")
    elif stat.S_ISLNK(ft):
        perms[0] = ord("l")
    elif stat.S_ISSOCK(ft):
        perms[0] = ord("s")
    else:
        perms[0] = ord("!")
    # User
    if mode & stat.S_IRUSR:
        perms[1] = ord("r")
    if mode & stat.S_IWUSR:
        perms[2] = ord("w")
    if mode & stat.S_IXUSR:
        perms[3] = ord("x")
    # Group
    if mode & stat.S_IRGRP:
        perms[4] = ord("r")
    if mode & stat.S_IWGRP:
        perms[5] = ord("w")
    if mode & stat.S_IXGRP:
        perms[6] = ord("x")
    # Other
    if mode & stat.S_IROTH:
        perms[7] = ord("r")
    if mode & stat.S_IWOTH:
        perms[8] = ord("w")
    if mode & stat.S_IXOTH:
        perms[9] = ord("x")
    # Suid/sgid
    if mode & stat.S_ISUID:
        if perms[3] == ord("x"):
            perms[3] = ord("s")
        else:
            perms[3] = ord("S")
    if mode & stat.S_ISGID:
        if perms[6] == ord("x"):
            perms[6] = ord("s")
        else:
            perms[6] = ord("S")

    if isinstance(name, bytes):
        name = name.decode("utf-8")
    lsPerms = perms.tobytes()
    lsPerms = lsPerms.decode("utf-8")

    lsresult = [
        lsPerms,
        str(s.st_nlink).rjust(5),
        " ",
        str(s.st_uid).ljust(9),
        str(s.st_gid).ljust(9),
        str(s.st_size).rjust(8),
        " ",
    ]
    # Need to specify the month manually, as strftime depends on locale
    ttup = localtime(s.st_mtime)
    sixmonths = 60 * 60 * 24 * 7 * 26
    if s.st_mtime + sixmonths < time():  # Last edited more than 6mo ago
        strtime = strftime("%%s %d  %Y ", ttup)
    else:
        strtime = strftime("%%s %d %H:%M ", ttup)
    lsresult.append(strtime % (_MONTH_NAMES[ttup[1]], ))

    lsresult.append(name)
    return "".join(lsresult)
        try:
            lstat = os.lstat(path)
        except OSError, e:
            if e.errno == errno.ENOENT:
                errors.append(
                    _("Missing: %s does not exist") % ftype_to_name(ftype))
            elif e.errno == errno.EACCES:
                errors.append(_("Skipping: Permission denied"))
            else:
                errors.append(_("Unexpected Error: %s") % e)
            abort = True

        if abort:
            return lstat, errors, warnings, info, abort

        if ftype is not None and ftype != stat.S_IFMT(lstat.st_mode):
            errors.append(
                _("File Type: '%(found)s' should be "
                  "'%(expected)s'") % {
                      "found": ftype_to_name(stat.S_IFMT(lstat.st_mode)),
                      "expected": ftype_to_name(ftype)
                  })
            abort = True

        if owner is not None and lstat.st_uid != owner:
            errors.append(
                _("Owner: '%(found_name)s "
                  "(%(found_id)d)' should be '%(expected_name)s "
                  "(%(expected_id)d)'") % {
                      "found_name": img.get_name_by_uid(lstat.st_uid, True),
                      "found_id": lstat.st_uid,
Exemple #22
0
 def _get_type_from_stat(cls, _stat):
     # type: (os.stat_result) -> ResourceType
     """Get the resource type from an `os.stat_result` object."""
     st_mode = _stat.st_mode
     st_type = stat.S_IFMT(st_mode)
     return cls.STAT_TO_RESOURCE_TYPE.get(st_type, ResourceType.unknown)
Exemple #23
0
 def chmod(self, path, perm):
     p = self.getfile(path)
     if p == False:
         raise OSError(errno.ENOENT, os.strerror(errno.ENOENT))
     p[A_MODE] = stat.S_IFMT(p[A_MODE]) | perm
Exemple #24
0
 def inode_type(self):
     return InodeType(stat.S_IFMT(self.status['st_mode'])).value
Exemple #25
0
def _sig(st):
    return (stat.S_IFMT(st.st_mode), st.st_size, st.st_mtime)
Exemple #26
0
class dircmp:
    """A class that manages the comparison of 2 directories.

    dircmp(a,b,ignore=None,hide=None)
      A and B are directories.
      IGNORE is a list of names to ignore,
        defaults to ['RCS', 'CVS', 'tags'].
      HIDE is a list of names to hide,
        defaults to [os.curdir, os.pardir].

    High level usage:
      x = dircmp(dir1, dir2)
      x.report() -> prints a report on the differences between dir1 and dir2
       or
      x.report_partial_closure() -> prints report on differences between dir1
            and dir2, and reports on common immediate subdirectories.
      x.report_full_closure() -> like report_partial_closure,
            but fully recursive.

    Attributes:
     left_list, right_list: The files in dir1 and dir2,
        filtered by hide and ignore.
     common: a list of names in both dir1 and dir2.
     left_only, right_only: names only in dir1, dir2.
     common_dirs: subdirectories in both dir1 and dir2.
     common_files: files in both dir1 and dir2.
     common_funny: names in both dir1 and dir2 where the type differs between
        dir1 and dir2, or the name is not stat-able.
     same_files: list of identical files.
     diff_files: list of filenames which differ.
     funny_files: list of files which could not be compared.
     subdirs: a dictionary of dircmp objects, keyed by names in common_dirs.
     """
    def __init__(self, a, b, ignore=None, hide=None):  # Initialize
        self.left = a
        self.right = b
        if hide is None:
            self.hide = [os.curdir, os.pardir]  # Names never to be shown
        else:
            self.hide = hide
        if ignore is None:
            self.ignore = ['RCS', 'CVS', 'tags']  # Names ignored in comparison
        else:
            self.ignore = ignore

    def phase0(self):  # Compare everything except common subdirectories
        self.left_list = _filter(os.listdir(self.left),
                                 self.hide + self.ignore)
        self.right_list = _filter(os.listdir(self.right),
                                  self.hide + self.ignore)
        self.left_list.sort()
        self.right_list.sort()

    def phase1(self):  # Compute common names
        a = dict(izip(imap(os.path.normcase, self.left_list), self.left_list))
        b = dict(izip(imap(os.path.normcase, self.right_list),
                      self.right_list))
        self.common = map(a.__getitem__, ifilter(b.__contains__, a))
        self.left_only = map(a.__getitem__, ifilterfalse(b.__contains__, a))
        self.right_only = map(b.__getitem__, ifilterfalse(a.__contains__, b))

    def phase2(self):  # Distinguish files, directories, funnies
        self.common_dirs = []
        self.common_files = []
        self.common_funny = []

        for x in self.common:
            a_path = os.path.join(self.left, x)
            b_path = os.path.join(self.right, x)

            ok = 1
            try:
                a_stat = os.stat(a_path)
            except os.error, why:
                # print 'Can\'t stat', a_path, ':', why[1]
                ok = 0
            try:
                b_stat = os.stat(b_path)
            except os.error, why:
                # print 'Can\'t stat', b_path, ':', why[1]
                ok = 0

            if ok:
                a_type = stat.S_IFMT(a_stat.st_mode)
                b_type = stat.S_IFMT(b_stat.st_mode)
                if a_type != b_type:
                    self.common_funny.append(x)
                elif stat.S_ISDIR(a_type):
                    self.common_dirs.append(x)
                elif stat.S_ISREG(a_type):
                    self.common_files.append(x)
                else:
                    self.common_funny.append(x)
            else:
                self.common_funny.append(x)
Exemple #27
0
def _compare_files(file1, stat1, file2, stat2):
    """
    Compares two files and their file stats.

    Those files are compared for type, link numbers, size and/or content,
    access mode, uid, gid.

    The result is a list of explained differences, empty if there are no
    differences.
    """
    differences = []

    if stat.S_IFMT(stat1.st_mode) != stat.S_IFMT(stat2.st_mode):
        differences.append(
            "Paths {pa1} and {pa2} have different types {ft1} vs. {ft2}".format(
                pa1=file1, pa2=file2,
                ft1=stat.S_IFMT(stat1.st_mode),
                ft2=stat.S_IFMT(stat2.st_mode)))
        # if the files don't have the same type, there is no point comparing
        # them further...
        return differences

    if stat.S_IMODE(stat1.st_mode) != stat.S_IMODE(stat2.st_mode):
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "access rights {ar1} vs. {ar2}".format(
                pa1=file1, pa2=file2,
                ar1=stat.S_IMODE(stat1.st_mode),
                ar2=stat.S_IMODE(stat2.st_mode)))

    if stat1.st_nlink != stat2.st_nlink:
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "link numbers {ln1} vs. {ln2}".format(
                pa1=file1, pa2=file2, ln1=stat1.st_nlink, ln2=stat2.st_nlink))

    if stat1.st_size != stat2.st_size:
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "file sizes {fs1} vs. {fs2}".format(
                pa1=file1, pa2=file2, fs1=stat1.st_size, fs2=stat2.st_size))
    elif stat.S_ISREG(stat1.st_mode) and stat.S_ISREG(stat2.st_mode):
        with open(file1) as fd1, open(file2) as fd2:
            content1 = fd1.read()
            content2 = fd2.read()
        if content1 != content2:
            if len(content1) > 75:
                content1 = content1[:36] + "..." + content1[-36:]
            if len(content2) > 75:
                content2 = content2[:36] + "..." + content2[-36:]
            differences.append(
                "Paths {pa1} and {pa2} have different "
                "regular file' contents '{rc1}' vs. '{rc2}'".format(
                    pa1=file1, pa2=file2, rc1=content1, rc2=content2))

    # we compare only the modification seconds, because rdiff-backup doesn't
    # save milliseconds.
    if int(stat1.st_mtime) != int(stat2.st_mtime):
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "modification times {mt1} vs. {mt2}".format(
                pa1=file1, pa2=file2,
                mt1=int(stat1.st_mtime), mt2=int(stat2.st_mtime)))

    if stat1.st_uid != stat2.st_uid:
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "user owners {uo1} vs. {uo2}".format(
                pa1=file1, pa2=file2, uo1=stat1.st_uid, uo2=stat2.st_uid))

    if stat1.st_gid != stat2.st_gid:
        differences.append(
            "Paths {pa1} and {pa2} have different "
            "group owners {go1} vs. {go2}".format(
                pa1=file1, pa2=file2, go1=stat1.st_uid, go2=stat2.st_uid))

    return differences
Exemple #28
0
def is_device(mode):
    mode = stat.S_IFMT(mode)
    return stat.S_ISCHR(mode) or stat.S_ISBLK(mode)
Exemple #29
0
def main(argv):

    # Hack around lack of nonlocal vars in python 2
    _nonlocal = {}

    o = options.Options(optspec)
    opt, flags, extra = o.parse_bytes(argv[1:])

    if opt.indexfile:
        opt.indexfile = argv_bytes(opt.indexfile)
    if opt.name:
        opt.name = argv_bytes(opt.name)
    if opt.remote:
        opt.remote = argv_bytes(opt.remote)
    if opt.strip_path:
        opt.strip_path = argv_bytes(opt.strip_path)

    git.check_repo_or_die()
    if not (opt.tree or opt.commit or opt.name):
        o.fatal("use one or more of -t, -c, -n")
    if not extra:
        o.fatal("no filenames given")

    extra = [argv_bytes(x) for x in extra]

    opt.progress = (istty2 and not opt.quiet)
    opt.smaller = parse_num(opt.smaller or 0)
    if opt.bwlimit:
        client.bwlimit = parse_num(opt.bwlimit)

    if opt.date:
        date = parse_date_or_fatal(opt.date, o.fatal)
    else:
        date = time.time()

    if opt.strip and opt.strip_path:
        o.fatal("--strip is incompatible with --strip-path")

    graft_points = []
    if opt.graft:
        if opt.strip:
            o.fatal("--strip is incompatible with --graft")

        if opt.strip_path:
            o.fatal("--strip-path is incompatible with --graft")

        for (option, parameter) in flags:
            if option == "--graft":
                parameter = argv_bytes(parameter)
                splitted_parameter = parameter.split(b'=')
                if len(splitted_parameter) != 2:
                    o.fatal(
                        "a graft point must be of the form old_path=new_path")
                old_path, new_path = splitted_parameter
                if not (old_path and new_path):
                    o.fatal("a graft point cannot be empty")
                graft_points.append(
                    (resolve_parent(old_path), resolve_parent(new_path)))

    is_reverse = environ.get(b'BUP_SERVER_REVERSE')
    if is_reverse and opt.remote:
        o.fatal("don't use -r in reverse mode; it's automatic")

    name = opt.name
    if name and not valid_save_name(name):
        o.fatal("'%s' is not a valid branch name" % path_msg(name))
    refname = name and b'refs/heads/%s' % name or None
    if opt.remote or is_reverse:
        try:
            cli = client.Client(opt.remote)
        except client.ClientError as e:
            log('error: %s' % e)
            sys.exit(1)
        oldref = refname and cli.read_ref(refname) or None
        w = cli.new_packwriter(compression_level=opt.compress)
    else:
        cli = None
        oldref = refname and git.read_ref(refname) or None
        w = git.PackWriter(compression_level=opt.compress)

    handle_ctrl_c()

    # Metadata is stored in a file named .bupm in each directory.  The
    # first metadata entry will be the metadata for the current directory.
    # The remaining entries will be for each of the other directory
    # elements, in the order they're listed in the index.
    #
    # Since the git tree elements are sorted according to
    # git.shalist_item_sort_key, the metalist items are accumulated as
    # (sort_key, metadata) tuples, and then sorted when the .bupm file is
    # created.  The sort_key should have been computed using the element's
    # mangled name and git mode (after hashsplitting), but the code isn't
    # actually doing that but rather uses the element's real name and mode.
    # This makes things a bit more difficult when reading it back, see
    # vfs.ordered_tree_entries().

    # Maintain a stack of information representing the current location in
    # the archive being constructed.  The current path is recorded in
    # parts, which will be something like ['', 'home', 'someuser'], and
    # the accumulated content and metadata for of the dirs in parts is
    # stored in parallel stacks in shalists and metalists.

    parts = []  # Current archive position (stack of dir names).
    shalists = []  # Hashes for each dir in paths.
    metalists = []  # Metadata for each dir in paths.

    def _push(part, metadata):
        # Enter a new archive directory -- make it the current directory.
        parts.append(part)
        shalists.append([])
        metalists.append([(b'', metadata)])  # This dir's metadata (no name).

    def _pop(force_tree, dir_metadata=None):
        # Leave the current archive directory and add its tree to its parent.
        assert (len(parts) >= 1)
        part = parts.pop()
        shalist = shalists.pop()
        metalist = metalists.pop()
        # FIXME: only test if collision is possible (i.e. given --strip, etc.)?
        if force_tree:
            tree = force_tree
        else:
            names_seen = set()
            clean_list = []
            metaidx = 1  # entry at 0 is for the dir
            for x in shalist:
                name = x[1]
                if name in names_seen:
                    parent_path = b'/'.join(parts) + b'/'
                    add_error('error: ignoring duplicate path %s in %s' %
                              (path_msg(name), path_msg(parent_path)))
                    if not stat.S_ISDIR(x[0]):
                        del metalist[metaidx]
                else:
                    names_seen.add(name)
                    clean_list.append(x)
                    if not stat.S_ISDIR(x[0]):
                        metaidx += 1

            if dir_metadata:  # Override the original metadata pushed for this dir.
                metalist = [(b'', dir_metadata)] + metalist[1:]
            sorted_metalist = sorted(metalist, key=lambda x: x[0])
            metadata = b''.join([m[1].encode() for m in sorted_metalist])
            metadata_f = BytesIO(metadata)
            mode, id = hashsplit.split_to_blob_or_tree(w.new_blob,
                                                       w.new_tree,
                                                       [metadata_f],
                                                       keep_boundaries=False)
            clean_list.append((mode, b'.bupm', id))

            tree = w.new_tree(clean_list)
        if shalists:
            shalists[-1].append((GIT_MODE_TREE,
                                 git.mangle_name(part, GIT_MODE_TREE,
                                                 GIT_MODE_TREE), tree))
        return tree

    _nonlocal['count'] = 0
    _nonlocal['subcount'] = 0
    _nonlocal['lastremain'] = None

    def progress_report(n):
        _nonlocal['subcount'] += n
        cc = _nonlocal['count'] + _nonlocal['subcount']
        pct = total and (cc * 100.0 / total) or 0
        now = time.time()
        elapsed = now - tstart
        kps = elapsed and int(cc / 1024. / elapsed)
        kps_frac = 10**int(math.log(kps + 1, 10) - 1)
        kps = int(kps / kps_frac) * kps_frac
        if cc:
            remain = elapsed * 1.0 / cc * (total - cc)
        else:
            remain = 0.0
        if (_nonlocal['lastremain'] and (remain > _nonlocal['lastremain']) and
            ((remain - _nonlocal['lastremain']) / _nonlocal['lastremain'] <
             0.05)):
            remain = _nonlocal['lastremain']
        else:
            _nonlocal['lastremain'] = remain
        hours = int(remain / 60 / 60)
        mins = int(remain / 60 - hours * 60)
        secs = int(remain - hours * 60 * 60 - mins * 60)
        if elapsed < 30:
            remainstr = ''
            kpsstr = ''
        else:
            kpsstr = '%dk/s' % kps
            if hours:
                remainstr = '%dh%dm' % (hours, mins)
            elif mins:
                remainstr = '%dm%d' % (mins, secs)
            else:
                remainstr = '%ds' % secs
        qprogress(
            'Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r' %
            (pct, cc / 1024, total / 1024, fcount, ftotal, remainstr, kpsstr))

    indexfile = opt.indexfile or git.repo(b'bupindex')
    r = index.Reader(indexfile)
    try:
        msr = index.MetaStoreReader(indexfile + b'.meta')
    except IOError as ex:
        if ex.errno != EACCES:
            raise
        log('error: cannot access %r; have you run bup index?' %
            path_msg(indexfile))
        sys.exit(1)
    hlink_db = hlinkdb.HLinkDB(indexfile + b'.hlink')

    def already_saved(ent):
        return ent.is_valid() and w.exists(ent.sha) and ent.sha

    def wantrecurse_pre(ent):
        return not already_saved(ent)

    def wantrecurse_during(ent):
        return not already_saved(ent) or ent.sha_missing()

    def find_hardlink_target(hlink_db, ent):
        if hlink_db and not stat.S_ISDIR(ent.mode) and ent.nlink > 1:
            link_paths = hlink_db.node_paths(ent.dev, ent.ino)
            if link_paths:
                return link_paths[0]

    total = ftotal = 0
    if opt.progress:
        for (transname, ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
            if not (ftotal % 10024):
                qprogress('Reading index: %d\r' % ftotal)
            exists = ent.exists()
            hashvalid = already_saved(ent)
            ent.set_sha_missing(not hashvalid)
            if not opt.smaller or ent.size < opt.smaller:
                if exists and not hashvalid:
                    total += ent.size
            ftotal += 1
        progress('Reading index: %d, done.\n' % ftotal)
        hashsplit.progress_callback = progress_report

    # Root collisions occur when strip or graft options map more than one
    # path to the same directory (paths which originally had separate
    # parents).  When that situation is detected, use empty metadata for
    # the parent.  Otherwise, use the metadata for the common parent.
    # Collision example: "bup save ... --strip /foo /foo/bar /bar".

    # FIXME: Add collision tests, or handle collisions some other way.

    # FIXME: Detect/handle strip/graft name collisions (other than root),
    # i.e. if '/foo/bar' and '/bar' both map to '/'.

    first_root = None
    root_collision = None
    tstart = time.time()
    fcount = 0
    lastskip_name = None
    lastdir = b''
    for (transname, ent) in r.filter(extra, wantrecurse=wantrecurse_during):
        (dir, file) = os.path.split(ent.name)
        exists = (ent.flags & index.IX_EXISTS)
        hashvalid = already_saved(ent)
        wasmissing = ent.sha_missing()
        oldsize = ent.size
        if opt.verbose:
            if not exists:
                status = 'D'
            elif not hashvalid:
                if ent.sha == index.EMPTY_SHA:
                    status = 'A'
                else:
                    status = 'M'
            else:
                status = ' '
            if opt.verbose >= 2:
                log('%s %-70s\n' % (status, path_msg(ent.name)))
            elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
                if not lastdir.startswith(dir):
                    log('%s %-70s\n' %
                        (status, path_msg(os.path.join(dir, b''))))
                lastdir = dir

        if opt.progress:
            progress_report(0)
        fcount += 1

        if not exists:
            continue
        if opt.smaller and ent.size >= opt.smaller:
            if exists and not hashvalid:
                if opt.verbose:
                    log('skipping large file "%s"\n' % path_msg(ent.name))
                lastskip_name = ent.name
            continue

        assert (dir.startswith(b'/'))
        if opt.strip:
            dirp = stripped_path_components(dir, extra)
        elif opt.strip_path:
            dirp = stripped_path_components(dir, [opt.strip_path])
        elif graft_points:
            dirp = grafted_path_components(graft_points, dir)
        else:
            dirp = path_components(dir)

        # At this point, dirp contains a representation of the archive
        # path that looks like [(archive_dir_name, real_fs_path), ...].
        # So given "bup save ... --strip /foo/bar /foo/bar/baz", dirp
        # might look like this at some point:
        #   [('', '/foo/bar'), ('baz', '/foo/bar/baz'), ...].

        # This dual representation supports stripping/grafting, where the
        # archive path may not have a direct correspondence with the
        # filesystem.  The root directory is represented by an initial
        # component named '', and any component that doesn't have a
        # corresponding filesystem directory (due to grafting, for
        # example) will have a real_fs_path of None, i.e. [('', None),
        # ...].

        if first_root == None:
            first_root = dirp[0]
        elif first_root != dirp[0]:
            root_collision = True

        # If switching to a new sub-tree, finish the current sub-tree.
        while parts > [x[0] for x in dirp]:
            _pop(force_tree=None)

        # If switching to a new sub-tree, start a new sub-tree.
        for path_component in dirp[len(parts):]:
            dir_name, fs_path = path_component
            # Not indexed, so just grab the FS metadata or use empty metadata.
            try:
                meta = metadata.from_path(fs_path, normalized=True) \
                    if fs_path else metadata.Metadata()
            except (OSError, IOError) as e:
                add_error(e)
                lastskip_name = dir_name
                meta = metadata.Metadata()
            _push(dir_name, meta)

        if not file:
            if len(parts) == 1:
                continue  # We're at the top level -- keep the current root dir
            # Since there's no filename, this is a subdir -- finish it.
            oldtree = already_saved(ent)  # may be None
            newtree = _pop(force_tree=oldtree)
            if not oldtree:
                if lastskip_name and lastskip_name.startswith(ent.name):
                    ent.invalidate()
                else:
                    ent.validate(GIT_MODE_TREE, newtree)
                ent.repack()
            if exists and wasmissing:
                _nonlocal['count'] += oldsize
            continue

        # it's not a directory
        if hashvalid:
            id = ent.sha
            git_name = git.mangle_name(file, ent.mode, ent.gitmode)
            git_info = (ent.gitmode, git_name, id)
            shalists[-1].append(git_info)
            sort_key = git.shalist_item_sort_key((ent.mode, file, id))
            meta = msr.metadata_at(ent.meta_ofs)
            meta.hardlink_target = find_hardlink_target(hlink_db, ent)
            # Restore the times that were cleared to 0 in the metastore.
            (meta.atime, meta.mtime, meta.ctime) = (ent.atime, ent.mtime,
                                                    ent.ctime)
            metalists[-1].append((sort_key, meta))
        else:
            id = None
            hlink = find_hardlink_target(hlink_db, ent)
            try:
                meta = metadata.from_path(ent.name,
                                          hardlink_target=hlink,
                                          normalized=True)
            except (OSError, IOError) as e:
                add_error(e)
                lastskip_name = ent.name
                continue
            if stat.S_IFMT(ent.mode) != stat.S_IFMT(meta.mode):
                # The mode changed since we indexed the file, this is bad.
                # This can cause two issues:
                # 1) We e.g. think the file is a regular file, but now it's
                #    something else (a device, socket, FIFO or symlink, etc.)
                #    and _read_ from it when we shouldn't.
                # 2) We then record it as valid, but don't update the index
                #    metadata, and on a subsequent save it has 'hashvalid'
                #    but is recorded as the file type from the index, when
                #    the content is something else ...
                # Avoid all of these consistency issues by just skipping such
                # things - it really ought to not happen anyway.
                add_error("%s: mode changed since indexing, skipping." %
                          path_msg(ent.name))
                lastskip_name = ent.name
                continue
            if stat.S_ISREG(ent.mode):
                try:
                    with hashsplit.open_noatime(ent.name) as f:
                        (mode, id) = hashsplit.split_to_blob_or_tree(
                            w.new_blob, w.new_tree, [f], keep_boundaries=False)
                except (IOError, OSError) as e:
                    add_error('%s: %s' % (ent.name, e))
                    lastskip_name = ent.name
            elif stat.S_ISDIR(ent.mode):
                assert (0)  # handled above
            elif stat.S_ISLNK(ent.mode):
                mode, id = (GIT_MODE_SYMLINK, w.new_blob(meta.symlink_target))
            else:
                # Everything else should be fully described by its
                # metadata, so just record an empty blob, so the paths
                # in the tree and .bupm will match up.
                (mode, id) = (GIT_MODE_FILE, w.new_blob(b''))

            if id:
                ent.validate(mode, id)
                ent.repack()
                git_name = git.mangle_name(file, ent.mode, ent.gitmode)
                git_info = (mode, git_name, id)
                shalists[-1].append(git_info)
                sort_key = git.shalist_item_sort_key((ent.mode, file, id))
                metalists[-1].append((sort_key, meta))

        if exists and wasmissing:
            _nonlocal['count'] += oldsize
            _nonlocal['subcount'] = 0

    if opt.progress:
        pct = total and _nonlocal['count'] * 100.0 / total or 100
        progress(
            'Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n' %
            (pct, _nonlocal['count'] / 1024, total / 1024, fcount, ftotal))

    while len(parts) > 1:  # _pop() all the parts above the root
        _pop(force_tree=None)
    assert (len(shalists) == 1)
    assert (len(metalists) == 1)

    # Finish the root directory.
    tree = _pop(
        force_tree=None,
        # When there's a collision, use empty metadata for the root.
        dir_metadata=metadata.Metadata() if root_collision else None)

    sys.stdout.flush()
    out = byte_stream(sys.stdout)

    if opt.tree:
        out.write(hexlify(tree))
        out.write(b'\n')
    if opt.commit or name:
        if compat.py_maj > 2:
            # Strip b prefix from python 3 bytes reprs to preserve previous format
            msgcmd = b'[%s]' % b', '.join(
                [repr(argv_bytes(x))[1:].encode('ascii') for x in argv])
        else:
            msgcmd = repr(argv)
        msg = b'bup save\n\nGenerated by command:\n%s\n' % msgcmd
        userline = (b'%s <%s@%s>' % (userfullname(), username(), hostname()))
        commit = w.new_commit(tree, oldref, userline, date, None, userline,
                              date, None, msg)
        if opt.commit:
            out.write(hexlify(commit))
            out.write(b'\n')

    msr.close()
    w.close()  # must close before we can update the ref

    if opt.name:
        if cli:
            cli.update_ref(refname, commit, oldref)
        else:
            git.update_ref(refname, commit, oldref)

    if cli:
        cli.close()

    if saved_errors:
        log('WARNING: %d errors encountered while saving.\n' %
            len(saved_errors))
        sys.exit(1)
  """

    file_path = utils.PathJoin(base_dir, filename)
    try:
        st = os.stat(file_path)
    except EnvironmentError, err:
        if not required:
            logging.info("Optional file '%s' under path '%s' is missing",
                         filename, base_dir)
            return None

        base.ThrowError("File '%s' under path '%s' is missing (%s)" %
                        (filename, base_dir, utils.ErrnoOrStr(err)))

    if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
        base.ThrowError("File '%s' under path '%s' is not a regular file" %
                        (filename, base_dir))

    if filename in constants.ES_SCRIPTS:
        if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
            base.ThrowError("File '%s' under path '%s' is not executable" %
                            (filename, base_dir))

    return file_path


def ExtStorageFromDisk(name, base_dir=None):
    """Create an ExtStorage instance from disk.

  This function will return an ExtStorage instance