Beispiel #1
0
	def __getattr__(self, attr):
		if attr == 'mtime':
			# use stat.ST_MTIME; accessing .st_mtime gets you a float
			# depending on the python version, and int(float) introduces
			# some rounding issues that aren't present for people using
			# the straight c api.
			# thus use the defacto python compatibility work around;
			# access via index, which guarantees you get the raw int.
			try:
				self.mtime = obj = os.stat(self.location)[stat.ST_MTIME]
			except OSError as e:
				if e.errno in (errno.ENOENT, errno.ESTALE):
					raise FileNotFound(self.location)
				elif e.errno == PermissionDenied.errno:
					raise PermissionDenied(self.location)
				raise
			return obj
		if not attr.islower():
			# we don't care to allow .mD5 as an alias for .md5
			raise AttributeError(attr)
		hashname = attr.upper()
		if hashname not in checksum.get_valid_checksum_keys():
			raise AttributeError(attr)
		val = checksum.perform_checksum(self.location, hashname)[0]
		setattr(self, attr, val)
		return val
Beispiel #2
0
	def addFile(self, ftype, fname, hashdict=None, ignoreMissing=False):
		""" Add entry to Manifest optionally using hashdict to avoid recalculation of hashes """
		if ftype == "AUX" and not fname.startswith("files/"):
			fname = os.path.join("files", fname)
		if not os.path.exists(self.pkgdir+fname) and not ignoreMissing:
			raise FileNotFound(fname)
		if not ftype in MANIFEST2_IDENTIFIERS:
			raise InvalidDataType(ftype)
		if ftype == "AUX" and fname.startswith("files"):
			fname = fname[6:]
		self.fhashdict[ftype][fname] = {}
		if hashdict != None:
			self.fhashdict[ftype][fname].update(hashdict)
		if not MANIFEST2_REQUIRED_HASH in self.fhashdict[ftype][fname]:
			self.updateFileHashes(ftype, fname, checkExisting=False, ignoreMissing=ignoreMissing)
Beispiel #3
0
def hardlink_lockfile(lockfilename, max_wait=14400):
    """Does the NFS, hardlink shuffle to ensure locking on the disk.
	We create a PRIVATE lockfile, that is just a placeholder on the disk.
	Then we HARDLINK the real lockfile to that private file.
	If our file can 2 references, then we have the lock. :)
	Otherwise we lather, rise, and repeat.
	We default to a 4 hour timeout.
	"""

    start_time = time.time()
    myhardlock = hardlock_name(lockfilename)
    reported_waiting = False

    while (time.time() < (start_time + max_wait)):
        # We only need it to exist.
        myfd = os.open(myhardlock, os.O_CREAT | os.O_RDWR, 0o660)
        os.close(myfd)

        if not os.path.exists(myhardlock):
            raise FileNotFound(
             _("Created lockfile is missing: %(filename)s") % \
             {"filename" : myhardlock})

        try:
            res = os.link(myhardlock, lockfilename)
        except OSError:
            pass

        if hardlink_is_mine(myhardlock, lockfilename):
            # We have the lock.
            if reported_waiting:
                writemsg("\n", noiselevel=-1)
            return True

        if reported_waiting:
            writemsg(".", noiselevel=-1)
        else:
            reported_waiting = True
            from portage.const import PORTAGE_BIN_PATH
            msg = _("\nWaiting on (hardlink) lockfile: (one '.' per 3 seconds)\n"
             "%(bin_path)s/clean_locks can fix stuck locks.\n"
             "Lockfile: %(lockfilename)s\n") % \
             {"bin_path": PORTAGE_BIN_PATH, "lockfilename": lockfilename}
            writemsg(msg, noiselevel=-1)
        time.sleep(3)

    os.unlink(myhardlock)
    return False
