Why doesn't find . -delete delete current directory?

The members of findutils aware of it, it's for compatible with *BSD:

One of the reasons that we skip deletion of "." is for compatibility with *BSD, where this action originated.

The NEWS in findutils source code shows that they decided to keep the behavior:

#20802: If -delete fails, find's exit status will now be non-zero. However, find still skips trying to delete ".".

[UPDATE]

Since this question become one of the hot topic, so i dive into FreeBSD source code and come out a more convincing reason.

Let's see the find utility source code of FreeBSD:

int
f_delete(PLAN *plan __unused, FTSENT *entry)
{
    /* ignore these from fts */
    if (strcmp(entry->fts_accpath, ".") == 0 ||
        strcmp(entry->fts_accpath, "..") == 0)
        return 1;
...
    /* rmdir directories, unlink everything else */
    if (S_ISDIR(entry->fts_statp->st_mode)) {
        if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
            warn("-delete: rmdir(%s)", entry->fts_path);
    } else {
        if (unlink(entry->fts_accpath) < 0)
            warn("-delete: unlink(%s)", entry->fts_path);
    }
...

As you can see, if it doesn't filter out dot and dot-dot, then it will reach rmdir() C function defined by POSIX's unistd.h.

Do a simple test, rmdir with dot/dot-dot argument will return -1:

printf("%d\n", rmdir(".."));

Let's take a look how POSIX describe rmdir:

If the path argument refers to a path whose final component is either dot or dot-dot, rmdir() shall fail.

No reason was given why shall fail.

I found rename explain some reason:

Renaming dot or dot-dot is prohibited in order to prevent cyclical file system paths.

Cyclical file system paths ?

I look over The C Programming Language (2nd Edition) and search for directory topic, surprisingly i found the code is similar:

if(strcmp(dp->name,".") == 0 || strcmp(dp->name,"..") == 0)
    continue;

And the comment !

Each directory always contains entries for itself, called ".", and its parent, ".."; these must be skipped, or the program will loop forever.

"loop forever", this is same like how rename describe it as "cyclical file system paths" above.

I slightly modify the code and to make it run in Kali Linux based on this answer:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <dirent.h>
#include <unistd.h>

void fsize(char *);
void dirwalk(char *, void (*fcn)(char *));

int
main(int argc, char **argv) {
    if (argc == 1)
        fsize(".");
    else
        while (--argc > 0) {
            printf("start\n");
            fsize(*++argv);
        }
    return 0;
}

void fsize(char *name) {
    struct stat stbuf;
    if (stat(name, &stbuf) == -1 )  {
        fprintf(stderr, "fsize: can't access %s\n", name);
        return;
    }
    if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
        dirwalk(name, fsize);
    printf("%81d %s\n", stbuf.st_size, name);
}

#define MAX_PATH 1024
void dirwalk(char *dir, void (*fcn)(char *))
{
    char name[MAX_PATH];
    struct dirent *dp;

    DIR *dfd;

    if ((dfd = opendir(dir)) == NULL) {
            fprintf(stderr, "dirwalk: can't open %s\n", dir);
            return;
    }

    while ((dp = readdir(dfd)) != NULL) {
            sleep(1);
            printf("d_name: S%sG\n", dp->d_name);
            if (strcmp(dp->d_name, ".") == 0
                            || strcmp(dp->d_name, "..") == 0) {
                    printf("hole dot\n");
                    continue;
                    }
            if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name)) {
                    printf("mocha\n");
                    fprintf(stderr, "dirwalk: name %s/%s too long\n",
                                    dir, dp->d_name);
                    }
            else {
                    printf("ice\n");
                    (*fcn)(dp->d_name);
            }
    }
    closedir(dfd);
}

Let's see:

xb@dnxb:/test/dot$ ls -la
total 8
drwxr-xr-x 2 xiaobai xiaobai 4096 Nov 20 04:14 .
drwxr-xr-x 3 xiaobai xiaobai 4096 Nov 20 04:14 ..
xb@dnxb:/test/dot$ 
xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .                     
start
d_name: S..G
hole dot
d_name: S.G
hole dot
                                                                             4096 .
xb@dnxb:/test/dot$ 

It work correctly, now what if I comment out the continue instruction:

xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .
start
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
^C
xb@dnxb:/test/dot$

As you can see, I have to use Ctrl+C to kill this infinitely loop program.

The '..' directory read its first entry '..' and loop forever.

Conclusion:

  1. GNU findutils try to compatible with find utility in *BSD.

  2. find utility in *BSD internally use rmdir POSIX-compliant C function which dot/dot-dot is not allow.

  3. The reason of rmdir do not allow dot/dot-dot is prevent cyclical file system paths.

  4. The C Programming Language written by K&R shows the example of how dot/dot-dot will lead to forever loop program.


Because your find command returns . as result. From the info page of rm:

Any attempt to remove a file whose last file name component is ‘.’ or ‘..’ is rejected without any prompting, as mandated by POSIX.

So, it looks like find just sticks to POSIX rules in this case.


The rmdir system call fails with EINVAL if the last component of its argument path is ".". It's documented at http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html and the rationale for the behavior is:

The meaning of deleting pathname /dot is unclear, because the name of the file (directory) in the parent directory to be removed is not clear, particularly in the presence of multiple links to a directory.

Tags:

Directory

Find

Rm