Segfault when deleting non-empty directory using C - c

I am trying to delete a none-empty directory without system calls and without using extensive libraries. My code so far is...
int rmrf(char *path) {
char* path_copy = (char *) malloc(1024 * sizeof(char));
strcpy(path_copy, path);
DIR *directory = opendir(path_copy);
struct dirent *entry = readdir(directory);
while (entry != NULL) {
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { //skip /. and /..
} else if (entry->d_type == DT_DIR) { //directory recurse
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
rmrf(path_copy);
remove(path);
} else { //file delete
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
remove(path_copy);
}
entry = readdir(directory);
}
closedir(directory);
return 0;
}
my current file structure looks something like this...
Who
|---Region 1
|---County 1
|---SubCounty 1
|---County 2
|---Region 2
|---County 1
|---Region 3
currently I am getting seg faults but in different places as the day progresses. Earlier today I would get about two levels of recursion deep and then seg fault out but as of now I can't even make past a full level down. I can't figure out what is wrong and when I use gdb to look into the problem I get...
malloc.c: No such file or directory.
Any help would be appreciated!
UPDATE:
I have taken suggestions from paxdiablo and came up with the resulting function...
int rmrf(char *path) {
char* path_copy = malloc(1024);
DIR *directory = opendir(path);
struct dirent *entry = readdir(directory);
while (entry != NULL) {
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { //skip /. and /..
} else if (entry->d_type == DT_DIR) { //directory recurse
strcpy(path_copy, path);
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
rmrf(path_copy);
remove(path);
} else { //file delete
strcpy(path_copy, path);
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
remove(path_copy);
}
entry = readdir(directory);
}
closedir(directory);
free(path_copy);
return 0;
}
however I am still getting a seg fault though it is getting further in the recursion. The gdb output for the seg fault is as followed...
Program received signal SIGSEGV, Segmentation fault.
_int_malloc (av=av#entry=0x7ffff7dd1b20 <main_arena>, bytes=bytes#entry=32816) at malloc.c:3802
3802 malloc.c: No such file or directory.
(gdb) where
#0 _int_malloc (av=av#entry=0x7ffff7dd1b20 <main_arena>, bytes=bytes#entry=32816) at malloc.c:3802
#1 0x00007ffff7a91184 in __GI___libc_malloc (bytes=32816) at malloc.c:2913
#2 0x00007ffff7ad51ba in __alloc_dir (statp=0x7fffffffe190, flags=0, close_fd=true, fd=6) at ../sysdeps/posix/opendir.c:247
#3 opendir_tail (fd=6) at ../sysdeps/posix/opendir.c:145
#4 __opendir (name=<optimized out>) at ../sysdeps/posix/opendir.c:200
#5 0x0000000000401bca in rmrf ()
#6 0x0000000000401c8d in rmrf ()
#7 0x0000000000401c8d in rmrf ()
#8 0x0000000000402380 in main ()
Thoughts?

For your initial code, you do this once when entering the function:
strcpy(path_copy, path);
Then you do this for each file or directory in the current directory:
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
That means, if you have the files a, b and c in your current directory /xx, the path_copy variable will cycle through:
/xx/a /xx/a/b /xx/a/b/c
rather than the correct:
/xx/a /xx/b /xx/c
With a sufficiently large number of files, you will easily blow out the 1024 bytes allocates for the path.
If you want to fix that then you should start the variable from scratch each time:
if ((strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0)) {
if (entry->d_type == DT_DIR) {
strcpy(path_copy, path);
strcat(path_copy, "/");
strcat(path_copy, entry->d_name);
rmrf(path_copy);
remove(path);
} else {
sprintf(path_copy, "%s/%s", path, entry->d_name);
remove(path_copy);
}
}
You'll note that I've modified your initial condition a little so that it makes more sense (only do the inner bit if the file is neither . nor ..).
I've also shown, in the else clause, a shorter way of constructing the string to delete using sprintf rather than a set of strcpy/strcat calls. Feel free to do that in the if clause as well if you wish, I've left it using the old method so you can see all you needed to do was add the initial path.
And just a few extra points, applicable to your first and/or second code snippet:
You should also make sure you free the memory you allocate at each level, immediately before returning from the function, between closedir() and return.
You never need to cast the return value of malloc since a void * can be implicitly cast to any other type of pointer. In fact, it's dangerous to do so since it can hide certain subtle errors.
Similarly, you never need to multiply by sizeof(char) - that is, by definition, always one.
You can move the creation of path_copy to before the file/directory check since it's common to both parts.
And, finally, you're going to have troubles if the directory you're processing doesn't actually exist since opendir will return NULL and you will immediately try to pass that to readdir.
With all that in mind, I'd start with the following program which actually walks the tree and prints out all the files it finds. Once you're happy with that, you can add back in the bit that deletes:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int rmrf(char *path) {
char *path_copy = malloc(1024);
DIR *directory = opendir(path);
if (directory != NULL) {
struct dirent *entry = readdir(directory);
while (entry != NULL) {
if ((strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0)) {
sprintf(path_copy, "%s/%s", path, entry->d_name);
if (entry->d_type == DT_DIR) {
rmrf(path_copy);
puts(path);
} else {
puts(path_copy);
}
}
entry = readdir(directory);
}
closedir(directory);
}
free(path_copy);
return 0;
}
The main code is just a driver to ensure thinks are set up correctly. Just make sure, before running, you don't have (in your current directory) a paxtest or paxtest2 file or directory you want to keep around.
int main(void) {
system("rm -rf paxjunk");
system("mkdir paxjunk");
system("touch paxjunk/0.txt");
system("mkdir paxjunk/1");
system("touch paxjunk/1/1.txt");
system("mkdir paxjunk/2");
system("touch paxjunk/2/2.txt");
rmrf("paxjunk");
puts("===");
system("rm -rf paxjunk2");
rmrf("paxjunk2");
puts("===");
system("rm -rf paxjunk");
return 0;
}
When you run this, you should see it working okay:
paxjunk/0.txt
paxjunk/1/1.txt
paxjunk
paxjunk/2/2.txt
paxjunk
===
===

Related

C - word array keeps getting overridden

#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
// This program is going to scan all files in the current directory. It will make a tree for every folder
// and the folder will have a subsection of files in the tree format. YES SORTING!
char **word;
int coun = 0;
void printdir(char *dir, int depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
if ((dp = opendir(dir)) == NULL)
{
fprintf(stderr,"cannot open directory: %s\n", dir);
return;
}
chdir(dir);
while((entry = readdir(dp)) != NULL)
{
lstat(entry->d_name,&statbuf);
if (S_ISDIR(statbuf.st_mode)) // Check if it's a directory
{
/* Found a directory, but ignore . and .. */
if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)
{
continue;
}
word[coun] = ("%s", entry->d_name); // Put the file name in the array.
coun++;
printf("- %*s%s\n", depth, "", entry->d_name); // Print the name of the dir entry.
/* Recurse at a new indent level */
printdir(entry->d_name, depth + 1);
}
else
{
word[coun] = ("%s", entry->d_name); // Put the file name in the array.
coun++;
printf("%*s - %s\n", depth, "", entry->d_name); // This will print the file.
}
}
chdir("..");
closedir(dp);
}
int main(int argc, char* argv[])
{
word = calloc(1000, sizeof(*word));
printdir(".", 0);
printf("now, print the words in the order they were printed.\n");
for (int i = 0; i < coun; ++i)
{
printf("%s\n", word[i]);
}
exit(0);
}
My main aim for this code is to make a tree structure of the files that are currently in the directory. When I run it, I get this output.
- hw2
- tree
- Makefile
- ls.c
- tree.c
- find.c
- hw1
- grep.c
- factor.c
- uniq.c
- monster.c
- sort.c
- .nfs0000000006c543ea0000e073
- tree.c
- tree
now, print the words in the order they were printed.
hw2
grep.c
hw1
grep.c
factor.c
uniq.c
monster.c
sort.c
.nfs0000000006c543ea0000e073
tree.c
tree
The tree works fine, but I still need to sort the files afterward. My plan is to put all of the file names into the global words array, then discern between folders and files, and print in the same format, but ordered alphabetically case-insensitive.
I check the array, but the hw2 folder and its files gets overridden completely. I don't understand why this is happening because it should be working fine.
Does anyone know a fix or maybe a better way to do this?
I believe your issue lies here:
word[coun] = ("%s", entry->d_name);
You have not allocated any memory space for word[coun] to point to.
I have no idea what ("%s", ...) is meant to accomplish, but gcc throws warnings about it. It actually does return entry->d_name but the "%s is unused.
You have word[coun] pointing to something that changes before you try to access it. Instead you need to use strcpy to copy entry->d_name into the memory space you allocate.
Instead of:
word[coun] = ("%s", entry->d_name);
coun++;
You want:
word[coun] = malloc(strlen(entry->d_name) + 1);
strcpy(word[coun++], entry->d_name);
Make sure to allocate an extra byte for the '\0' that terminates every C string.