Beispiel #4
0
	def _readManifest(self, file_path, myhashdict=None, **kwargs):
		"""Parse a manifest.  If myhashdict is given then data will be added too it.
		   Otherwise, a new dict will be created and returned."""
		try:
			with io.open(_unicode_encode(file_path,
				encoding=_encodings['fs'], errors='strict'), mode='r',
				encoding=_encodings['repo.content'], errors='replace') as f:
				if myhashdict is None:
					myhashdict = {}
				self._parseDigests(f, myhashdict=myhashdict, **kwargs)
			return myhashdict
		except (OSError, IOError) as e:
			if e.errno == errno.ENOENT:
				raise FileNotFound(file_path)
			else:
				raise
Beispiel #5
0
    def __init__(self, **kwargs):
        CompositeTask.__init__(self, **kwargs)

        pkg = self.pkg
        bintree = pkg.root_config.trees["bintree"]
        binpkg_path = None

        if bintree._remote_has_index:
            instance_key = bintree.dbapi._instance_key(pkg.cpv)
            binpkg_path = bintree._remotepkgs[instance_key].get("PATH")
            if binpkg_path:
                self.pkg_path = binpkg_path + ".partial"
            else:
                self.pkg_path = (pkg.root_config.trees["bintree"].getname(
                    pkg.cpv, allocate_new=True) + ".partial")
        else:
            raise FileNotFound("Binary packages index not found")
Beispiel #6
0
def apply_secpass_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1,
	stat_cached=None, follow_links=True):
	"""A wrapper around apply_permissions that uses secpass and simple
	logic to apply as much of the permissions as possible without
	generating an obviously avoidable permission exception. Despite
	attempts to avoid an exception, it's possible that one will be raised
	anyway, so be prepared.
	Returns True if all permissions are applied and False if some are left
	unapplied."""

	if stat_cached is None:
		try:
			if follow_links:
				stat_cached = os.stat(filename)
			else:
				stat_cached = os.lstat(filename)
		except OSError as oe:
			func_call = "stat('%s')" % filename
			if oe.errno == errno.EPERM:
				raise OperationNotPermitted(func_call)
			elif oe.errno == errno.EACCES:
				raise PermissionDenied(func_call)
			elif oe.errno == errno.ENOENT:
				raise FileNotFound(filename)
			else:
				raise

	all_applied = True

	if portage.data.secpass < 2:

		if uid != -1 and \
		uid != stat_cached.st_uid:
			all_applied = False
			uid = -1

		if gid != -1 and \
		gid != stat_cached.st_gid and \
		gid not in os.getgroups():
			all_applied = False
			gid = -1

	apply_permissions(filename, uid=uid, gid=gid, mode=mode, mask=mask,
		stat_cached=stat_cached, follow_links=follow_links)
	return all_applied
Beispiel #7
0
 def addFile(self, ftype, fname, hashdict=None, ignoreMissing=False):
     """Add entry to Manifest optionally using hashdict to avoid recalculation of hashes"""
     if ftype == "AUX":
         if not fname.startswith("files/"):
             fname = os.path.join("files", fname)
         if fname.startswith("files"):
             fname = fname[6:]
     if not os.path.exists(f"{self.pkgdir}{fname}") and not ignoreMissing:
         raise FileNotFound(fname)
     if ftype not in MANIFEST2_IDENTIFIERS:
         raise InvalidDataType(ftype)
     self.fhashdict[ftype][fname] = {}
     if hashdict is not None:
         self.fhashdict[ftype][fname].update(hashdict)
     if self.required_hashes.difference(set(self.fhashdict[ftype][fname])):
         self.updateFileHashes(
             ftype, fname, checkExisting=False, ignoreMissing=ignoreMissing
         )
