53

When creating directories, mkdir -m <mode> <dir> provides for creating one or more directories with the given mode/permissions set (atomically).

Is there an equivalent for creating files, on the command line?

Something akin to:

open("file", O_WRONLY | O_APPEND | O_CREAT, 0777);

Is using touch followed by a chmod my only option here?


Edit: After trying out teppic's suggestion to use install, I ran it through strace to see how close to atomic it was. The answer is, not very:

$ strace install -m 777 /dev/null newfile
...
open("newfile", O_WRONLY|O_CREAT|O_EXCL, 0666) = 4
fstat(4, {st_mode=S_IFREG|0666, st_size=0, ...}) = 0
...
fchmod(4, 0600)                         = 0
close(4)                                = 0
...
chmod("newfile", 0777)                  = 0
...

Still, it's a single shell command and one I didn't know before.

quornian
  • 635

6 Answers6

64

You could use the install command with a dummy file, e.g.

install -b -m 755 /dev/null newfile

The -b option backs up newfile if it already exists. You can use this command to set the owner as well.

On Linux it's usually part of GNU coreutils or BusyBox, but it's also available on systems derived from 4.2BSD, e.g. FreeBSD, NetBSD or OpenBSD.

teppic
  • 4,611
  • It's a useful little command. As its name suggests it's really designed for installing software compiled from source code, to save having to use chmod, chown, and so forth all the time. – teppic Sep 07 '12 at 18:29
  • If only it took stdin! OK, /proc/self/fd/0 will do for my purpose. – proski Oct 28 '16 at 18:52
  • Note that mode 755 is the default permissions of files created with install, so -m 755 is not needed. – Kusalananda Nov 11 '19 at 07:20
  • 3
    @Kusalananda It's obviously just an example of how to set a mode, since that was the question, not how to set 755. – teppic Nov 13 '19 at 12:43
  • @proski another option is a process substitution; install -b -m 755 <(echo contents) newfile worked for me. – dimo414 Apr 25 '20 at 05:51
  • 4
    install does not atomically create files with the given permissions. It calls fchmod() separately. An unauthorised user may open the file between the creation and the permissions change, thus it possible to read contents later that they are not allowed to read (e.g. for a private key file). Thus install should not be used if file permission security is important. (This was edited into the question, but I think it's good to state it under this answer since it's currently marked accepted despite not fulfilling the requested "atomic" requirement.) – nh2 Feb 09 '22 at 11:35
29

touch always creates the file if it doesn't exist, always follows symbolic links, and always makes the file non-executable. You can decide the read and write bits through the umask.

(umask 077; touch file)  # creates a 600 (rw-------) file
(umask 002; touch file)  # creates a 664 (rw-rw-r--) file

“Safe” atomic file creation (in particular, with O_NOFOLLOW) is not possible with traditional shell tools. You can use sysopen in perl. If you have the BSD-inspired mktemp utility, it creates a file atomically with O_NOFOLLOW; you'll have to to call chmod afterwards if the default mode of 600 is not the right one.

  • I was in the process of writing out a more detailed explanation, but the wikipedia articles covers everything just fine. – jordanm Sep 05 '12 at 01:16
  • If only touch had the option of creating executable files, that's really what I'm after. Then umask could be used to tailor the details. Sadly there is no way with umask and touch to create executables. – quornian Sep 07 '12 at 15:42
  • @quornian You can't create a non-empty file atomically anyway. What's the point of creating an empty executable file atomically? Use touch followed by chmod +x. – Gilles 'SO- stop being evil' Sep 07 '12 at 15:44
  • 6
    You can create non-empty, executables files atomically with... ln or mv. You can always create the file with the right content and permissions in a directory created with umask 077 and move it or ln it afterwards. – Stéphane Chazelas Sep 09 '12 at 22:45
3

Building on @teppic's answer, if you want to create the file with content and mode at the same time (bash):

install -m 755 <(echo commands go here) newscript

<() places the output into a temporary file, see Process-Substitution

laktak
  • 5,946
1

With zsh's sysopen builtin (enabled with zmodload zsh/system) you can specify the mode with -m, still subject to umask, but you can set it to 0:

zmodload zsh/system
(umask 0 && sysopen -u fd -m 777 -o excl file)

Should be atomic. strace shows:

openat(AT_FDCWD, "file", O_RDONLY|O_CREAT|O_EXCL|O_NOCTTY, 0777) = 3

-o excl is for it to fail with a *"file exists"" error when the file already existed, so you can do (...) || chmod 777 file for instance. Replace with -o creat to just open it (still in read-only with -r) when it already existed. In any case, the fd is closed as soon as the subshell exists.

You can also set the uid and gid at the same time atomically if you're superuser and set the EGID and EUID variables (in that order) beforehand:

(umask 0 && EGID=456 EUID=123 && sysopen -u fd -m 777 -o excl file)
0

I don't think you can really do this from just standard tools, but if you have access to a python interpreter, you can do something like:

python3 -c 'import os, sys, shutil; shutil.copyfileobj(sys.stdin, open("filename.txt", "w", opener=lambda name, flag: os.open(name, flag, mode=0o631)))' <<< "file_content"

Expanded, the python script basically does:

import os, sys, shutil
file_opener = lambda name, flag: os.open(name, flag, mode=0o631)
dest = open("filename.txt", "w", opener=file_opener)
shutil.copyfileobj(sys.stdin, dest)

There's probably something similar in other scripting language interpreter's toolkit.

Output of strace:

$ strace python3 -c 'import os, sys, shutil; shutil.copyfileobj(sys.stdin, open("filename.txt", "w", opener=lambda name, flag: os.open(name, flag, mode=0o631)))' <<< "file_content"
...
openat(AT_FDCWD, "filename.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0631) = 3
ioctl(3, FIOCLEX)                       = 0
newfstatat(3, "", {st_mode=S_IFREG|0631, st_size=0, ...}, AT_EMPTY_PATH) = 0
ioctl(3, TCGETS, 0x7ffe4fce65e0)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
ioctl(3, TCGETS, 0x7ffe4fce6430)        = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
brk(0x55c88d356000)                     = 0x55c88d356000
read(0, "file_content\n", 65536)        = 13
brk(0x55c88d346000)                     = 0x55c88d346000
read(0, "", 65523)                      = 0
read(0, "", 65536)                      = 0
write(3, "file_content\n", 13)          = 13
...

Alternatively, if the only reason you care about atomicity is just to prevent the file content being read from a hostile process running as other users, and not necessarily the use of atomicity of open(..., mode) itself, you can just set the umask to 777 in a subprocess so that the file is first created to deny permission to everyone:

$ (umask 777; <<< "file_content" > filename.txt)
$ ls -lah filename.txt
---------- 1 lieryan lieryan 13 Sep  8 15:58 filename.txt

then set the file permission with the right value right afterwards:

$ chmod 631 filename.txt

Or, putting it all together:

$ (umask 777; <<< "file_content" > filename.txt; chmod 631 filename.txt)

This is still susceptible to race condition attacks from other processes running as the same user, but if you have to worry about that, you're probably worrying about the wrong thing.

Lie Ryan
  • 1,228
-1

I have a bash script in my home directory /home/anthony/touchmod.sh containing:

#!/bin/bash

touch "$2"
chmod "$1" "$2"

So if I need to create readme.txt with 644 permissions I can type:

~/touchmod.sh 644 readme.txt
cuonglm
  • 153,898
  • 4
    You likely did not understand the question. This is exactly what the questioner tries to avoid. – ceving Aug 24 '16 at 09:16