本篇文章由网友“小黑”投稿发布。os.chflags,os.lchflags,os.lchmod这些存在于BSD及Mac OS X系统中,但是不存在于Linux与Windows中的方法,则是通过Mac OS X里的Python 2.6.x的版本来进行测试的。

    页面导航: 前言:

    本篇文章由网友“小黑”投稿发布。

    这篇文章是接着上一篇的内容来写的。

    相关英文教程的下载链接,以及Python 2.7.8的C源代码的下载链接,请参考之前"Python基本的I/O操作 (一)"的内容。

    文章中的脚本代码主要是通过Linux系统中的python 2.7.x的版本来进行测试的。

    至于os.chflags,os.lchflags,os.lchmod这些存在于BSD及Mac OS X系统中,但是不存在于Linux与Windows中的方法,则是通过Mac OS X里的Python 2.6.x的版本来进行测试的。

os模块的access方法,Linux系统中的几种uid/gid的概念:

    如果要检测当前进程是否对某文件或目录具有读写执行的权限的话,可以使用access方法。其语法格式如下:

os.access(path, mode) -> True if granted, False otherwise

    path参数用于指定文件或目录的路径。

    mode参数则用于指定需要检测的权限。可以是os.R_OK(读权限),os.W_OK(写权限),os.X_OK(执行权限)或者是os.F_OK(检测目标文件是否存在)。

    当返回True时,表示检测通过,否则返回False。在Linux系统中,会使用当前进程的real uid/gid来进行检测,下面会举例说明。

    先看个简单的例子:

import os

print 'test.txt(r/w):', os.access('test.txt', os.R_OK | os.W_OK)
print 'tmpdir(r):', os.access('tmpdir', os.R_OK)
print 'tmpdir(w)', os.access('tmpdir', os.W_OK)


    这段代码的执行结果如下:

black@slack:~/test$ ls -l
...................................................
-rwxr-xr-x 1 black users    5 Nov 20 16:12 test.txt
drwxr-xr-x 2 root  root  4096 Nov 19 13:41 tmpdir
black@slack:~/test$ python test.py
test.txt(r/w): True
tmpdir(r): True
tmpdir(w) False
black@slack:~/test$ 


    由于test.txt文件的owner(所有者)是black用户,且owner对文件具有rwx(读写执行)的权限。因此,access使用os.R_OK | os.W_OK来检测当前进程是否具有读和写的权限时,就会返回True。

    而tmpdir目录的owner是root用户,other(其他用户)对其只有r-x(读和执行)的权限。因此,只有os.R_OK的读操作检测会返回True,os.W_OK的写操作检测则会返回False。

    从上面例子中还可以看到,当需要检测多个操作权限时,可以使用按位或运算符。例如,上面的os.R_OK | os.W_OK,就可以检测读和写的权限,这两个权限必须都检测通过,才会返回True。

    上面提到,Linux系统中,access方法会使用当前进程的real uid/gid来进行检测。uid指的是user id(用户ID),gid指的是group id(组ID)。在系统内核里,都是通过用户的ID值或者组的ID值来进行各种权限的检测操作的,用户名与组名则主要用于显示用途,不会参与具体的检测工作。

    Linux系统中,主要有以下三种uid/gid:

    1) real uid/gid:当前进程的real uid/gid,会从创建它的进程中继承下来。假设我设置了一个black用户(uid为1000),同时设置该用户属于users组(gid为100)。那么,当我以black用户登录系统后,启动的shell进程的real uid/gid就会是1000(uid)/100(gid),所有shell启动的进程的real uid/gid也都会是1000(uid)/100(gid),也就是从shell进程中继承过来的。

    real uid通常简称为ruid,real gid则简称为rgid。

    2) effective uid/gid:当进程要执行一个具体的操作时,例如打开文件的操作时,系统就会使用effective uid/gid来先检测是否可以进行相关的操作,很多时候,我们在执行命令时,如果出现Operation not permitted,那么大多是因为effective uid/gid没检测通过。一个进程可以执行的大部分的实际操作都是通过effective uid/gid来进行检测的。当普通用户需要访问root的资源时,也主要是通过将effective uid/gid设置为root的ID值来实现提权的。

    effective uid通常简称为euid,effective gid则简称为egid。

    3) saved uid/gid:从字面上理解,意思就是saved(起存储作用)的uid/gid。例如:当发生降权的时候,effective uid/gid会从root的ID变为普通用户的ID。此时,如果saved uid/gid里存储了root的ID值时,那么降权操作完成后,进程还可以提权回root用户。当然,上面提到的real uid/gid也可以起到这样的作用,只不过大多数情况下,我们会用saved uid/gid来做这件事,毕竟,saved uid/gid基本上就只有这个用途,而real uid/gid还可以做些别的事,比如,access方法就需要用real uid/gid来进行检测等。

    saved uid通常简称为suid,saved gid则简称为sgid。

    一般情况下,ruid == 创建当前进程的父进程的ruid,euid == 父进程的euid,suid == 父进程的suid,gid同理。并且,缺省情况下,如果没经过特殊设置的话,父进程的这三个uid值是相等的。因此,当前进程的这三个uid值在正常情况下也应该是相等的,gid也是同理。

    在某些特殊情况下,例如后面会介绍的os.chmod方法,可以设置可执行文件的setuid或setgid位,当可执行文件被设置了setuid位时,可执行文件在启动时,ruid == 父进程的ruid,而euid == suid == 可执行文件的owner的uid值。当可执行文件被设置了setgid位时,rgid == 父进程的rgid,而egid == sgid == 可执行文件的group组的id值。

    Python中,可以使用os.getresuid方法来获取ruid,euid及suid的值,同时还可以使用os.setresuid来设置这几个uid的值。例如下面这段代码:

import os

resuid = os.getresuid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'

print "\nok, set ruid to user black(uid=1000)"
os.setresuid(1000,0,0)
resuid = os.getresuid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'

print "\nok, set euid to user black(uid=1000)"
os.setresuid(1000,1000,0)
resuid = os.getresuid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'

print "\nrestoring back to root"
os.setresuid(0,0,0)
resuid = os.getresuid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'


    这段代码的执行结果如下:

black@slack:~/test$ ls -l /test
-rw-r--r-- 1 root root 0 Nov 22 16:42 /test
black@slack:~/test$ ls -ln /test
-rw-r--r-- 1 0 0 0 Nov 22 16:42 /test
black@slack:~/test$ sudo python test.py
ruid: 0  euid: 0  suid: 0
access("/test"): True
open /test success!

ok, set ruid to user black(uid=1000)
ruid: 1000  euid: 0  suid: 0
access("/test"): False
open /test success!