Beispiel #8
0
def make_herd_base(filename):
    herd_to_emails = dict()
    all_emails = set()

    try:
        xml_tree = xml.etree.ElementTree.parse(
            _unicode_encode(filename,
                            encoding=_encodings['fs'],
                            errors='strict'),
            parser=xml.etree.ElementTree.XMLParser(target=_HerdsTreeBuilder()))
    except ExpatError as e:
        raise ParseError("metadata.xml: " + str(e))
    except EnvironmentError as e:
        func_call = "open('%s')" % filename
        if e.errno == errno.EACCES:
            raise PermissionDenied(func_call)
        elif e.errno == errno.ENOENT:
            raise FileNotFound(filename)
        raise

    herds = xml_tree.findall('herd')
    for h in herds:
        _herd_name = h.find('name')
        if _herd_name is None:
            continue
        herd_name = _herd_name.text.strip()
        del _herd_name

        maintainers = h.findall('maintainer')
        herd_emails = set()
        for m in maintainers:
            _m_email = m.find('email')
            if _m_email is None:
                continue
            m_email = _m_email.text.strip()

            herd_emails.add(m_email)
            all_emails.add(m_email)

        herd_to_emails[herd_name] = herd_emails

    return HerdBase(herd_to_emails, all_emails)
Beispiel #9
0
def write_atomic(file_path, content, **kwargs):
	f = None
	try:
		f = atomic_ofstream(file_path, **kwargs)
		f.write(content)
		f.close()
	except (IOError, OSError) as e:
		if f:
			f.abort()
		func_call = "write_atomic('%s')" % file_path
		if e.errno == errno.EPERM:
			raise OperationNotPermitted(func_call)
		elif e.errno == errno.EACCES:
			raise PermissionDenied(func_call)
		elif e.errno == errno.EROFS:
			raise ReadOnlyFileSystem(func_call)
		elif e.errno == errno.ENOENT:
			raise FileNotFound(file_path)
		else:
			raise
Beispiel #10
0
def compression_probe(f):
    """
    Identify the compression type of a file. Returns one of the
    following identifier strings:

            bzip2
            gzip
            lz4
            lzip
            lzop
            xz
            zstd

    @param f: a file path, or file-like object
    @type f: str or file
    @return: a string identifying the compression type, or None if the
            compression type is unrecognized
    @rtype str or None
    """

    open_file = isinstance(f, str)
    if open_file:
        try:
            f = open(
                _unicode_encode(f, encoding=_encodings["fs"], errors="strict"),
                mode="rb",
            )
        except IOError as e:
            if e.errno == PermissionDenied.errno:
                raise PermissionDenied(f)
            elif e.errno in (errno.ENOENT, errno.ESTALE):
                raise FileNotFound(f)
            else:
                raise

    try:
        return _compression_probe_file(f)
    finally:
        if open_file:
            f.close()
Beispiel #11
0
def _parse_color_map(config_root='/', onerror=None):
    """
	Parse /etc/portage/color.map and return a dict of error codes.

	@param onerror: an optional callback to handle any ParseError that would
		otherwise be raised
	@type onerror: callable
	@rtype: dict
	@return: a dictionary mapping color classes to color codes
	"""
    global codes, _styles
    myfile = os.path.join(config_root, COLOR_MAP_FILE)
    ansi_code_pattern = re.compile("^[0-9;]*m$")
    quotes = '\'"'

    def strip_quotes(token):
        if token[0] in quotes and token[0] == token[-1]:
            token = token[1:-1]
        return token

    try:
        with io.open(_unicode_encode(myfile,
                                     encoding=_encodings['fs'],
                                     errors='strict'),
                     mode='r',
                     encoding=_encodings['content'],
                     errors='replace') as f:
            lines = f.readlines()
        for lineno, line in enumerate(lines):
            commenter_pos = line.find("#")
            line = line[:commenter_pos].strip()

            if len(line) == 0:
                continue

            split_line = line.split("=")
            if len(split_line) != 2:
                e = ParseError(_("'%s', line %s: expected exactly one occurrence of '=' operator") % \
                 (myfile, lineno))
                raise e
                if onerror:
                    onerror(e)
                else:
                    raise e
                continue

            k = strip_quotes(split_line[0].strip())
            v = strip_quotes(split_line[1].strip())
            if not k in _styles and not k in codes:
                e = ParseError(_("'%s', line %s: Unknown variable: '%s'") % \
                 (myfile, lineno, k))
                if onerror:
                    onerror(e)
                else:
                    raise e
                continue
            if ansi_code_pattern.match(v):
                if k in _styles:
                    _styles[k] = (esc_seq + v, )
                elif k in codes:
                    codes[k] = esc_seq + v
            else:
                code_list = []
                for x in v.split():
                    if x in codes:
                        if k in _styles:
                            code_list.append(x)
                        elif k in codes:
                            code_list.append(codes[x])
                    else:
                        e = ParseError(_("'%s', line %s: Undefined: '%s'") % \
                         (myfile, lineno, x))
                        if onerror:
                            onerror(e)
                        else:
                            raise e
                if k in _styles:
                    _styles[k] = tuple(code_list)
                elif k in codes:
                    codes[k] = "".join(code_list)
    except (IOError, OSError) as e:
        if e.errno == errno.ENOENT:
            raise FileNotFound(myfile)
        elif e.errno == errno.EACCES:
            raise PermissionDenied(myfile)
        raise
