Subject: Re: bug#24730: rmdir/mkdir error(s) and/or not working "reciprocally" w/each other

From: Assaf Gordon <[email-masked]>
Date: Tue, 18 Oct 2016 16:57:45 -0400

Hello,

Before deciding on the wording, it's worth nothing that the errors and reasons for the errors are different between mkdir and rmdir, and between the two cases.

On 10/18/2016 03:49 PM, L. A. Walsh wrote:

> mkdir -p ./a/b/c   # no error
> rmdir -p ./a/b/c   # get error msg, but a,b,c removed.

The error in this case (at least on Linux) is "Invalid Argument",
because 'rmdir .' is invalid and rejected by the kernel (EINVAL)
while 'mkdir .' returns EEXISTS - and '-p' specifically instruct it to silently ignore EEXIST.

Demonstrated another way:

'mkdir' without -p:

     $ strace -e mkdir mkdir .
     mkdir(".", 0777)                        = -1 EEXIST (File exists)
     mkdir: cannot create directory ‘.’: File exists
     +++ exited with 1 +++

'mkdir' with -p:

     $ strace -e mkdir mkdir -p .
     mkdir(".", 0777)                        = -1 EEXIST (File exists)
     +++ exited with 0 +++

but 'rmdir' gives an unrecoverable error because of the kernel,
not because of coreutils' code:

     $ strace -e rmdir rmdir .
     rmdir(".")                              = -1 EINVAL (Invalid argument)
     rmdir: failed to remove '.': Invalid argument

This is also mandated by posix:
    http://pubs.opengroup.org/onlinepubs/9699919799/functions/rmdir.html
    "If the path argument refers to a path whose final component is either dot or dot-dot, rmdir() shall fail."

However, if there is no dot component in the path, they do behave similarly (or reciprocally, as you've said).

> mkdir -p a/../b      # no error
> rmdir -p a/../b      # error, but a & b removed

At least on my system (Linux kernel 3.13), 'a' is not removed in the above example,
and the error is due to non-empty directory:

     $ strace -e mkdir mkdir -p a/../b
     mkdir("a", 0777)                        = 0
     mkdir("b", 0777)                        = 0
     +++ exited with 0 +++

     $ strace -e rmdir rmdir -p a/../b
     rmdir("a/../b")                         = 0
     rmdir("a/..")                           = -1 ENOTEMPTY (Directory not empty)
     rmdir: failed to remove directory 'a/..': Directory not empty
     +++ exited with 1 +++

So two cases these are slightly different (EINVAL vs ENOTEMPTY).

Note that coreutils' mkdir contains an optimization not to try and make '..' as it is
guaranteed to exist (implemented in gnulib's mkancesdirs.c).

However, by definition, when 'rmdir' traverses the directories on the given path,
the directory 'a/..' is not empty (it contains 'a') - so this must fail.

If you want to 'rmdir' to silently ignore non-empty directories,
there's a gnu extension option of 'rmdir --ignore-fail-on-non-empty':

     $ strace -e rmdir rmdir --ignore-fail-on-non-empty -p a/../b
     rmdir("a/../b")                         = 0
     rmdir("a/..")                           = -1 ENOTEMPTY (Directory not empty)
     +++ exited with 0 +++

But note that this causes 'rmdir' to stop upon first failure, and 'a' is still not removed.

> ======> seems to be best wording & solution:
>
> "mkdir -p", it seems should really be restated to:
>
>    follow given path and make directories as possible"
>
> then "rmdir -p" could be
>
>    "follow given path and delete directories if empty"

This does not accurately reflect how 'rmdir' is currently implemented.
A more accurate description is "follow given path and delete directories, until the first encountered failure".

     $ mkdir -p a/../b/../c/../d

     $ strace -e rmdir rmdir -p a/../b/../c/../d
     rmdir("a/../b/../c/../d")               = 0
     rmdir("a/../b/../c/..")                 = -1 ENOTEMPTY (Directory not empty)
     rmdir: failed to remove directory 'a/../b/../c/..': Directory not empty
     +++ exited with 1 +++

     $ ls
     a  b  c

If you want a behavior where 'rmdir' will continue beyond the first failure
and try to delete all directories in the given path (e.g. a/b/c in the example above),
then it sounds like a new feature request, and a non-standard behavior which will be a gnu extension (and also quite confusing behavior, IMHO).

regards,
  - assaf