How to delete a directory and its contents in (POSIX) C?
Use the nftw()
(File Tree Walk) function, with the FTW_DEPTH
flag. Provide a callback that just calls remove()
on the passed file:
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <ftw.h>
#include <unistd.h>
int unlink_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
{
int rv = remove(fpath);
if (rv)
perror(fpath);
return rv;
}
int rmrf(char *path)
{
return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS);
}
I just cracked open the GNU rm source and see what exactly it does:
http://www.gnu.org/software/coreutils/
rm relies on the following functions:
fts_open
fts_read
fts_set
fts_close
which have man pages on both linux and mac.
- You need to use
nftw()
(or possiblyftw()
) to traverse the hierarchy. - You need to use
unlink()
to remove files and other non-directories. - You need to use
rmdir()
to remove (empty) directories.
You would be better off using nftw()
(rather than ftw()
) since it gives you controls such as FTW_DEPTH
to ensure that all files under a directory are visited before the directory itself is visited.
You can write own implementation command "rm -rf" on pure the C programming language. Source code based only on headers: dirent.h, sys/stat.h and unistd.h. If you need portable code to other systems, as example to the Windows, you need only change headers with corresponding functionality, at the same time the algorithm will not be changed.
A file rmtree.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// POSIX dependencies
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
void
rmtree(const char path[])
{
size_t path_len;
char *full_path;
DIR *dir;
struct stat stat_path, stat_entry;
struct dirent *entry;
// stat for the path
stat(path, &stat_path);
// if path does not exists or is not dir - exit with status -1
if (S_ISDIR(stat_path.st_mode) == 0) {
fprintf(stderr, "%s: %s\n", "Is not directory", path);
exit(-1);
}
// if not possible to read the directory for this user
if ((dir = opendir(path)) == NULL) {
fprintf(stderr, "%s: %s\n", "Can`t open directory", path);
exit(-1);
}
// the length of the path
path_len = strlen(path);
// iteration through entries in the directory
while ((entry = readdir(dir)) != NULL) {
// skip entries "." and ".."
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
// determinate a full path of an entry
full_path = calloc(path_len + strlen(entry->d_name) + 1, sizeof(char));
strcpy(full_path, path);
strcat(full_path, "/");
strcat(full_path, entry->d_name);
// stat for the entry
stat(full_path, &stat_entry);
// recursively remove a nested directory
if (S_ISDIR(stat_entry.st_mode) != 0) {
rmtree(full_path);
continue;
}
// remove a file object
if (unlink(full_path) == 0)
printf("Removed a file: %s\n", full_path);
else
printf("Can`t remove a file: %s\n", full_path);
free(full_path);
}
// remove the devastated directory and close the object of it
if (rmdir(path) == 0)
printf("Removed a directory: %s\n", path);
else
printf("Can`t remove a directory: %s\n", path);
closedir(dir);
}
int
main(const int argc, char const *argv[])
{
if (argc != 2) {
fprintf(stderr, "Missing single operand: path\n");
return -1;
}
rmtree(argv[1]);
return 0;
}
Examine it.
I am use a shell script for generation a file/folder structure.
$ cat script.sh
mkdir -p dir1/{dir1.1,dir1.2,dir1.3}
mkdir -p dir1/dir1.2/{dir1.2.1,dir1.2.2,dir1.2.3}
mkdir -p dir2/{dir2.1,dir2.2}
mkdir -p dir2/dir2.2/dir2.2.1
mkdir -p dir2/dir2.2/{dir2.2.1,dir2.2.2}
mkdir -p dir3/dir3.1
mkdir -p dir4
mkdir -p dir5
touch dir1/dir1.1/file.scala
touch dir1/dir1.2/file.scala
touch dir2/dir2.2/{file.c,file.cpp}
touch dir2/dir2.2/dir2.2.2/{file.go,file.rb}
touch dir3/{file.js,file.java}
touch dir3/dir3.1/{file.c,file.cpp}
> dir4/file.py
Run the script
$ ./script.sh
Generated the file/folder structure
$ tree
.
├── dir1
│ ├── dir1.1
│ │ └── file.scala
│ ├── dir1.2
│ │ ├── dir1.2.1
│ │ ├── dir1.2.2
│ │ ├── dir1.2.3
│ │ └── file.scala
│ └── dir1.3
├── dir2
│ ├── dir2.1
│ └── dir2.2
│ ├── dir2.2.1
│ ├── dir2.2.2
│ │ ├── file.go
│ │ └── file.rb
│ ├── file.c
│ └── file.cpp
├── dir3
│ ├── dir3.1
│ │ ├── file.c
│ │ └── file.cpp
│ ├── file.java
│ └── file.js
├── dir4
│ └── file.py
├── dir5
├── rmtree.c
└── script.sh
16 directories, 13 files
Build the source code of the file rmtree.c by the GCC
$ cc -o -Wall -Werror -o rmtree rmtree.c
Remove a directory dir1/dir1.1
$ ./rmtree dir1/dir1.1
Removed a file: dir1/dir1.1/file.scala
Removed a directory: dir1/dir1.1
Remove a directory dir1/dir1.2
$ ./rmtree dir1/dir1.2
Removed a directory: dir1/dir1.2/dir1.2.3
Removed a file: dir1/dir1.2/file.scala
Removed a directory: dir1/dir1.2/dir1.2.1
Removed a directory: dir1/dir1.2/dir1.2.2
Removed a directory: dir1/dir1.2
Remove a directory dir1/
$ ./rmtree dir1
Removed a directory: dir1/dir1.3
Removed a directory: dir1
Remove a directory dir2/dir2.2/dir2.2.2
$ ./rmtree dir2/dir2.2/dir2.2.2
Removed a file: dir2/dir2.2/dir2.2.2/file.rb
Removed a file: dir2/dir2.2/dir2.2.2/file.go
Removed a directory: dir2/dir2.2/dir2.2.2
Remove a directory dir2/
$ ./rmtree dir2
Removed a directory: dir2/dir2.1
Removed a file: dir2/dir2.2/file.c
Removed a directory: dir2/dir2.2/dir2.2.1
Removed a file: dir2/dir2.2/file.cpp
Removed a directory: dir2/dir2.2
Removed a directory: dir2
Remove a directory dir3/dir3.1
$ ./rmtree dir3/dir3.1
Removed a file: dir3/dir3.1/file.c
Removed a file: dir3/dir3.1/file.cpp
Removed a directory: dir3/dir3.1
Remove a directory dir3
$ ./rmtree dir3
Removed a file: dir3/file.js
Removed a file: dir3/file.java
Removed a directory: dir3
Remove a directory dir4
$ ./rmtree dir4
Removed a file: dir4/file.py
Removed a directory: dir4
Remove a empty directory dir5
$ ./rmtree dir5
Removed a directory: dir5
If a passed path is not exists or is not path of a directory, you will can see next:
$ ./rmtree rmtree.c
Is not directory: rmtree.c
$ ./rmtree 11111111111111111
Is not directory: 11111111111111111
See results
$ tree
.
├── rmtree
├── rmtree.c
└── script.sh
0 directories, 3 files
Testing environment
$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.7 (jessie)
Release: 8.7
Codename: jessie
$ uname -a
Linux localhost 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux
$ cc --version
cc (Debian 4.9.2-10) 4.9.2