ok, set euid to user black(uid=1000)
ruid: 1000  euid: 1000  suid: 0
access("/test"): False
open /test failed!

restoring back to root
ruid: 0  euid: 0  suid: 0
access("/test"): True
open /test success!
black@slack:~/test$ 


    由于/test文件只有owner才有写入权限,而owner是root(uid = 0)。因此,只有在当前进程的ruid为0时,access才会返回True,如果ruid不为0,access就会返回False。

    而open内建函数打开文件的操作则是通过euid来检测权限的,因此,只有在当前进程的euid为0时,open操作才会success成功,如果euid不为0,则open操作就会failed失败。

    因此,上面在ruid等于1000,而euid等于0时,access会返回False,而open却可以打开成功,因为access方法只看ruid,open内建函数则只看euid。

    在执行结束时,因为suid为0,因此,进程在降权到black用户(uid=1000)后,还可以提权回root用户。

    这里测试时,我们需要通过sudo先给进程提权,让suid能先存储好root的id值即0 。

    Python中还存在os.getresgid与os.setresgid方法可以获取和设置rgid,egid及sgid的值。例如下面这段代码:

import os

resuid = os.getresuid()
resgid = os.getresgid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'rgid:',resgid[0],' egid:',resgid[1],' sgid:',resgid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'

print "\nok, set ruid to user black(uid=1000)"
os.setresgid(1000,100,1000)
os.setresuid(1000,1000,0)
resuid = os.getresuid()
resgid = os.getresgid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'rgid:',resgid[0],' egid:',resgid[1],' sgid:',resgid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'

print "\nrestoring back to root"
os.setresuid(0,0,0)
os.setresgid(0,0,0)
resuid = os.getresuid()
resgid = os.getresgid()
print 'ruid:',resuid[0],' euid:',resuid[1],' suid:',resuid[2]
print 'rgid:',resgid[0],' egid:',resgid[1],' sgid:',resgid[2]
print 'access("/test"):', os.access("/test", os.W_OK)
try:
	fo = open('/test', 'w+')
	print 'open /test success!'
except:
	print 'open /test failed!'


    这段代码的执行结果如下:

black@slack:~/test$ sudo chown root:users /test
Password:
black@slack:~/test$ sudo chmod 664 /test
black@slack:~/test$ ls -l /test
-rw-rw-r-- 1 root users 0 Nov 22 17:05 /test
black@slack:~/test$ ls -ln /test
-rw-rw-r-- 1 0 100 0 Nov 22 17:05 /test
black@slack:~/test$ sudo python test.py
ruid: 0  euid: 0  suid: 0
rgid: 0  egid: 0  sgid: 0
access("/test"): True
open /test success!

ok, set ruid to user black(uid=1000)
ruid: 1000  euid: 1000  suid: 0
rgid: 1000  egid: 100  sgid: 1000
access("/test"): False
open /test success!

restoring back to root
ruid: 0  euid: 0  suid: 0
rgid: 0  egid: 0  sgid: 0
access("/test"): True
open /test success!
black@slack:~/test$ 


    上面我们先将/test文件的group组设置为users(gid = 100),原始的root组在本例测试时会有些问题(至少在我的系统下测试时有点问题,感觉root组在本例中没起到作用)。

    接着用chmod命令为/test文件的group组也设置写入权限。

    通过执行test.py脚本可以看到,只有当ruid为0或者rgid为100时,access才会返回True,否则返回False。open操作则是在euid为0或者egid为100时才会操作成功。也就是uid检测不通过时,系统会使用gid来进行检测,当gid等于/test文件的group组的gid时,就可以检测通过。

    上面只对uid,gid的值做了简单的测试,读者可以自行修改这些值来进行更全面的测试。

    在windows系统中,只有当文件不存在,或者检测某个只读文件是否可写时,access方法才会返回False,其他情况下,都会返回True。如果目标是一个目录,那么access方法始终会返回True。os.access方法会调用的底层C函数为posix_access(定义在Python源码中的Modules/posixmodule.c文件里),读者可以阅读相关C源码来加深理解。

os模块的chflags与lchflags方法:

    chflags方法可以在Mac OS X与BSD系统下使用,但是无法在Linux与Windows中使用。

    chflags方法可以修改文件的内部标志,这些标志可以看成是文件的特殊属性,有的标志可以让文件无法被修改,哪怕是文件的owner(所有者)都无法修改。

    chflags方法的语法格式如下:

os.chflags(path, flags)

    path参数用于指定文件的路径,flags用于指定具体的标志值。

    os.chflags会调用的底层C函数是posix_chflags(定义在Python源码中的Modules/posixmodule.c文件里),从这个C函数中可以看到,它最终会通过chflags这个底层的系统调用去执行具体的操作。在Mac OS X系统里可以通过man 2 chflags来查看该系统调用的详情:

MacOSX:~ black$ man 2 chflags
CHFLAGS(2)                  BSD System Calls Manual                 CHFLAGS(2)

NAME
     chflags, fchflags -- set file flags

SYNOPSIS
     #include <sys/stat.h>
     #include <unistd.h>

     int
     chflags(const char *path, u_int flags);

     int
     fchflags(int fd, u_int flags);

DESCRIPTION
     The file whose name is given by path or referenced by the descriptor fd
     has its flags changed to flags.

     The flags specified are formed by or'ing the following values

           UF_NODUMP      Do not dump the file.
           UF_IMMUTABLE   The file may not be changed.
           UF_APPEND      The file may only be appended to.
           UF_OPAQUE      The directory is opaque when viewed through a union
                          stack.
           UF_HIDDEN      The file or directory is not intended to be dis-
                          played to the user.
           SF_ARCHIVED    The file has been archived.
           SF_IMMUTABLE   The file may not be changed.
           SF_APPEND      The file may only be appended to.


    flags具有UF_NODUMP,UF_IMMUTABLE之类的值。其中,以UF_开头的标志表示普通用户可以进行设置的标志,而以SF_开头的标志则表示只有superuser(超级用户)才能设置的标志。这些标志在Python中被定义在了stat模块里。下面是一个简单的例子:

import os
import stat

os.chflags("test.txt", stat.UF_NODUMP | stat.UF_IMMUTABLE)
print 'set test.txt to nodump and unchangeable'


    这段代码的执行结果如下:

MacOSX:~ black$ python test.py
set test.txt to nodump and unchangeable
MacOSX:~ black$ ls -lO
....................................................
-rw-r--r--   1 black  staff  -            283 11 21 15:07 test.py
-rwxrwxrwx   1 black  staff  uchg,nodump   18 11 17 16:50 test.txt
....................................................
MacOSX:~ black$ 


    执行完test.py脚本后,通过ls -lO命令可以看到,test.txt文件就被赋予了uchgnodump标志,其中uchg表示此文件无法被修改,哪怕是owner(所有者)或者使用sudo提权都无法对其进行修改和删除操作,并且也无法使用chmod命令来修改其读写执行的访问权限。nodump则表示在使用dump工具对文件系统进行备份时,可以不备份该文件。

    可以使用os.chflags('test.txt', 0)语句来清理掉文件的这些标志。

    Python中还有一个os.lchflags方法,同样只适用于Mac OS X与BSD系统中。该方法类似于chflags,只不过当遇到符号链接时,它只会对符号链接本身进行操作,不会对符号链接所指向的目标文件进行操作。例如下面这段代码:

import os
import stat

os.lchflags("symlink", stat.UF_NODUMP)
print 'set symlink to nodump'


    这段代码的执行结果如下:

MacOSX:~ black$ ln -s ./test.txt ./symlink
MacOSX:~ black$ python test.py
set symlink to nodump
MacOSX:~ black$ ls -lO
....................................................
lrwxr-xr-x   1 black  staff  nodump   10 11 23 15:29 symlink -> ./test.txt
-rw-r--r--   1 black  staff  -       283 11 21 15:07 test.py
-rwxrwxrwx   1 black  staff  -        23 11 23 15:16 test.txt
....................................................
MacOSX:~ black$ 


    可以看到,只有symlink会被设置为nodump,而其指向的test.txt文件不会受到影响。如果是chflags方法,则会作用到test.txt这个目标文件上。

os模块的chmod、fchmod及lchmod方法:

    chmod方法可以修改文件的owner,group,other的读写执行的权限,其语法格式如下:

os.chmod(path, mode)

    path用于指定文件的路径,mode则用于设置访问权限。由于在Linux系统中,os.chmod最终会通过chmod这个底层C库函数去完成具体的操作,因此,mode的具体值也由chmod库函数来决定。可以通过man 2 chmod来查看到这些mode值:

black@slack:~/test$ man 2 chmod
CHMOD(2)                   Linux Programmer's Manual                  CHMOD(2)

NAME
       chmod, fchmod - change permissions of a file

SYNOPSIS
       #include <sys/stat.h>

       int chmod(const char *path, mode_t mode);
       .............................................

DESCRIPTION
       These system calls change the permissions of a file.  They differ  only
       in how the file is specified:

       * chmod()  changes the permissions of the file specified whose pathname
         is given in path, which is dereferenced if it is a symbolic link.

       .............................................

       The  new  file  permissions  are specified in mode, which is a bit mask
       created by ORing together zero or more of the following:

       S_ISUID  (04000)  set-user-ID  (set  process  effective  user   ID   on
                         execve(2))

       S_ISGID  (02000)  set-group-ID  (set  process  effective  group  ID  on
                         execve(2);  mandatory  locking,   as   described   in
                         fcntl(2);  take a new file's group from parent direc-
                         tory, as described in chown(2) and mkdir(2))

       S_ISVTX  (01000)  sticky bit (restricted deletion flag, as described in
                         unlink(2))

       S_IRUSR  (00400)  read by owner

       S_IWUSR  (00200)  write by owner

       S_IXUSR  (00100)  execute/search  by owner ("search" applies for direc-
                         tories, and means that entries within  the  directory
                         can be accessed)

       S_IRGRP  (00040)  read by group

       S_IWGRP  (00020)  write by group

       S_IXGRP  (00010)  execute/search by group

       S_IROTH  (00004)  read by others

       S_IWOTH  (00002)  write by others

       S_IXOTH  (00001)  execute/search by others

       .............................................


    mode可以是S_ISUID,S_ISGID之类的值,这些值被定义在Python的stat模块中。下面是一个简单的例子:

import os
import stat

os.chmod('test.txt', stat.S_IRUSR | stat.S_IWUSR | \
			stat.S_IRGRP | \
			stat.S_IROTH)
print 'set test.txt to -rw-r--r--'


    这段代码的执行结果如下:

black@slack:~/test$ python test.py 
set test.txt to -rw-r--r--
black@slack:~/test$ ls -l
....................................................
-rw-r--r-- 1 black users    5 Nov 20 16:12 test.txt
....................................................
black@slack:~/test$ 


    上面例子就为test.txt文件的owner设置了读和写的权限,同时将该文件的group(组)与other(其他用户)的访问权限设置为了只读权限。

    从前面man 2 chmod命令中可以看到:S_IRUSR、S_IWUSR、S_IRGRP及S_IROTH的八进制值分别是:00400、00200、00040、00004,它们进行按位或运算的结果是00644,因此,可以使用 os.chmod('test.txt', 00644) 语句来达到相同的效果。

    S_ISUID这个mode可以设置可执行文件的setuid位。在前面介绍access方法与Linux系统中的uid/gid的概念时,我们提到过,当设置了可执行文件的setuid位后,可执行文件启动时,进程的ruid == 父进程的ruid,而euid == suid == 可执行文件的owner的uid值。为了能够测试setuid位,我们需要先用C语言写一个简单的测试程序(用于生成可执行文件):

#define _GNU_SOURCE
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char **argv) {
	uid_t ruid = -1, euid = -1, suid = -1;
	getresuid(&ruid, &euid, &suid);
	printf("> ruid=%d, euid=%d, suid=%d\n", ruid, euid, suid);
	return 0;
}


    对这个C程式进行编译,同时将编译生成的a.out可执行文件的owner设置为root:

black@slack:~/test$ gcc example.c 
black@slack:~/test$ sudo chown root a.out
black@slack:~/test$ ls -l
....................................................
-rwxr-xr-x 1 root  users 5763 Nov 23 16:50 a.out
-rw-r--r-- 1 black users  282 Nov 23 16:50 example.c
....................................................
black@slack:~/test$ ./a.out 
> ruid=1000, euid=1000, suid=1000
black@slack:~/test$ 


    可以看到,虽然a.out的owner被设置为了root,但是当black用户启动a.out程式时,a.out对应的进程的ruid、euid及suid都会是black用户的uid(本例中为1000)。如果想让black用户启动a.out时,能让进程的euid与suid自动提权到root的话,可以设置a.out可执行文件的setuid位,例如下面这段python代码:

import os
import stat

os.chmod('a.out', 00755 | stat.S_ISUID)
print 'set a.out to -rwsr-xr-x'


    这段代码的执行结果如下:

