在setup.py文件中设置文件权限

10 投票
4 回答
6506 浏览
提问于 2025-04-16 17:16

我用setup.py创建了一个Python软件安装包。在这个软件里,我使用了一些数据文件(XML文件)。当我通过setup.py安装这些XML文件时,它们会和其他文件一起保存在/usr/lib/python2.7/site_packages/XYZ这个地方。不过,这些文件的权限设置成了rwx------,这意味着只有超级用户(root)可以读取这些文件。我想把这些XML文件的权限改成rwxr-----,这样当前用户也能读取这些文件。我该怎么改变这些数据文件的权限呢?

4 个回答

1

不编辑 setup.py 的解决方案

如果你只是想安装一个现有的包,而不想编辑它的 setup.py 文件,可以试试下面的命令:

sudo sh -c 'umask 0; \
    python setup.py install --installed files.txt && \
    xargs chmod -R a+rX < files.txt'

编辑 setup.py 的解决方案

如果可以编辑 setup.py 文件,一个简单的解决办法是在 setup.py 的开头加上以下内容:

import os, subprocess
# Set a reasonable umask.
os.umask(0o022)
# Make all local files readable and all directories listable.
subprocess.call(['chmod', '-R', 'a+rX', '.'])

这个方法在 Linux 系统上有效,但在其他操作系统上可能不行。如果你需要支持其他操作系统,或者只是想改变已安装文件的权限,可以用更复杂的方法替换 chmod 的调用,比如在这个回答中提到的那种。

解释

我在使用 setup.py install 时遇到问题,创建的文件夹只能被 root 用户读取,复制的文件也是如此。后来我发现,setup.py 创建的文件夹的权限是由我的 umask 决定的,而我认为 setup.py 复制的文件权限是保留的,不过我对此不太确定。

规范性声明 :)

我觉得 setup.py install 应该负责使用合理的默认设置——比如,当我用 apt 安装软件时,它会忽略我的 umask,这才是应该的。

1

我使用 setup.py 来构建各种类型的 RPM 包。对我来说,解决方案有点不同。我觉得这样做更稳妥,主要有两个原因:

  1. 我可以明确地覆盖文件的权限
  2. 我不需要知道用户的 uid 和 gid,而是可以直接使用文本。

下面是一个有效的例子:

from distutils.core import setup
import distutils.command.bdist_rpm
import distutils.command.install

version='13'

data_files = [
    ('/usr/share/blah', ['README', 'test.sh']),
]

permissions = [
    ('/usr/share/blah', 'test.sh', '(755, sri, sri)'),
]

class bdist_rpm(distutils.command.bdist_rpm.bdist_rpm):

    def _make_spec_file(self):
        spec = distutils.command.bdist_rpm.bdist_rpm._make_spec_file(self)
        for path, files , perm in permissions:

            ##
            # Add a line to the SPEC file to change the permissions of a
            # specific file upon install.
            #
            # example:
            #   %attr(666, root, root) path/file
            #
            spec.extend(['%attr{} {}/{}'.format(perm, path, files)])

        return spec


setup(name='sri-testme',
      version=version,
      description='This is garganbe and is only used to test the permision flag behavior',
      author='Chris Gembarowski',
      author_email='chrisg@summationresearch.com',
      url='https://www.python.org/sigs/distutils-sig/',
      data_files=data_files,
      cmdclass={'bdist_rpm':bdist_rpm}
     )

让我更详细地解释一下发生了什么。RPM 包是从一个 SPEC 文件构建的。bdist_rpm 会生成一个 SPEC 文件。在 SPEC 文件中,你可以通过提供 %attr 选项来选择文件的权限和所有权。

在这个例子中,为了让 test.sh 可执行并且归用户 'sri' 所有,我会在 SPEC 文件的末尾添加 %attr(755, sri, sri)

所以,当我覆盖 bdist_rpm._make_spec_file 的行为时,我所做的就是为每个我想要覆盖权限的文件添加一行。

这个例子的完整 SPEC 文件如下:

%define name sri-testme
%define version 13
%define unmangled_version 13
%define release 1

Summary: This is garganbe and is only used to test the permision flag behavior
Name: %{name}
Version: %{version}
Release: %{release}
Source0: %{name}-%{unmangled_version}.tar.gz
License: UNKNOWN
Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Vendor: Chris Gembarowski <chrisg@summationresearch.com>
Url: https://www.python.org/sigs/distutils-sig/

%description
UNKNOWN

%prep
%setup -n %{name}-%{unmangled_version}

%build
python setup.py build

%install
python setup.py install -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES

%clean
rm -rf $RPM_BUILD_ROOT

%post
##
# sri will be turned on in the run-once script instead of here
#


%preun
#!/bin/bash



%files -f INSTALLED_FILES
%defattr(-,root,root)
%attr(755, sri, sri) /usr/share/blah/test.sh
12

正确的做法是重写 install 命令,下面是具体的做法。

首先,在你的 setup.py 文件开头添加以下导入:

from setuptools.command.install import install
from distutils import log # needed for outputting information messages 

接下来,你需要创建一个可以调用的命令类。这里有一个例子,我创建了一个命令类,用来安装一个脚本,并确保这个脚本只能被 root 用户执行(在 Python 中还有其他方法可以做到这一点,比如如果你的用户 ID 不是 0,就可以直接退出脚本)。我在这里还使用了另一个导入:

from setuptools.command.install_scripts import install_scripts

class OverrideInstall(install):

    def run(self):
        uid, gid = 0, 0
        mode = 0700
        install.run(self) # calling install.run(self) insures that everything that happened previously still happens, so the installation does not break! 
        # here we start with doing our overriding and private magic ..
        for filepath in self.get_outputs():
            if self.install_scripts in filepath:
                log.info("Overriding setuptools mode of scripts ...")
                log.info("Changing ownership of %s to uid:%s gid %s" %
                         (filepath, uid, gid))
                os.chown(filepath, uid, gid)
                log.info("Changing permissions of %s to %s" %
                         (filepath, oct(mode)))
                os.chmod(filepath, mode)

现在类已经创建好了。我通知安装程序,当命令行中出现 install 时,应该调用这个类:

setup(
      # keep
      # all the previous keywords you had ...
      # add
      cmdclass={'install': OverrideInstall}
      ) 

希望这个回答对你有帮助。

撰写回答