Beispiel #12
0
def hardlink_lockfile(lockfilename,
                      max_wait=DeprecationWarning,
                      waiting_msg=None,
                      flags=0):
    """Does the NFS, hardlink shuffle to ensure locking on the disk.
	We create a PRIVATE hardlink to the real lockfile, that is just a
	placeholder on the disk.
	If our file can 2 references, then we have the lock. :)
	Otherwise we lather, rise, and repeat.
	"""

    if max_wait is not DeprecationWarning:
        warnings.warn(
            "The 'max_wait' parameter of "
            "portage.locks.hardlink_lockfile() is now unused. Use "
            "flags=os.O_NONBLOCK instead.",
            DeprecationWarning,
            stacklevel=2)

    global _quiet
    out = None
    displayed_waiting_msg = False
    preexisting = os.path.exists(lockfilename)
    myhardlock = hardlock_name(lockfilename)

    # Since Python 3.4, chown requires int type (no proxies).
    portage_gid = int(portage.data.portage_gid)

    # myhardlock must not exist prior to our link() call, and we can
    # safely unlink it since its file name is unique to our PID
    try:
        os.unlink(myhardlock)
    except OSError as e:
        if e.errno in (errno.ENOENT, errno.ESTALE):
            pass
        else:
            func_call = "unlink('%s')" % myhardlock
            if e.errno == OperationNotPermitted.errno:
                raise OperationNotPermitted(func_call)
            elif e.errno == PermissionDenied.errno:
                raise PermissionDenied(func_call)
            else:
                raise

    while True:
        # create lockfilename if it doesn't exist yet
        try:
            myfd = os.open(lockfilename, os.O_CREAT | os.O_RDWR, 0o660)
        except OSError as e:
            func_call = "open('%s')" % lockfilename
            if e.errno == OperationNotPermitted.errno:
                raise OperationNotPermitted(func_call)
            elif e.errno == PermissionDenied.errno:
                raise PermissionDenied(func_call)
            elif e.errno == ReadOnlyFileSystem.errno:
                raise ReadOnlyFileSystem(func_call)
            else:
                raise
        else:
            myfd_st = None
            try:
                myfd_st = os.fstat(myfd)
                if not preexisting:
                    # Don't chown the file if it is preexisting, since we
                    # want to preserve existing permissions in that case.
                    if portage.data.secpass >= 1 and myfd_st.st_gid != portage_gid:
                        os.fchown(myfd, -1, portage_gid)
            except OSError as e:
                if e.errno not in (errno.ENOENT, errno.ESTALE):
                    writemsg("%s: fchown('%s', -1, %d)\n" % \
                     (e, lockfilename, portage_gid), noiselevel=-1)
                    writemsg(_("Cannot chown a lockfile: '%s'\n") % \
                     lockfilename, noiselevel=-1)
                    writemsg(_("Group IDs of current user: %s\n") % \
                     " ".join(str(n) for n in os.getgroups()),
                     noiselevel=-1)
                else:
                    # another process has removed the file, so we'll have
                    # to create it again
                    continue
            finally:
                os.close(myfd)

            # If fstat shows more than one hardlink, then it's extremely
            # unlikely that the following link call will result in a lock,
            # so optimize away the wasteful link call and sleep or raise
            # TryAgain.
            if myfd_st is not None and myfd_st.st_nlink < 2:
                try:
                    os.link(lockfilename, myhardlock)
                except OSError as e:
                    func_call = "link('%s', '%s')" % (lockfilename, myhardlock)
                    if e.errno == OperationNotPermitted.errno:
                        raise OperationNotPermitted(func_call)
                    elif e.errno == PermissionDenied.errno:
                        raise PermissionDenied(func_call)
                    elif e.errno in (errno.ESTALE, errno.ENOENT):
                        # another process has removed the file, so we'll have
                        # to create it again
                        continue
                    else:
                        raise
                else:
                    if hardlink_is_mine(myhardlock, lockfilename):
                        if out is not None:
                            out.eend(os.EX_OK)
                        break

                    try:
                        os.unlink(myhardlock)
                    except OSError as e:
                        # This should not happen, since the file name of
                        # myhardlock is unique to our host and PID,
                        # and the above link() call succeeded.
                        if e.errno not in (errno.ENOENT, errno.ESTALE):
                            raise
                        raise FileNotFound(myhardlock)

        if flags & os.O_NONBLOCK:
            raise TryAgain(lockfilename)

        if out is None and not _quiet:
            out = portage.output.EOutput()
        if out is not None and not displayed_waiting_msg:
            displayed_waiting_msg = True
            if waiting_msg is None:
                waiting_msg = _("waiting for lock on %s\n") % lockfilename
            out.ebegin(waiting_msg)

        time.sleep(_HARDLINK_POLL_LATENCY)

    return True