black@slack:~/test$ sudo python test.py
set a.out to -rwsr-xr-x
black@slack:~/test$ ls -l
....................................................
-rwsr-xr-x 1 root  users 5763 Nov 23 16:50 a.out
....................................................
black@slack:~/test$ ./a.out 
> ruid=1001, euid=0, suid=0
black@slack:~/test$ 


    由于a.out的owner是root,因此,修改其访问权限时,需要sudo提权。当设置了a.out的setuid位后,该文件的rwx就会变为rws,也就是其中的x变为了s 。当我们以普通的black用户启动a.out时,在setuid位的作用下,进程的euid,suid就会被自动提权为root用户的uid了(root的uid为0)。这样就不需要sudo提权也可以让a.out程式访问root的大部分资源了。

    S_ISGID即setgid位是同理,当a.out被设置了setgid位后,可执行文件启动时,进程的egid与sgid将被设置为可执行文件的group组的id值。

    至于S_ISVTX这个mode,则主要用于目录。最典型的应用就是/tmp目录:

black@slack:~/test$ ls -l /   
....................................................
drwxrwxrwt  15 root root   4096 Nov 23 17:21 tmp
....................................................
black@slack:~/test$ 


    /tmp目录的访问权限的最后是以字母t结尾的,就说明该目录被设置了S_ISVTX这个mode。在这个mode的作用下,该目录内的文件就只能被root或者文件的owner(所有者)给删除掉,如果没有这个mode,那么,任何用户都可以删除/tmp目录中其他用户的文件了。

    os.chmod会调用的底层C函数为posix_chmod(也定义在Python源码中的Modules/posixmodule.c文件里),通过阅读相关C源码可知,在windows系统中,起作用的只有一个S_IWUSR模式,当对文件设置S_IWUSR模式时,可以去掉文件的只读属性,否则就会设置文件的只读属性。此外,对windows的目录进行chmod设置不会起到什么作用。

    os模块的fchmod方法类似于chmod,只不过fchmod是使用文件描述符作为输入参数的,fchmod只能用于Unix系统中,其语法格式如下:

os.fchmod(fd, mode)

    fd对应为文件的描述符,mode为需要设置的模式(或者叫访问权限),mode的具体值请参考上面的chmod方法。例如下面这段代码:

import os

fo = open('test.txt')
os.fchmod(fo.fileno(), 00755)
print 'set test.txt to -rwxr-xr-x'


    这段代码执行后,会为test.txt文件设置rwxr-xr-x的访问权限。

    在Mac OS X及BSD系统中,Python还支持一个lchmod方法,其语法格式如下:

os.lchmod(path, mode)

    在使用方式上,lchmod与chmod没什么太大的区别,path参数都是文件的路径,mode为要设置的访问权限。只不过,lchmod在遇到符号链接时,只会对符号链接本身进行设置,不会像chmod那样作用到符号链接的目标文件上。Linux系统中之所以没有lchmod方法,是因为Linux忽略掉了符号链接的读写执行的访问权限。

    下面是一个简单的例子:

import os

os.lchmod('symlink', 0775)
print 'set symlink to rwxrwxr-x' 


    这段代码在Mac OS X系统中的测试结果如下:

MacOSX:~ black$ ln -s ./test.txt ./symlink
MacOSX:~ black$ ls -l
....................................................
lrwxr-xr-x   1 black  staff    10 11 23 18:29 symlink -> ./test.txt
-rw-r--r--   1 black  staff    73 11 23 18:29 test.py
-rwxrwxrwx   1 black  staff    23 11 23 15:16 test.txt
....................................................
MacOSX:~ black$ python test.py
set symlink to rwxrwxr-x
MacOSX:~ black$ ls -l
....................................................
lrwxrwxr-x   1 black  staff    10 11 23 18:29 symlink -> ./test.txt
-rw-r--r--   1 black  staff    73 11 23 18:29 test.py
-rwxrwxrwx   1 black  staff    23 11 23 15:16 test.txt
....................................................
MacOSX:~ black$ 


    可以看到,lchmod只会对symlink符号链接进行设置,symlink所指向的test.txt文件不会受到影响。

os模块的chown、fchown及lchown方法:

    如果要修改文件或目录的owner和group,就可以使用chown方法,该方法的语法格式如下(只存在于Unix系统中):

os.chown(path, uid, gid)

    path用于指定文件或目录的路径,uid用于指定需要设置的owner的用户ID,gid则用于指定需要设置的group的组ID。如果uid被设为-1,那么目标文件的owner就会保持不变,gid同理。

    以下是一个简单的例子:

import os

os.chown('test.txt', 0, -1)
print 'change test.txt owner to root'
os.chown('tmpdir', -1, 100)
print 'change tmpdir group to users'


    这段代码的执行结果如下:

black@slack:~/test$ ls -l
....................................................
-rwxr-xr-x 1 black users    5 Nov 20 16:12 test.txt
drwxrwxrwx 2 root  root  4096 Nov 23 17:45 tmpdir
....................................................
black@slack:~/test$ sudo python test.py
change test.txt owner to root
change tmpdir group to users
black@slack:~/test$ ls -l
....................................................
-rwxr-xr-x 1 root  users    5 Nov 20 16:12 test.txt
drwxrwxrwx 2 root  users 4096 Nov 23 17:45 tmpdir
....................................................
black@slack:~/test$ 


    可以使用getent指令来查询用户或组的ID值:

black@slack:~/test$ getent passwd root
root:x:0:0::/root:/bin/bash
black@slack:~/test$ getent group users
users:x:100:
black@slack:~/test$ cat /etc/passwd
root:x:0:0::/root:/bin/bash
....................................................
black@slack:~/test$ cat /etc/group 
....................................................
users:x:100:
....................................................
black@slack:~/test$ 


    getent passwd <UserName>可以查询用户名对应的uid,getent group <GroupName>可以查询组名对应的gid。getent passwd本质上就是从/etc/passwd文件中提取的内容,而getent group则是从/etc/group文件中提取的内容。

    fchown方法类似于chown,只不过它是使用的文件描述符作为输入参数,其语法格式如下(也只适用于Unix系统):

os.fchown(fd, uid, gid)

    fd表示文件描述符,uid,gid的含义与chown方法中的含义相同。例如下面这段代码:

import os

fd = os.open('test.txt', os.O_RDONLY)
os.fchown(fd, 0,0)
print 'change owner:group of test.txt to root:root'


    os.open是后面会介绍的方法,该方法可以直接通过底层的open系统调用打开文件,并将文件描述符作为结果返回。这样,fchown就可以直接根据文件描述符来进行操作了,这段代码的执行结果如下:

black@slack:~/test$ sudo python test.py
change owner:group of test.txt to root:root
black@slack:~/test$ ls -l
....................................................
-rwxr-xr-x 1 root  root     5 Nov 20 16:12 test.txt
....................................................
black@slack:~/test$ 


    os模块还有一个lchown方法,它的输入参数与chown方法的参数类似。只不过,当lchown方法遇到符号链接时,只会对符号链接本身进行修改,其语法格式如下(同样只适用于Unix系统):

