9

I am writing a Python script in which I write output to a temporary file and then move that file to its final destination once it is finished and closed. When the script finishes, I want the output file to have the same permissions as if it had been created normally through open(filename,"w"). As it is, the file will have the restrictive set of permissions used by the tempfile module for temp files.

Is there a way for me to figure out what the "default" file permissions for the output file would be if I created it in place, so that I can apply them to the temp file before moving it?

Ryan C. Thompson
  • 40,856
  • 28
  • 97
  • 159

4 Answers4

7

For the record, I had a similar issue, here is the code I have used:

import os
from tempfile import NamedTemporaryFile

def UmaskNamedTemporaryFile(*args, **kargs):
    fdesc = NamedTemporaryFile(*args, **kargs)
    # we need to set umask to get its current value. As noted
    # by Florian Brucker (comment), this is a potential security
    # issue, as it affects all the threads. Considering that it is
    # less a problem to create a file with permissions 000 than 666,
    # we use 666 as the umask temporary value.
    umask = os.umask(0o666)
    os.umask(umask)
    os.chmod(fdesc.name, 0o666 & ~umask)
    return fdesc
Pierre
  • 6,047
  • 1
  • 30
  • 49
  • 1
    Using `os.umask(0)` is a potential security issue, see https://bugs.python.org/issue21082. – Florian Brucker Sep 16 '20 at 14:31
  • Thanks! I had no idea about that problem (shame on me). I have updated the answer. Is it OK for you? – Pierre Sep 17 '20 at 18:13
  • 1
    I'm not sure about the optimal approach myself, but at least this doesn't leave the risk of world-writable files. Thanks for updating your answer and there's no reason to feel ashamed: your original answer is basically what everybody recommends, and I also just learned about the problem. – Florian Brucker Sep 19 '20 at 14:52
3

There is a function umask in the os module. You cannot get the current umask per se, you have to set it and the function returns the previous setting.

The umask is inherited from the parent process. It describes, which bits are not to be set when creating a file or directory.

  • 1
    So, should I call `os.umask` once with any value, save the return value, then call it again with that value to restore it, and then derive the appropriate permissions from that mask? – Ryan C. Thompson Aug 22 '11 at 20:00
  • why would you ever only be interested in what the umask _is_ and not what it _should be_? –  Aug 22 '11 at 21:42
  • are you sure you need to use `tempfile`? –  Aug 22 '11 at 21:57
  • Well, my reason for using the tempfile module is that if the output file already exists, I don't want to clobber it until after my new output file is already finished. So I buffer the output in a temp file until it is finished, and then move it to the desired filename. Is there a better way to do this? – Ryan C. Thompson Aug 23 '11 at 13:02
  • @Ryan: sure, just create the new file as `filename + 'new'` or whatever. –  Aug 23 '11 at 18:42
  • 1
    Well, the advantage of the tempfile module is that it gives you a filename that is guaranteed not to have already existed. – Ryan C. Thompson Aug 24 '11 at 01:27
  • *sigh* then copy the temp file over the target instead of moving it there. –  Aug 24 '11 at 03:58
  • 2
    @hop It's a legitimate task. In security for example, it might be necessary to check that the umask is not too permissive. The task itself might not be to fix a weak umask, but to report it. Silenty fixing it could mask the problem of a user sharing files intentionally/unintentionally on a locked-down system. –  Jun 04 '18 at 19:06
1

This way is slow but safe and will work on any system that has a 'umask' shell command:

def current_umask() -> int:
  """Makes a best attempt to determine the current umask value of the calling process in a safe way.

  Unfortunately, os.umask() is not threadsafe and poses a security risk, since there is no way to read
  the current umask without temporarily setting it to a new value, then restoring it, which will affect
  permissions on files created by other threads in this process during the time the umask is changed.

  On recent linux kernels (>= 4.1), the current umask can be read from /proc/self/status.

  On older systems, the safest way is to spawn a shell and execute the 'umask' command. The shell will
  inherit the current process's umask, and will use the unsafe call, but it does so in a separate,
  single-threaded process, which makes it safe.

  Returns:
      int: The current process's umask value
  """
  mask: Optional[int] = None
  try:
    with open('/proc/self/status') as fd:
      for line in fd:
        if line.startswith('Umask:'):
          mask = int(line[6:].strip(), 8)
          break
  except FileNotFoundError:
    pass
  except ValueError:
    pass
  if mask is None:
    import subprocess
    mask = int(subprocess.check_output('umask', shell=True).decode('utf-8').strip(), 8)
  return mask
Sam
  • 101
  • 1
  • 5
0
import os

def current_umask() -> int:
    tmp = os.umask(0o022)
    os.umask(tmp)
    return tmp

This function is implemented in some python packages, e.g. pip and setuptools.

draganHR
  • 2,578
  • 2
  • 21
  • 14