Beispiel #13
0
    def _init_index(self):

        cp_map = {}
        desc_cache = {}
        self._desc_cache = desc_cache
        self._cp_map = cp_map
        index_missing = []

        streams = []
        for repo_path in self._portdb.porttrees:
            outside_repo = os.path.join(self._portdb.depcachedir,
                                        repo_path.lstrip(os.sep))
            filenames = []
            for parent_dir in (repo_path, outside_repo):
                filenames.append(
                    os.path.join(parent_dir, "metadata", "pkg_desc_index"))

            repo_name = self._portdb.getRepositoryName(repo_path)

            try:
                f = None
                for filename in filenames:
                    try:
                        f = io.open(filename,
                                    encoding=_encodings["repo.content"])
                    except IOError as e:
                        if e.errno not in (errno.ENOENT, errno.ESTALE):
                            raise
                    else:
                        break

                if f is None:
                    raise FileNotFound(filename)

                streams.append(
                    iter(
                        IndexStreamIterator(
                            f,
                            functools.partial(pkg_desc_index_line_read,
                                              repo=repo_name))))
            except FileNotFound:
                index_missing.append(repo_path)

        if index_missing:
            self._unindexed_cp_map = {}

            class _NonIndexedStream(object):
                def __iter__(self_):
                    for cp in self._portdb.cp_all(trees=index_missing):
                        # Don't call cp_list yet, since it's a waste
                        # if the package name does not match the current
                        # search.
                        self._unindexed_cp_map[cp] = index_missing
                        yield pkg_desc_index_node(cp, (), None)

            streams.append(iter(_NonIndexedStream()))

        if streams:
            if len(streams) == 1:
                cp_group_iter = ([node] for node in streams[0])
            else:
                cp_group_iter = MultiIterGroupBy(streams,
                                                 key=operator.attrgetter("cp"))

            for cp_group in cp_group_iter:

                new_cp = None
                cp_list = cp_map.get(cp_group[0].cp)
                if cp_list is None:
                    new_cp = cp_group[0].cp
                    cp_list = []
                    cp_map[cp_group[0].cp] = cp_list

                for entry in cp_group:
                    cp_list.extend(entry.cpv_list)
                    if entry.desc is not None:
                        for cpv in entry.cpv_list:
                            desc_cache[cpv] = entry.desc

                if new_cp is not None:
                    yield cp_group[0].cp