os.lchown(path, uid, gid)

    以下是一个简单的例子:

import os

os.lchown('symlink', 0, 0)
print 'change symlink to root:root'


    执行结果如下:

black@slack:~/test$ ls -l
total 36
-rwxrwxrwx 1 black users 5975 Nov 23 17:21 a.out
....................................................
lrwxrwxrwx 1 black users    7 Nov 21 14:38 symlink -> ./a.out
....................................................
black@slack:~/test$ sudo python test.py
change symlink to root:root
black@slack:~/test$ ls -l
total 36
-rwxrwxrwx 1 black users 5975 Nov 23 17:21 a.out
....................................................
lrwxrwxrwx 1 root  root     7 Nov 21 14:38 symlink -> ./a.out
....................................................
black@slack:~/test$ 


    可以看到,只有symlink符号链接的owner与group发生了改变,symlink所指向的a.out文件不会受到影响。

os模块的open方法:

    之前我们介绍过的open内建函数,底层会通过fopen的C标准库函数去打开文件,fopen在glibc中最终会通过open系统调用来打开文件。os.open方法则是直接通过open系统调用来打开文件,并将文件描述符作为结果返回,其语法格式如下:

os.open(filename, flags [, mode=0777]) -> fd

    filename用于指定文件的路径,flags可以指定访问模式(只读、只写等访问模式)。可选的mode参数则主要用于创建文件时,设置文件的读写执行的访问权限。

    在Linux系统中,我们可以通过man 2 open命令来查看open系统调用中可用的flags:

black@slack:~/test$ man 2 open
OPEN(2)                 Linux Programmer's Manual                 OPEN(2)

NAME
       open, creat - open and possibly create a file or device

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

       int creat(const char *pathname, mode_t mode);

DESCRIPTION
       .............................................

       The argument flags must include one of the following access modes:
       O_RDONLY,  O_WRONLY,  or  O_RDWR.   These request opening the file
       read-only, write-only, or read/write, respectively.

       In addition, zero or more file  creation  flags  and  file  status
       flags  can  be bitwise-or'd in flags.  The file creation flags are
       O_CREAT, O_EXCL, O_NOCTTY, and O_TRUNC.  The file status flags are
       all  of the remaining flags listed below.  The distinction between
       these two groups of flags is that the file  status  flags  can  be
       retrieved  and  (in some cases) modified using fcntl(2).  The full
       list of file creation flags and file status flags is as follows:

       .............................................


    flags参数中必须先指定读写访问模式,例如:O_RDONLY(只读模式),O_WRONLY(只写模式),O_RDWR(读写模式)。此外,还可以用按位或运算符包含一些额外的模式标志,例如:O_CREAT,O_EXCL,O_TRUNC等。这些flags值就定义在Python的os模块中。

    下面是一个简单的例子:

import os

fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 5)
os.close(fd)


    os.read可以根据文件描述符读取文件的内容,os.close则可以根据文件描述符来关闭打开的文件,这两个方法在后面会进行介绍。

    如果要向文件中写入数据,则需要使用os.O_WRONLY或者os.O_RDWR模式标志:

import os

fd = os.open('test.txt', os.O_WRONLY)
os.write(fd, "black world")
os.close(fd)
fd = os.open('test.txt', os.O_RDWR)
os.write(fd, "zlzlz")
os.close(fd)
fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 100)
os.close(fd)


    os.write可以根据文件描述符向文件中写入数据,该方法在稍后也会进行介绍,这段代码的执行结果如下:

black@slack:~/test$ python test.py
zlzlz world

black@slack:~/test$ 


    可以看到,在写入"zlzlz"时,它并不会清空原文件的内容," world"被残留了下来。如果想在打开文件时,清空原文件的内容的话,可以加入os.O_TRUNC标志:

import os

fd = os.open('test.txt', os.O_WRONLY | os.O_TRUNC)
os.write(fd, "black world")
os.close(fd)
fd = os.open('test.txt', os.O_RDWR | os.O_TRUNC)
os.write(fd, "zlzlz")
os.close(fd)
fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 100)
os.close(fd)


    这段代码执行后,就只会看到"zlzlz",不会再看到原文件中的其他内容了。

    如果要向文件中追加内容,也就是将数据写入到文件末尾,那么可以在写入模式中加入os.O_APPEND模式标志:

import os

fd = os.open('test.txt', os.O_WRONLY | os.O_APPEND)
os.write(fd, 'append...\n')
os.close(fd)


    如果加入os.O_CREAT标志,那么当文件不存在时,就可以创建指定的文件:

import os

fd = os.open('test.txt', os.O_WRONLY | os.O_CREAT, 00744)
os.write(fd, "black world\n")
os.close(fd)


    如果test.txt文件不存在,那么这段代码就会创建该文件,并将新创建的文件的读写执行权限设置为00744即-rwxr--r--的权限,这个mode权限参数只在创建文件时起作用。如果test.txt文件已存在,那么将以只写的方式打开文件。

    如果在os.O_CREAT标志的基础上,再加入os.O_EXCL标志的话,那么就可以确保os.open方法只会执行创建文件的操作,如果文件已存在,则会抛出异常:

import os

try:
	fd = os.open('test.txt', os.O_RDWR | os.O_CREAT | os.O_EXCL, 00744)
	os.write(fd, 'hello...\n')
	print 'create test.txt success!'
	os.close(fd)
except:
	print 'test.txt file exists..'


    这段代码的执行结果如下:

black@slack:~/test$ rm test.txt
black@slack:~/test$ python test.py
create test.txt success!
black@slack:~/test$ python test.py
test.txt file exists..
black@slack:~/test$ 


    前面提到的man 2 open命令所显示的flags标志中,并非所有的标志都存在于os模块内,不同操作系统所支持的标志也会有所不同。读者可以参考 https://docs.python.org/2/library/os.html#open-constants 该链接对应的Python官方手册,该链接页面就显示了哪些flags是通用的标志,哪些是专属于windows或者专属于Unix的标志等。

os模块的read方法:

    read方法的语法格式如下:

os.read(fd, n) -> string

    此方法可以从fd(文件描述符)所对应的文件中,读取最多n个字节的数据,并将这些数据以字符串的形式作为结果返回。例如下面这段代码:

import os

fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 10)
print os.read(fd, 5)
os.close(fd)
fd = os.open('test2.txt', os.O_RDONLY)
data = bytearray(os.read(fd, 5))
for x in data:
	print hex(x),
os.close(fd)


    这段代码的执行结果如下:

black@slack:~/test$ cat test.txt
hello
black@slack:~/test$ od -t x1 test2.txt
0000000 7b 03 ff 00 68
0000005
black@slack:~/test$ python test.py
hello


0x7b 0x3 0xff 0x0 0x68
black@slack:~/test$ 


    可以看到,os.read方法返回的字符串既可以存储普通的文本数据,还可以存储二进制数据,当文件已经读到结尾位置处时,os.read将返回空字符串。此外,在Windows中要读取二进制数据的话,os.open打开文件时,最好加上os.O_BINARY标志,这样二进制数据就不会经过特殊处理,能够被原样读取出来。

    os.read方法也是直接通过底层的read系统调用来读取文件数据的。之前介绍过的文件对象的read方法,则是通过fread(C标准库函数)来读取文件的数据的,fread在glibc内部最终也是通过read系统调用来读取文件的,fread在应用层面通常存在一层缓存,read系统调用读取出来的数据会先读到这个缓存中,再从缓存中读取数据。os.read由于直接通过read系统调用来读取数据,因此就没有这层缓存,只是在系统内核部分可能存在缓存。

    由于文件对象有缓存的机制,因此,如果将os.read与文件对象的read方法混合使用,那么很可能会得不到你想要的结果:

import os

fo = open('test.txt', 'r', 100)
print 'fileno:', fo.fileno()
print 'fo.read(5):', fo.read(5)
print 'os.read:', os.read(fo.fileno(), 10)
print 'fo.read(6):', fo.read(6)


    这段代码的执行结果如下:

black@slack:~/test$ cat test.txt
hello world
haha black world
zlzlzl...
black@slack:~/test$ python test.py
fileno: 3
fo.read(5): hello
os.read: 
fo.read(6):  world
black@slack:~/test$ 


    由于fo.read在读取文件时,会先将文件里的数据读取到100个字节的内部缓存中,test.txt的尺寸小于100个字节。因此,第一次fo.read(5)执行完后,实际上就已经将文件的内容读完了,文件指针也位于文件末尾了,os.read再去读时,就会返回空字符串。最后一个fo.read(6)在执行时并没有去读源文件,而是直接从内部缓存里读取的数据。

    因此,open内建函数所返回的文件对象,尽量使用自己的read方法。os.read则主要是配合os.open返回的文件描述符来读文件数据。

os模块的write方法:

    os.write方法的语法格式如下:

os.write(fd, data) -> byteswritten

    os.write可以将data数据写入到fd(文件描述符)对应的文件中,并将写入的字节数作为结果返回。例如下面这段代码:

import os

fd = os.open('test.txt', os.O_WRONLY | os.O_TRUNC)
os.write(fd, 'black world...\n')
print "write 'black world...' to test.txt"
os.close(fd)
fd = os.open('test2.txt', os.O_WRONLY | os.O_TRUNC)
bytes = [0x7b, 0x03, 0xff, 0x00, 0x77]
os.write(fd, bytearray(bytes))
print "write '0x7b 0x03 0xff 0x00 0x77' to text2.txt"
os.close(fd)


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
write 'black world...' to test.txt
write '0x7b 0x03 0xff 0x00 0x77' to text2.txt
black@slack:~/test$ cat test.txt
black world...
black@slack:~/test$ od -t x1 test2.txt
0000000 7b 03 ff 00 77
0000005
black@slack:~/test$ 


    可以看到,os.write方法既可以写入普通的文本数据,还可以写入二进制数据。在windows系统中,如果要写入二进制数据时,最好在os.open的第二个参数中加入os.O_BINARY标志,这样写入的数据就不会经过特殊的处理(比如换行符的转换处理等)。

    os.write在Python内部会直接通过write系统调用来完成写入操作。之前介绍过的文件对象的write方法,则是通过fwrite(C标准库函数)来进行的操作,fwrite在写入数据时,会先将数据写入缓存中,再将缓存里的数据通过write系统调用写入文件。

    os.write主要配合os.open返回的文件描述符来进行写入操作。如果是open内建函数返回的文件对象,那么尽量使用文件对象自身的write方法。如果混合使用os.write与文件对象的write方法,那么很可能会出现一些未知的缓存问题。

os模块的close方法:

    os.close可以将打开的文件描述符给关闭掉,其语法格式如下:

os.close(fd)

    fd为需要关闭的文件描述符,下面是一个简单的例子:

import os

fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 11)
os.close(fd)
print os.read(fd, 11)


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
black world
Traceback (most recent call last):
  File "test2.py", line 6, in <module>
    print os.read(fd, 11)
OSError: [Errno 9] Bad file descriptor
black@slack:~/test$ 


    如果在os.close关闭了fd文件描述符后,再使用该文件描述符进行读写操作的话,就会出现Bad file descriptor的错误。

    os.close在内部会直接通过底层的close系统调用来进行操作。之前介绍过的文件对象的close方法,则是通过fclose(C标准库函数)来关闭文件的,fclose在glibc内部最终也是通过close系统调用将文件关闭掉的。

    不要使用os.close去直接关闭文件对象中的文件描述符,因为手动通过os.close关闭文件对象的文件描述符时,文件对象并不会得到这方面的通知,Python会认为文件对象的文件描述符还是有效的,那么在Python退出并清理文件对象时,就会通过fclose去清理内部的FILE文件流结构,FILE文件流中还保存了已经失效了的文件描述符,fclose最终就会通过close系统调用再关闭一次该描述符,因此,就会出现close系统调用重复关闭同一个描述符的情况。这样就会发生异常,例如下面这段代码:

import os

fo = open('test.txt', 'r')
os.close(fo.fileno())
print 'os.close test.txt'


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
os.close test.txt
close failed in file object destructor:
IOError: [Errno 9] Bad file descriptor
black@slack:~/test$ 


    如果在windows中执行这段代码,在Python退出并清理未关闭的资源时,还会让Python停止工作而使其无法正常退出。因此,os.close主要用来关闭os.open打开的文件描述符,至于文件对象中的文件描述符,则请使用文件对象自身的close方法。

os模块的closerange方法:

    closerange方法的语法格式如下:

os.closerange(fd_from, fd_to)

    该方法会循环通过close系统调用将fd_from到fd_to(不包括fd_to在内)的文件描述符给关闭掉,它会调用的底层C函数为posix_closerange,定义在Python源码中的Modules/posixmodule.c文件里(因为该函数的C源码比较简短,因此就在这里显示出来):