Program to print directories using c not working, issues with recursive call

I need to create a program that basically acts similarly to the list utility on Linux. I've been trying to get this to work and I'm pretty close but now I've gotten stuck. Essentially it will print whatever files and sub-directories that are contained withing a directory(i.e. if i run ./project3, it lists whatevers in that directory). However, once I try to get the recursive call working it spits out something like:
sh: 1: /home/RageKage/Documents/Project3/dir1: Permission denied
That's where I'm stuck, I'm not exactly sure what to do from here. I'm getting the path of the directory to explore using realpath and that works fine, but the recursive call just isn't working and I'm not exactly sure what I'm doing wrong. Any help would be appreciated as I'm relatively new to this.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <limits.h>
int main (int argc, char *argv[])
{
DIR *dir;
struct dirent *sd;
const char *direct;
char buf[PATH_MAX + 1];
if (argc < 2)
{
direct = ".";
}else{
direct = argv[1];
//printf("Hey this is argv[1]: %s\n", argv[1]);
}
dir = opendir(direct);
if (dir == NULL)
{
printf("ERROR! NO DIRECTORY TO OPEN!\n");
exit(1);
}
while( (sd=readdir(dir)) != NULL )
{
if (!strcmp(sd->d_name, ".") || !strcmp(sd->d_name, ".."))
{
}else{
printf("\n>> %s\n", sd->d_name);
}
if (!strcmp(sd->d_name, "..") || !strcmp(sd->d_name, "."))
{
}else if (sd->d_type == 4){
printf("Attempting to Run!\n");
realpath(sd->d_name, buf);
printf("[%s]\n", buf);
system(("./project3 %s", buf));
printf("\n");
}
}
closedir(dir);
return 0;
}
system(("./project3 %s", buf));
Are you recursively calling the program itself again? That sounds a bit inefficient, and hard to do since you'd need to know where the executable file is. In general it could be just about anywhere (starting with /bin, /usr/bin etc.), and all you are likely to get in argv[0] is the filename part, not the whole path.
Also, as said in the comments, func((this, that)) is the same as func(that), not func(this, that), since the parenthesis make the comma act as the comma operator, not as an argument separator. And system() only takes one argument anyway, so you'd need to use sprintf() to build the command line. (Or perhaps use the exec() functions to actually give separate arguments without invoking a shell, but then you need to do the fork(), too.)
I'd suggest scrapping that idea, and putting the directory tree walking into a function of it's own, and calling that recursively:
void walkpath(void)
{
DIR *dir = opendir(".");
struct dirent *sd;
while((sd = readdir(dir)) != NULL) {
/* ... */
if (sd->d_type == DT_DIR) {
chdir(sd->d_name);
walkpath();
chdir("..");
}
}
}
int main(...)
{
/* ... */
chdir(directory);
walkpath();
}
I used chdir here to change the process's working directory along with the walk. If you need to track the full directory name, then you'll need to add that.
Also, now you have the test for . and .. twice. Use continue to end that iteration of the loop so you don't need to test the same thing again.
if (strcmp(sd->d_name, ".") == 0 || strcmp(sd->d_name, "..") == 0) {
continue;
}