Beispiel #14
0
    def _start(self):
        pkg = self.pkg
        pretend = self.pretend
        bintree = pkg.root_config.trees["bintree"]
        settings = bintree.settings
        pkg_path = self.pkg_path

        exists = os.path.exists(pkg_path)
        resume = exists and os.path.basename(pkg_path) in bintree.invalids
        if not (pretend or resume):
            # Remove existing file or broken symlink.
            try:
                os.unlink(pkg_path)
            except OSError:
                pass

        # urljoin doesn't work correctly with
        # unrecognized protocols like sftp
        fetchcommand = None
        resumecommand = None
        if bintree._remote_has_index:
            remote_metadata = bintree._remotepkgs[bintree.dbapi._instance_key(
                pkg.cpv)]
            binpkg_format = remote_metadata.get(
                "BINPKG_FORMAT", SUPPORTED_GENTOO_BINPKG_FORMATS[0])
            if binpkg_format not in SUPPORTED_GENTOO_BINPKG_FORMATS:
                raise InvalidBinaryPackageFormat(binpkg_format)
            rel_uri = remote_metadata.get("PATH")
            if not rel_uri:
                if binpkg_format == "xpak":
                    rel_uri = pkg.cpv + ".tbz2"
                elif binpkg_format == "gpkg":
                    rel_uri = pkg.cpv + ".gpkg.tar"
            remote_base_uri = remote_metadata["BASE_URI"]
            uri = remote_base_uri.rstrip("/") + "/" + rel_uri.lstrip("/")
            fetchcommand = remote_metadata.get("FETCHCOMMAND")
            resumecommand = remote_metadata.get("RESUMECOMMAND")
        else:
            raise FileNotFound("Binary packages index not found")

        if pretend:
            portage.writemsg_stdout("\n%s\n" % uri, noiselevel=-1)
            self.returncode = os.EX_OK
            self._async_wait()
            return

        fcmd = None
        if resume:
            fcmd = resumecommand
        else:
            fcmd = fetchcommand
        if fcmd is None:
            protocol = urllib_parse_urlparse(uri)[0]
            fcmd_prefix = "FETCHCOMMAND"
            if resume:
                fcmd_prefix = "RESUMECOMMAND"
            fcmd = settings.get(fcmd_prefix + "_" + protocol.upper())
            if not fcmd:
                fcmd = settings.get(fcmd_prefix)

        fcmd_vars = {
            "DISTDIR": os.path.dirname(pkg_path),
            "URI": uri,
            "FILE": os.path.basename(pkg_path),
        }

        for k in ("PORTAGE_SSH_OPTS", ):
            v = settings.get(k)
            if v is not None:
                fcmd_vars[k] = v

        fetch_env = dict(settings.items())
        fetch_args = [
            portage.util.varexpand(x, mydict=fcmd_vars)
            for x in portage.util.shlex_split(fcmd)
        ]

        if self.fd_pipes is None:
            self.fd_pipes = {}
        fd_pipes = self.fd_pipes

        # Redirect all output to stdout since some fetchers like
        # wget pollute stderr (if portage detects a problem then it
        # can send it's own message to stderr).
        fd_pipes.setdefault(0, portage._get_stdin().fileno())
        fd_pipes.setdefault(1, sys.__stdout__.fileno())
        fd_pipes.setdefault(2, sys.__stdout__.fileno())

        self.args = fetch_args
        self.env = fetch_env
        if settings.selinux_enabled():
            self._selinux_type = settings["PORTAGE_FETCH_T"]
        self.log_filter_file = settings.get("PORTAGE_LOG_FILTER_FILE_CMD")
        SpawnProcess._start(self)
