4

In Linux the following two commands work as expected:

mkdir -m 555 new_directory
mkdir -p a/b/c

But the following does not work as expected:

mkdir -m 555 -p a/b/c

the 3 directories are created but only the latest recieves the 555 permission. The a and b directories have the default permissions.

So how accomplish the goal described in the title? Is it possible?

BTW - I selected 555 how a random case, it fails with 666 and 777 too

Manuel Jordan
  • 1,728
  • 2
  • 16
  • 40

4 Answers4

3

If you expressly list the directories, parent first, you can achieve your stated aim of creating the directories in one command:

mkdir -m 555 -p a a/b a/b/c

With shells with support for csh-style brace expansion such as bash you can simplify this a little at the expense of readability:

mkdir -m 555 -p a{,/b{,/c}}

Notice, however, that for permissions 555 both commands will fail if it actually needs to create any of the parent directories: such directories are created with permissions that do not allow writing, and therefore next level directories cannot be created.

Finally, a bash shell script that will also give you the functionality to create the multiple directories in one command as requested, by wrapping the complexity in a function. This one will attempt to apply the permissions to newly created directories from the bottom up, so it will be possible to end up with directories that have no write permission:

mkdirs()
{
    local dirs=() modes=() dir old
# Grab arguments
[[ "$1" == '-m' ]] && modes=('-m' "$2") && shift 2
dir=$1

# Identify missing directories
while [[ "$dir" != "$old" ]]
do
    [[ ! -d "$dir" ]] && dirs+=("$dir")
    old="$dir"
    dir="${dir%/*}"
done

# Create necessary directories and maybe fix up permissions
for dir in "${dirs[@]}"
do
    mkdir -p "${modes[@]}" "$dir" || return 1
    [[ -n "${modes[1]}" ]] && chmod "${modes[1]}" "$dir"
done

}

Example

mkdirs -m 555 a/b/c

ls -ld a a/b a/b/c dr-xr-xr-x+ 1 roaima roaima 0 Jan 7 10:01 a dr-xr-xr-x+ 1 roaima roaima 0 Jan 7 10:01 a/b dr-xr-xr-x+ 1 roaima roaima 0 Jan 7 10:01 a/b/c

As always, this function can be put standalone into an executable script that's somewhere in your $PATH:

#!/bin/bash
mkdirs()
{
    ...as above...
}

mkdirs "$@"

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • if I try mkdir -m 555 -p r{,/s{,/t}} I receive twice: mkdir: cannot create directory ‘r/s’: Permission denied – Manuel Jordan Jan 07 '22 at 00:07
  • 1
    Yes of course you will. Please read the last paragraph of my answer – Chris Davies Jan 07 '22 at 00:10
  • Sorry, tired so far, I didn't read well that part - both commands work, but seems mandatory apply the verbose approach about repeat the names. It for complex/long paths would be something 'ugly' for scripts purposes. Is there a way to avoid that? – Manuel Jordan Jan 07 '22 at 00:18
  • huge thanks for the extra code. Valuable your feedback for all the community. – Manuel Jordan Jan 07 '22 at 12:32
  • Perhaps I've misunderstood your first paragraph, but mkdir -m 555 -p a a/b a/b/c fails under bash on two BSD variants I tested (FreeBSD and Mac OSX). FreeBSD reports: mkdir: a/b: Permission denied\nmkdir: a/b: Permission denied while Mac OSX reports: mkdir: a/b: Permission denied\nmkdir: a/b/c: Permission denied – Jim L. Jan 10 '22 at 21:47
  • Actually, it fails under Ubuntu as well, so it may well be that I am misunderstanding your solution. It does work on any of the platforms as root, perhaps because root can override the lack of write access. Thanks for your note re: writability, fixed: ls -ld . drwxrwxr-x 3 username root 4096 Jan 10 13:55 . That takes away the top-level permission denied on ./a. Remaining errors as before: mkdir: cannot create directory ‘a/b’: Permission denied\nmkdir: cannot create directory ‘a/b’: Permission denied` – Jim L. Jan 10 '22 at 21:53
  • @JimL. I've just realised what you're trying to do. The first command will indeed fail (please read the paragraph starting "Notice...". I included it because it mimicked the command that the OP was trying to use. I've emphasised this warning as part of the explanation – Chris Davies Jan 10 '22 at 22:02
2

It does look like mkdir doesn't apply the mode set by -m when it creates the intermediary directories. But, at least in most cases, you can use the umask to modify the permissions it sets for them.

The umask works by clearing the permission bits that are set in the umask, so if you want the intermediaries to have e.g. the permissions 0700, set umask to 0077. Using a subshell here to contain the umask change:

$ ( umask 0077; mkdir -p a/b )
$ ls -ld a
drwx------ 3 ilkkachu ilkkachu 4096 Jan  7 07:23 a/
$ ls -ld a/b
drwx------ 2 ilkkachu ilkkachu 4096 Jan  7 07:23 a/b/

Similarly, to get the permissions 0555, you'd set umask to 0222. But this doesn't actually work for the intermediaries, they are created with permissions 0755 instead. That is, the write permission for the owning user is added.

POSIX specifies for the permissions of the intermediate pathname components (see under the -p option):

[...] the mkdir utility shall create any pathname components of the path prefix of dir that do not name an existing directory [by equivalent of creating the missing directory] and then calling the chmod() function with the following arguments: [...]

 2. The value (S_IWUSR|S_IXUSR|~filemask)&0777 as the mode argument, where filemask is the file mode creation mask of the process.

(S_IWUSR and S_IXUSR are the C constants for those permissions bits.)

The GNU coreutils online manual says:

-p, --parents
Make any missing parent directories for each argument, setting their file permission bits to =rwx,u+wx, that is, with the umask modified by u+wx. Ignore existing parent directories, and do not change their file permission bits.

It goes on to say that the -m option doesn't apply to these, but that you can hence use the umask to control the permissions of them.

(Though the text there looks off, it mentions "The umask must include u=wx for this method to work", which seems to be missing the inverted sense of the umask.)


Presumably mkdir does that because otherwise it wouldn't be able to create the next directory inside the one just created without write permissions. The same happens for the access / search (x) permission bit, for the same reason.

So, if you want to create directories you can't write to or search yourself, it looks like you'll just have to chmod them after. Otherwise, setting umask for the mkdir works.

ilkkachu
  • 138,973
0

You could use the install command (not standard, from BSD in the 80s also included in GNU coreutils) as:

install -m 555 -d a/b/c a/b a

Or with shells with support for csh-style brace expansion:

install -m 555 -d a{/b{/c,},}

Note that if the directory components already existed beforehand, their permissions (the permissions of the directories they point to for symlinks) will be changed to 0555.

To only change the permissions of directory components that are being newly created, you could do it as a function. For instance, in zsh:

mkdirhierperm() {
  local mode=$1 dir=$2 dirs=()
  until [[ -d $dir ]] dirs[1,0]=$dir dir=$dir:h
  if (( $#dirs == 0 )); then
    (umask 77 && mkdir -p -- $dirs[-1]) &&
      chmod -- $mode $dirs
  fi
}

mkdirhierperm 555 a/b/c mkdirhierperm 555 /full/path/to/some/new/dir

0

One might do this in 2 steps: first call mkdir to create the directories and then run find . -type d -exec chmod 555 {} \; to modify the modes.