static PyObject *
posix_closerange(PyObject *self, PyObject *args)
{
    int fd_from, fd_to, i;
    if (!PyArg_ParseTuple(args, "ii:closerange", &fd_from, &fd_to))
        return NULL;
    Py_BEGIN_ALLOW_THREADS
    for (i = fd_from; i < fd_to; i++)
        if (_PyVerify_fd(i))
            close(i);
    Py_END_ALLOW_THREADS
    Py_RETURN_NONE;
}


    可以看到,它只会将大于等于fd_from,并且小于fd_to的文件描述符给关闭掉。而且此方法不会对close系统调用的返回值做处理,也就不会去判断close系统调用是否执行成功,因此,该方法会忽略掉close关闭文件描述符时可能会发生的错误。因此,closerange方法等效于下面这段Python代码:

for fd in xrange(fd_low, fd_high):
    try:
        os.close(fd)
    except OSError:
        pass


    下面是一个简单的例子:

import os

fd = os.open('test.txt', os.O_RDONLY)
print os.read(fd, 100)
fd2 = os.open('test3.txt', os.O_RDONLY)
print os.read(fd2, 100)
print 'test.txt fd:', fd
print 'test3.txt fd:', fd2
os.closerange(fd, fd2+1)
print 'close test.txt and test3.txt'


    这段代码的执行结果如下:

black@slack:~/test$ cat test.txt
black world...
black@slack:~/test$ cat test3.txt
hello world
black@slack:~/test$ python test.py
black world...

hello world

test.txt fd: 3
test3.txt fd: 4
close test.txt and test3.txt
black@slack:~/test$ 


    由于closerange只会关闭大于等于第一个参数,并且小于第二个参数的文件描述符。因此,上面的closerange方法中,就需要将fd2加1,才能将fd与fd2都关闭掉。

os模块的fdopen方法:

    如果想将os.open返回的文件描述符封装成文件对象,那么就可以使用fdopen方法,此方法的语法格式如下:

os.fdopen(fd [, mode='r' [, buffering = -1]]) -> file_object

    fd参数为需要进行封装的文件描述符,可选的mode参数为r,w,r+,w+之类的读写访问模式,可选的buffering参数表示文件缓存方式(可以是行缓存,块缓存之类的)。mode与buffering的具体含义请参考之前的"Python基本的I/O操作 (一)"文章中的"open函数"部分。

    mode需要与fd的读写模式相兼容。例如,假设fd是以os.O_RDONLY只读模式打开的,那么mode就需要是'r'。假设fd是以os.O_WRONLY只写模式打开的,那么mode就需要是'w'。假设fd是以os.O_RDWR读写模式打开的,那么mode就需要是'r+'或'w+',等等。

    在Linux系统中,如果mode与fd的读写模式不兼容的话,fdopen在执行时就会抛出Invalid argument(无效的参数)的错误。在windows系统中,只有在执行具体的读写操作时,才会因为mode与fd模式不兼容而抛出Bad file descriptor的错误。

    此外,Linux系统中,如果mode是'a'或'a+'的追加模式的话,那么即便fd原本的读写模式中没有包含os.O_APPEND,也可以产生追加效果。但是在windows中(至少在作者的win7系统中),只有当fd原本的读写模式里包含了os.O_APPEND时,才能产生追加效果。因此,如果你想让你的Python脚本可以跨平台运行的话,最好是os.open在打开文件时,为fd设置了什么样的读写模式,那么在fdopen里就设置一个最接近的兼容的mode。

    fdopen的mode在设置为w或w+时,它并不会像open内建函数那样清空原文件的内容,只有当fd的读写模式中包含了os.O_TRUNC时,才能起到清空原文件内容的作用。

    以下是一个简单的例子:

import os

fd = os.open('test.txt', os.O_RDONLY)
print 'fd:', fd
fo = os.fdopen(fd, 'r')
print 'fo.fileno():', fo.fileno()
print fo.read()


    在fdopen将文件描述符封装为文件对象后,就可以使用文件对象中的read,write之类的方法来读写文件了。在程序退出时,Python还会自动清理文件对象,并在清理文件对象时将打开的文件描述符给关闭掉,这样就可以不用手动去关闭os.open打开的文件描述符了。这段代码的执行结果如下:

black@slack:~/test$ python test.py 
fd: 3
fo.fileno(): 3
black world...
hello everyone...

black@slack:~/test$ 


os模块的fsync与fdatasync方法:

    在上一篇文章中,我们介绍文件对象的flush方法时,就介绍过os.fsync方法。文件对象的flush方法可以将文件流中的缓存数据写入系统缓存,要将系统缓存中的数据同步到磁盘中,就需要使用os.fsync或者os.fdatasync方法了。

    其中,os.fsync既可以在unix系统里使用,又可以在windows系统中使用(从Python 2.2.3版本开始可以在windows中使用),而os.fdatasync只能在unix系统中使用,而且fdatasync无法在MacOS中使用。

    这两个方法的语法格式如下:

os.fsync(fd)
os.fdatasync(fd)

    它们都需要接受fd文件描述符为输入参数。os.fsync会调用的底层C函数为posix_fsync,os.fdatasync会调用的底层C函数为posix_fdatasync,这两个C函数都定义在Python源码中的Modules/posixmodule.c文件里。posix_fsync最终会通过fsync系统调用去执行具体的操作,posix_fdatasync则最终会通过fdatasync系统调用去执行具体的操作。

    在Linux中可以通过man fsync或者man fdatasync来查看这两个系统调用的详情。从man手册中可以看到,fsync会将所有修改过的系统缓存数据,包括与文件相关的metadata(元数据)信息都同步到磁盘中。fdatasync类似于fsync,只不过fdatasync只会同步一些必要的metadata(元数据)信息。所谓元数据信息指的是文件的大小,修改时间,访问时间之类的信息。但是在目前版本的Linux系统中,fsync与fdatasync其实是做的同一件事,它们都会把所有的元数据信息都同步到磁盘中,因此,就目前看来,它们在Linux中已经没有什么区别了。

    下面是一个简单的例子:

import os

fd = os.open('test.txt', os.O_RDWR | os.O_TRUNC)
os.write(fd, 'hello world..\n')
os.fsync(fd)
os.write(fd, 'black world..\n')
os.fdatasync(fd)
os.lseek(fd, 0, os.SEEK_SET)
print os.read(fd, 100)


    os.write会通过write系统调用直接将数据写入系统缓存中,因此,这里就只需通过os.fsync或者os.fdatasync来将系统缓存同步到磁盘即可,不需要像文件对象那样还要先用flush方法将文件流中的应用层缓存更新到系统缓存。这段代码的执行结果如下:

black@slack:~/test$ python test.py
hello world..
black world..

black@slack:~/test$ 


os模块的dup与dup2方法:

    dup与dup2方法主要用于进行输出重定向的。

    dup方法的语法格式如下:

os.dup(fd) -> fd2

    dup方法会返回一个新的文件描述符fd2,并让fd2与fd指向同一个底层的I/O对象,此时,对fd2进行的读写操作,等效于对fd进行读写操作。dup的主要目的是做一个临时的fd2,让其指向fd所指向的原I/O对象,当fd通过下面的dup2指向了别的I/O对象后,还可以通过fd2指向回原来的I/O对象。

    dup2方法的语法格式如下:

os.dup2(target_fd, fd)

    dup2方法可以让fd文件描述符指向target_fd所对应的I/O对象,从而可以实现输出重定向。

    例如下面这段代码:

import os
import sys

print 'hello world'
dup_stdout_fd = os.dup(sys.stdout.fileno())
fd = os.open('test.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
print 'now redirect stdout to test.txt'
os.dup2(fd, sys.stdout.fileno())
print "I'm be printed to test.txt"
print "because stdout has redirect to test.txt"
sys.stdout.flush() # Flush stdout stream buffer so it goes to correct file 
os.dup2(dup_stdout_fd, sys.stdout.fileno())
print "hello, I'm back!"


    这段代码的执行结果如下:

black@slack:~/test$ python test.py
hello world
now redirect stdout to test.txt
hello, I'm back!
black@slack:~/test$ cat test.txt
I'm be printed to test.txt
because stdout has redirect to test.txt
black@slack:~/test$ 


    在sys.stdout的文件描述符没有重定向到test.txt之前,print语句会将数据输出到命令行终端。在os.dup2将sys.stdout的文件描述符重定向到test.txt后,print语句就会将输出信息写入到test.txt文件。最后利用os.dup2与最开始通过os.dup新建的dup_stdout_fd,就可以将sys.stdout的文件描述符重新指向标准的输出设备。

    上面代码里,必须包含sys.stdout.flush()语句,才能在windows中让数据正确的写入到重定向的文件中。

os模块的chroot方法:

    chroot可以将当前进程的根目录修改为指定的路径,其语法格式如下(只存在于Unix系统中):

os.chroot(path)

    它可以将根目录修改为path参数所指定的路径,此方法最终会通过chroot系统调用去执行具体的操作,在Linux中可以通过man 2 chroot来查看该系统调用的详情:

black@slack:~/test$ man 2 chroot
CHROOT(2)                  Linux Programmer's Manual                 CHROOT(2)

NAME
       chroot - change root directory

SYNOPSIS
       #include <unistd.h>

       int chroot(const char *path);

       .............................................

DESCRIPTION
       chroot()  changes  the  root  directory  of the calling process to that
       specified in path.  This directory will be used for pathnames beginning
       with /.  The root directory is inherited by all children of the calling
       process.

       Only a privileged process (Linux: one with the CAP_SYS_CHROOT  capabil-
       ity) may call chroot().

       This  call changes an ingredient in the pathname resolution process and
       does nothing else.

       This call does not change the current working directory, so that  after
       the call '.' can be outside the tree rooted at '/'.  In particular, the
       superuser can escape from a "chroot jail" by doing:

           mkdir foo; chroot foo; cd ..

       This call does not close open file descriptors, and such file  descrip-
       tors may allow access to files outside the chroot tree.

       .............................................


    可以看到,chroot在将root directory(根目录)设置为指定的path路径后,以'/'开头的路径名就会受到其影响。根目录信息是可以被调用进程的子进程继承下来的。此外,只有具有特权级别的进程才能调用chroot。chroot不会改变当前的工作目录,因此,我们可以利用当前的工作目录来还原真实的根目录。

    下面是os.chroot的使用例子:

import os

os.chroot('tmpdir')
print 'change root directory of process to tmpdir'
fo = open('/tmp.txt')
print fo.read()
os.chdir('../../..')
print 'change current working directory to real root directory'
os.chroot('.')
print 'change the root directory of the process to the current working directory, the real root directory\n'
fo = open('/home/black/test/tmpdir/tmp.txt')
print fo.read()


    假设当前测试目录中存在一个tmpdir目录,tmpdir目录内还存在一个tmp.txt文件。上面代码会先通过chroot将调用进程的根目录指向tmpdir,这样,直接使用'/tmp.txt'就可以访问到tmpdir目录中的tmp.txt文件了。

    当然,在根目录发生改变的情况下,你就无法访问到/etc之类的目录了,因此,chroot可以在一定程度上提高系统的安全性。

    由于当前工作目录不会受到chroot的影响,上面代码就通过os.chdir('../../..')利用当前工作目录与上一级目录,先将当前工作目录切换到real root directory(真实的根目录),再通过chroot('.')将调用进程的根目录设置到当前工作目录,也就将进程的根目录还原为真实的根目录了。还原后,就可以正常访问/home之类的路径了。

    这段代码的执行结果如下:

black@slack:~/test$ sudo python test.py
change root directory of process to tmpdir
hello world

change current working directory to real root directory
change the root directory of the process to the current working directory, the real root directory

hello world

black@slack:~/test$ 


    由于只有特权级的进程才能调用chroot,因此,上面就使用了sudo命令来运行python。

os模块的fchdir方法:

    fchdir类似于chdir,它也可以修改进程的当前工作目录,只不过fchdir需要接受文件描述符作为输入参数,其语法格式如下(只存在于Unix系统中):

os.fchdir(fd)

    fd必须是与目录相关的文件描述符,不可以是普通文件的描述符。

    以下是一个简单的例子:

import os

fd = os.open('tmpdir', os.O_RDONLY)
os.fchdir(fd)
print '\nchange current working directory to tmpdir.\n'
os.close(fd)
print os.getcwd(), '\n'
fo = open('tmp.txt', 'r')
print 'content of ' + os.getcwd() + \
      '/tmp.txt:', fo.read()


    上面代码先通过os.open打开tmpdir目录,并将该目录对应的文件描述符作为输入参数传递给fchdir。这样,调用进程的当前工作目录就会改为tmpdir。可以通过os.getcwd方法来检测工作目录是否改变成功。这段代码的执行结果如下:

black@slack:~/test$ python test.py

change current working directory to tmpdir.

/home/black/test/tmpdir 

content of /home/black/test/tmpdir/tmp.txt: hello world

black@slack:~/test$ 


结束语:

    限于篇幅,本章先到这里,其他的内容将放到以后的章节中。

    OK,就到这里,休息,休息一下 o(∩_∩)o~~

    智慧、勤劳和天才,高于显贵和富有。

——  贝多芬
 
上下篇

下一篇: Python基本的I/O操作 (四)

上一篇: Python基本的I/O操作 (二)

相关文章

Python基本的I/O操作 (四)

Python的calendar模块

Python的变量类型

Python基本的I/O操作 (二)

Python循环语句

Python定义和使用模块