writing ls from scratch recursively

I am working on a simple project to implement "ls -R" from scratch. Whenever I run what I have, my program just keeps searching the root directory over and over again. What am I doing wrong?
void lsR(char dirName[]) {
/*
The recursive function call.
*/
DIR *dir;
struct dirent *directory;
struct stat fileStat;
char type;
char **nameList[MAX_RECURSIVE_FILES];
struct passwd *user;
int count = 0;
int i = 0;
printf("\n");
printf("./%s :\n", dirName);
printf("\n");
if ((dir = opendir(dirName)) == NULL) {
perror("opendir error:");
return;
}
while ((directory = readdir(dir)) != NULL) {
if (stat(directory->d_name, &fileStat) < 0) {
perror("fstat error:");
return;
}
if (fileStat.st_uid == 1) {
continue;
}
user = getpwuid(fileStat.st_uid);
printf("%s ", directory->d_name);
fileType(&fileStat, &type);
if ((type == 'd') && (count < MAX_RECURSIVE_FILES)) {
nameList[count] = malloc(sizeof(char)*MAX_STRING_LENGTH);
strncpy(nameList[count++], directory->d_name, MAX_STRING_LENGTH);
}
}
closedir(dir);
printf("\n");
for (i=0; i<count; i++) {
printf("Calling lsR on: %s\n", nameList[i]);
lsR(nameList[i]);
}
}
When it executes, I get the following output:
"./. :
., .., ... all other files in my current working directory ....
./. :
., .., ... all other files in my current working directory...
"
Among the list of files in the current directory you've noticed . and .. The first one is a hardlink to the current directory and the second one to the parent directory. So when you recurse through your dir entries you will want to skip those two. Otherwise the first directory you will recurse into will be ., in other words the directory you've just gone through.
This is the reason of your program current behavior, but once you fix that you will run into the issue lurker mentioned in his answer.
Additional notes :
Are you sure about the char **nameList[MAX_RECURSIVE_FILES]; variable? Seems to me you want an array of char * not an array of char **.
Are you aware you can use the S_ISDIR macro on the st_mode field of your stat struct, in order to check that the current file is not a directory instead of your custom function?
You need to include the path relative to your program's current directory. Each nameList element will need to be dirName + "/" + directory->d_name.
If you started out calling lsR on the local directory, ./foo and foo has directory named bar under it, then to open bar you need to open ./foo/bar since your program is running from the directory represented by ..

