How to Set File Permissions in Python3

How to set file permissions in Python3?

OK. Let's start from the beginning. If you know the linux chmod you are already are the right track to understand.

In Python 2, you could simply do for example in your Django project:

>>> os.chmod('manage.py', 0666)

and now you will see that the permissions have changed:

git diff
diff --git a/manage.py b/manage.py
old mode 100755
new mode 100644

The Python call you just saw is the direct equivalent of doing in bash:

chmod 0666 manage.py

In later Python versions you can use os functions with octal numbers:

>>> os.chmod('manage.py', 0o666)

This might look strange in the beginning. But it's just the new grammar introduced in pep3127. So instead of feeding Python with a 0 followed by an octal number, you feed Python with 0o followed by an octal numbers.

Finally, the stat module has numerical constants which you can combine with a bitwise OR operation on them. It's very verbose, but let's take a look at it.

>>> stat.S_IRUSR  # this means user read permissions
256

Which looks like an Integer. But actually is interpreted as a binary number by os.chmod, so you should read this like that:

>>> "{0:b}".format(stat.S_IRUSR)
'100000000'
>>>"{0:b}".format(stat.S_IWUSR)
'10000000'

Ok, that's confusing, but checkout out len on each result, the former is 1 bit longer

>>> len("{0:b}".format(stat.S_IWUSR))
8
>>> len("{0:b}".format(stat.S_IRUSR))
9

We can combine these stat constants to get a proper desired file mode:

>>> "{0:b}".format(stat.S_IRUSR|stat.S_IWUSR) # this is read and write for the user
'110000000'

In case you wonder | here is not a pipe as in base. This is the bitwise OR. The bit wise combination took both positive bits and gave us a result with a length of 9, with the first two bits set to 1. Which means the user can read an write the files.

Check this:

>>> "{0:b}".format(stat.S_IRUSR|stat.S_IWUSR|stat.S_IWGRP|stat.S_IRGRP)
'110110000'

This start looking like the usual chmod in bash:

$ chmod 0660 manage.py
$ ls -l manage.py
-rw-rw---- 1 oznt oznt 805 Mar 31 16:38 manage.py

Bash represents the bits which are on not only as 1 or 0, but also as their meaning. So the 2nd bit from the left has is the read permission of the user, the 3rd is the write permission of the user. And the next group of bits are the group permission and so on.

Finally, you can combine the mode 666 in bash to this in Python:

os.chmod('manage.py', stat.S_IWGRP | stat.S_IRGRP | stat.S_IRUSR | stat.S_IWUSR | stat.S_IWOTH | stat.S_IROTH) 

And if that is too long, check again that you can do: os.chmod('manage.py', 0o666).

How can I change the file permissions in Python?

0640 is an octal number (that is the meaning of the leading 0, which doesn't count as a digit), and means the following permissions (see e.g. Wikipedia):

  • the first digit is for the file's owner, and (as 6 is 110 in binary, where the bits are read, write and execute permission respectively) means
    read and write permission;
  • the second digit is for the group, and (as 4 is 100 in binary) means read-only; and
  • the third digit is for other users, and means no permissions (0 is 000!)

Therefore in this case you want to combine S_IRUSR (user read), S_IWUSR (user write) and S_IRGRP (group read):

>>> import stat
>>> oct(stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP)
'0640'

You can see each permission individually by displaying the octal number in binary form:

>>> bin(0640)
'0b110100000'

This breaks down as follows:

 # USR
0b 110 100 000
# ^ user read (yes)
# ^ user write (yes)
# ^ user execute (no)

# GRP
0b 110 100 000
# ^ group read (yes)
# ^ group write (no)
# ^ group execute (no)

# OTH
0b 110 100 000
# ^ other read (no)
# ^ other write (no)
# ^ other execute (no)

How do you create in python a file with permissions other users can write

If you don't want to use os.chmod and prefer to have the file created with appropriate permissions, then you may use os.open to create the appropriate file descriptor and then open the descriptor:

import os

# The default umask is 0o22 which turns off write permission of group and others
os.umask(0)

descriptor = os.open(
path='filepath',
flags=(
os.O_WRONLY # access mode: write only
| os.O_CREAT # create if not exists
| os.O_TRUNC # truncate the file to zero
),
mode=0o777
)

with open(descriptor, 'w') as fh:
fh.write('some text')
# the descriptor is automatically closed when fh is closed

Using a custom opener will make things easier and less error-prone. open will generate the appropriate flags for our opener according to the requested mode (w):

import os

os.umask(0)

def opener(path, flags):
return os.open(path, flags, 0o777)

with open('filepath', 'w', opener=opener) as fh:
fh.write('some text')

Python 2 Note:

The built-in open() in Python 2.x doesn't support opening by file descriptor. Use os.fdopen instead; otherwise you'll get:

TypeError: coercing to Unicode: need string or buffer, int found.

How to change file permissions using os.chmod while preserving existing permissions?

All your attempts come quite close. The problem is that the | operator can't turn off bits. So when you do current_permissions | S_IRGRP, you're setting the right bits, but you're not turning off the write permission.

To turn off bits, you need &. There are a few good ways to do this.

The one I'd choose is probably to unset all group permissions, and set them to what you want:

(current_permissions & ~S_IRWXG) | S_IRGRP

The first part unsets all group permissions, and the second part applies read only mode as before.

Another way would be to disable everything but user and other sections, then set the group:

(current_permissions & (S_IRWXU | S_IRWXO)) | S_IRGRP

The two are generally equivalent, unless you have some weird bits set in your permissions.

How to set folder permissions in Windows?

You want the win32security module, which is a part of pywin32. Here's an example of doing the sort of thing you want to do.

That example creates a new DACL for the file and replaces the old one, but it's easy to modify the existing one; all you need to do is get the existing DACL from the security descriptor instead of creating an empty one, like so:

import win32security
import ntsecuritycon as con

FILENAME = "whatever"

userx, domain, type = win32security.LookupAccountName ("", "User X")
usery, domain, type = win32security.LookupAccountName ("", "User Y")

sd = win32security.GetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION)
dacl = sd.GetSecurityDescriptorDacl() # instead of dacl = win32security.ACL()

dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_GENERIC_READ | con.FILE_GENERIC_WRITE, userx)
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, con.FILE_ALL_ACCESS, usery)

sd.SetSecurityDescriptorDacl(1, dacl, 0) # may not be necessary
win32security.SetFileSecurity(FILENAME, win32security.DACL_SECURITY_INFORMATION, sd)

Removing permissions using chmod

You don't remove a permission. You set a new permission. So you can set the permission to read or read+write; those variants don't have executable permission.

If you AND (&) the current permission with a combined read + write permission, it should effectively remove the execute permission. So,

os.chmod(filepath, permissions.st_mmode & (stat.IRGRP | stat.IWGRP)

Only the common modes will remain in the second argument, and any execute permission will be removed.

Disclaimer: I haven't checked nor used this, but if it's all bitmasks, this should work.



Related Topics



Leave a reply



Submit