Beispiel #15
0
def apply_permissions(filename, uid=-1, gid=-1, mode=-1, mask=-1,
	stat_cached=None, follow_links=True):
	"""Apply user, group, and mode bits to a file if the existing bits do not
	already match.  The default behavior is to force an exact match of mode
	bits.  When mask=0 is specified, mode bits on the target file are allowed
	to be a superset of the mode argument (via logical OR).  When mask>0, the
	mode bits that the target file is allowed to have are restricted via
	logical XOR.
	Returns True if the permissions were modified and False otherwise."""

	modified = False

	if stat_cached is None:
		try:
			if follow_links:
				stat_cached = os.stat(filename)
			else:
				stat_cached = os.lstat(filename)
		except OSError as oe:
			func_call = "stat('%s')" % filename
			if oe.errno == errno.EPERM:
				raise OperationNotPermitted(func_call)
			elif oe.errno == errno.EACCES:
				raise PermissionDenied(func_call)
			elif oe.errno == errno.ENOENT:
				raise FileNotFound(filename)
			else:
				raise

	if	(uid != -1 and uid != stat_cached.st_uid) or \
		(gid != -1 and gid != stat_cached.st_gid):
		try:
			if follow_links:
				os.chown(filename, uid, gid)
			else:
				portage.data.lchown(filename, uid, gid)
			modified = True
		except OSError as oe:
			func_call = "chown('%s', %i, %i)" % (filename, uid, gid)
			if oe.errno == errno.EPERM:
				raise OperationNotPermitted(func_call)
			elif oe.errno == errno.EACCES:
				raise PermissionDenied(func_call)
			elif oe.errno == errno.EROFS:
				raise ReadOnlyFileSystem(func_call)
			elif oe.errno == errno.ENOENT:
				raise FileNotFound(filename)
			else:
				raise

	new_mode = -1
	st_mode = stat_cached.st_mode & 0o7777 # protect from unwanted bits
	if mask >= 0:
		if mode == -1:
			mode = 0 # Don't add any mode bits when mode is unspecified.
		else:
			mode = mode & 0o7777
		if	(mode & st_mode != mode) or \
			((mask ^ st_mode) & st_mode != st_mode):
			new_mode = mode | st_mode
			new_mode = (mask ^ new_mode) & new_mode
	elif mode != -1:
		mode = mode & 0o7777 # protect from unwanted bits
		if mode != st_mode:
			new_mode = mode

	# The chown system call may clear S_ISUID and S_ISGID
	# bits, so those bits are restored if necessary.
	if modified and new_mode == -1 and \
		(st_mode & stat.S_ISUID or st_mode & stat.S_ISGID):
		if mode == -1:
			new_mode = st_mode
		else:
			mode = mode & 0o7777
			if mask >= 0:
				new_mode = mode | st_mode
				new_mode = (mask ^ new_mode) & new_mode
			else:
				new_mode = mode
			if not (new_mode & stat.S_ISUID or new_mode & stat.S_ISGID):
				new_mode = -1

	if not follow_links and stat.S_ISLNK(stat_cached.st_mode):
		# Mode doesn't matter for symlinks.
		new_mode = -1

	if new_mode != -1:
		try:
			os.chmod(filename, new_mode)
			modified = True
		except OSError as oe:
			func_call = "chmod('%s', %s)" % (filename, oct(new_mode))
			if oe.errno == errno.EPERM:
				raise OperationNotPermitted(func_call)
			elif oe.errno == errno.EACCES:
				raise PermissionDenied(func_call)
			elif oe.errno == errno.EROFS:
				raise ReadOnlyFileSystem(func_call)
			elif oe.errno == errno.ENOENT:
				raise FileNotFound(filename)
			raise
	return modified