Recursively find subdirectories and files

I want to retrieve all the files, directories and subdirectories contained within a given path, recursively. But I have a problem when my code reaches the second level (a directory within a directory): instead of opening the inner directory to search its contents, it throws an error. Here is what I have done:
void getFile(char *path)
{
DIR *dir;
struct dirent *ent;
if ((dir = opendir(path)) != NULL) {
/* print all the files and directories within directory */
while ((ent = readdir(dir)) != NULL) {
if((strcmp(ent->d_name,"..") != 0) && (strcmp(ent->d_name,".") != 0)){
printf ("%s", ent->d_name);
if(ent->d_type == DT_DIR){
printf("/\n");
getFile(ent->d_name);
}
else{
printf("\n");
}
} // end of if condition
} // end of while loop
closedir (dir);
}
Use the ftw(3) library function to recursively walk a file tree. It is quite standard.
You may also look into nftw and the MUSL libc source code for it. It is quite readable.
When you recursively call getFile you only call it with the only the name of the directory you just read. It's not the full path, which is what you need. You have to manage that yourself.
Something like this:
if(ent->d_type == DT_DIR)
{
if ((strlen(path) + strlen(ent->d_name) + 1) > PATH_MAX)
{
printf("Path to long\n");
return;
}
char fullpath[PATH_MAX + 1];
strcpy(fullpath, path);
strcat(fullpath, "/");
strcat(fullpath, ent->d_name); // corrected
getFile(fullpath);
}

How can I searches files in current dir and the files in directories that under current dir?

The function searches the files in current directory. If It accrosses a directory, It gets in and again searches for file except the current '.' and the previous '..' directory. But It doesnt work how I want.It does not get in the next directory.
int foo(char *currDir)
{
struct dirent *direntp;
DIR *dirp;
char currentDir[250];
if ((dirp = opendir(currDir)) == NULL)
{
perror ("Failed to open directory");
return 1;
}
//By Sabri Mev at GYTE
while ((direntp = readdir(dirp)) != NULL)
{
printf("%s\n", direntp->d_name);
if(direntp->d_type == DT_DIR)
{
if(strcmp(direntp->d_name,".") !=0 && strcmp(direntp->d_name,"..") != 0)
foo(direntp->d_name); //Recursive!
}
}
getcwd(currentDir,250);
printf("curr Dir : %s\n",currentDir );
while ((closedir(dirp) == -1) && (errno == EINTR)) ;
return 0;
}
Because your path is error.
try this
if(direntp->d_type == DT_DIR)
{
if(strcmp(direntp->d_name,".") !=0 && strcmp(direntp->d_name,"..") != 0)
{
sprintf(currentDir, "%s/%s", currDir, direntp->d_name);
foo(currentDir); //Recursive!
}
}
When you do the recursive call to foo() inside the loop, notice that what direntp->d_name contains is not the full path, but just the subdirectory name. You have to concatenate it with currDir and use the result to call foo().
For instance, if you're starting with foo("/home") and the first subdir is "root", you're calling recursively foo("root") when it should be foo("/home/root").
in direntp->d_name you access only the local directory name, it does not return the whole path
also getcwd function is deprecated. Use the ISO C++ conformant _getcwd instead (if you write in C++ off course).

Resources