EDIT: After some help from the forum it was made clear that this issue was not in the use of S_ISREG() but in my use of lstat(). Sorry for the misleading question.
I'm looking in a directory and trying to tell the difference between regular files and sub-directories.
I've looked through other people's issues with this problem, and while some were similar, none were answered clearly enough to fix my code.
int find(char *argv)
{
DIR *pointerToDir;
struct dirent *pointerToDirent;
struct stat status;
int mode;
pointerToDir = opendir(argv);
if(pointerToDir == NULL)
{
printf("Can't open that directory (or it doesn't exist)\n");
closedir(pointerToDir);
return 0;
}
else
{
while((pointerToDirent = readdir(pointerToDir)) != NULL)
{
lstat(pointerToDirent->d_name, &status);
mode = S_ISREG(status.st_mode);
if(mode != 0)
printf("%s must be a file\n", pointerToDirent->d_name);
else
printf("%s must be a dir\n", pointerToDirent->d_name);
}
closedir(pointerToDir);
return 0;
}
}
I pass the program a test directory that has 2 sub-directories and 2 regular files. The layout would be something like this:
dir1
sub1
sub2
dir1.txt
test.c
Now when I run my program and pass "dir1" as the argument, I would expect it to return the following:
. must be a dir
.. must be a dir
sub1 must be a dir
dir1.txt must be a file
sub2 must be a dir
test.c must be a file
But instead, it returns that they are all "dirs". What am I missing?
Related
I write a function to count the number of files in, and below a directory (including files in the sub directory).
However, When I test the code on a directory with sub directory, it always report error said: "fail to open dir: No such file or directory".
Is there any thing I could do to make it work?
int countfiles(char *root, bool a_flag)//a_flag decide if it including hidden file
{
DIR *dir;
struct dirent * ptr;
int total = 0;
char path[MAXPATHLEN];
dir = opendir(root); //open root dirctory
if(dir == NULL)
{
perror("fail to open dir");
exit(1);
}
errno = 0;
while((ptr = readdir(dir)) != NULL)
{
//read every entry in dir
//skip ".." and "."
if(strcmp(ptr->d_name,".") == 0 || strcmp(ptr->d_name,"..") == 0)
{
continue;
}
//If it is a directory, recurse
if(ptr->d_type == DT_DIR)
{
sprintf(path,"%s%s/",root,ptr->d_name);
//printf("%s/n",path);
total += countfiles(path, a_flag);
}
if(ptr->d_type == DT_REG)
{
if(a_flag == 1){
total++;
}
else if (a_flag == 0){
if (isHidden(ptr->d_name) == 0){
total++;
}
}
}
}
if(errno != 0)
{
printf("fail to read dir");
exit(1);
}
closedir(dir);
return total;
}
Is there anything I could make it to work?
Sure, lots. Personally, I'd start by using the correct interface for this stuff, which in Linux and POSIXy systems would be nftw(). This would lead to a program that was not only shorter and more effective, but would not as easily get confused if someone renames a directory or file in the tree being scanned at the same time.
Programmers almost never implement opendir()/readdir()/closedir() as robustly and as efficiently as nftw(), scandir(), glob(), or the fts family of functions do. Why teachers still insist on using the archaic *dir() functions in this day and age, puzzles me to no end.
If you have to use the *dir functions because your teacher does not know POSIX and wants you to use interfaces you should not use in real life, then look at how you construct the path to the new directory: the sprintf() line. Perhaps even print it (path) out, and you'll probably find the fix on your own.
Even then, sprintf() is not something that is allowed in real life programs (because it will cause a silent buffer overrun when the arguments are longer than expected; and that CAN happen in Linux, because there actually isn't a fixed limit on the length of a path). You should use at minimum snprintf() and check its return value for overruns, or in Linux, asprintf() which allocates the resulting string dynamically.
I'm not sure how I'm supposed to read the files in a directory. When I call my print function it segfaults. Can someone help out? I think it has something to do with the path name but I don't know how to go about fixing it.
I know it's not something wrong with my print function because it prints regular files just fine.
int main(int argc, char **argv){
DIR *d;
struct dirent *dir;
d = opendir(argv[1]);
FILE *fp;
if(d){
while((dir = readdir(d)) != NULL){
if(strcmp(dir->d_name, ".") == 0)
continue;
if(strcmp(dir->d_name, ".." == 0)
continue;
fp = fopen(dir->d_name, "r");
//print function on fp
}
}
return 0;
}
You must append dir->d_name to argv[1] (with a '/' of course) to create the full path to the file. Otherwise, you can only open files in the current directory. Alternatively, you can use chdir to change the current directory to argv[1].
To avoid the segfault, you should check the return value from fopen. In fact, you should always check the return value from fopen, because it fails often, especially during development.
I'm writing something that recursively finds .c and .h files and deletes all comments (just as a learning excercise). For every .c/.h file found, this program creates an additional file which is equal to the original file without the comments. So for example, "helloworld.c" would result in an additional file "__helloworld.c"
The problem I am encountering is this:
I have a loop which iterates over all entries in a directory, and keeps going until it stops finding files with .c or .h extensions. However, the loop never actually ends, since each time a file is found, another is created. So I have this recursive situation where "__helloworld.c" becomes "____helloworld.c" which becomes "______helloworld.c", etc.
(in case anyone suggests, yes it is necessary for the new files to have a .c extension.)
One possible solution may be to keep track of the inode numbers so we know only to iterate over original files, however this requires several iterations of the loop: once to count directory entries,
(and use this number to initialise array for inode nums), twice to store inode numbers, and finally a third time to do the work.
Can anybody share any ideas that could achieve this in a single pass of the loop?
code is split across two files so I have posted the main recursive routine:
consume_comments():
takes single file as argument, creates new file with comments omitted
My main routine pretty much just does some argument handling- the routine posted below is where the real problems are.
/*
opens a directory stream of the dir pointed to by 'filename',
looks for .c .h files, consumes comments. If 'rc' == 1, find()
calls itself when it encounters a sub-directory.
*/
int find (const char * dirname)
{
int count = 3;
DIR * dh;
struct dirent * dent;
struct stat buf;
const char * fnext;
int filecount = 0;
chdir(dirname);
if ((dh = opendir(".")) == NULL)
{
printf("Error opening directory \"%s\"\n", dirname);
exit(-1);
}
while ((dent = readdir(dh)) != NULL)
{
if (count) count--;
if (!count)
{
if (lstat(dent->d_name, &buf) == -1)
{
printf("Error opening file \"%s\" for lstat()\n", dent->d_name);
exit(EXIT_FAILURE);
}
if (S_ISDIR(buf.st_mode) && rc)
{
find(dent->d_name);
chdir("..");
//when this find() completes, it will be one level down:
//so we must come back up again.
}
if (S_ISREG(buf.st_mode))
{
fnext = fnextension(dent->d_name);
if (*fnext == 'c' || *fnext == 'h')
{
consume_comments(dent->d_name);
printf("Comments consumed:%20s\n", dent->d_name);
}
}
}
}
}
You can use 1 of the 3 solutions
As suggested in comment by #Theolodis, ignore files starting with
__.
Split your algorithm into 2 parts. In first part prepare a list of
all the .c and .h files(recursive). In second step, go through the list and
generated stripped versions of files(non-recursive).
Prepare the stripped .c and .h files in some temp directory
(/tmp in linux or %TEMP% in windows) and move it to folder once
all the .c and .h files of the folders have been processed. Now
scan all the sub-folders.
I do see multiple solutions to your problem. But in any case you might need to check if the file you are going to create does already exist or not! Otherwise you could run into cases where you do override existing files!
(Example: file.c, __file.c in your directory, you check the file __file.c and generate the file ____file.c, then you check the file file.c and override the file __file.c)
Ignore files that do begin with your chosen prefix.
advantages: easy to implement
downsides: you might miss some files starting with your prefix
while going through all the directory you make a set of unique filenames you have already created. Before converting any file you check if this file has been created by yourself.
advantages: you don't miss files that begin with your prefix
disadvantages: if you do have a very long list of files the memory usage might explode.
edit: the second and third solution of Mohit Jain look pretty good too!
New implementation, using a routine chk_prefix() to match the prefix of filenames.
char * prefix = "__nmc_";
int chk_prefix (char * name)
{
int nsize = strlen(name);
int fsize = strlen(prefix);
int i;
if (nsize < fsize) return 1;
for (i = 0; i < fsize; i++)
{
if (name[i] != prefix[i]) return 1;
}
return 0;
}
int find (const char * dirname)
{
int count = 3;
DIR * dh;
struct dirent * dent;
struct stat buf;
const char * fnext;
int filecount = 0;
chdir(dirname);
if ((dh = opendir(".")) == NULL)
{
printf("Error opening directory \"%s\"\n", dirname);
exit(-1);
}
while ((dent = readdir(dh)) != NULL)
{
if (count) count--;
if (!count)
{
if (lstat(dent->d_name, &buf) == -1)
{
printf("Error opening file \"%s\" for lstat()\n", dent->d_name);
exit(EXIT_FAILURE);
}
if (S_ISDIR(buf.st_mode) && rc)
{
find(dent->d_name);
chdir("..");
//when this find() completes, it will be one level down:
//so we must come back up again.
}
if (S_ISREG(buf.st_mode))
{
fnext = fnextension(dent->d_name);
if (*fnext == 'c' || *fnext == 'h' && chk_prefix(dent->d_name))
{
consume_comments(dent->d_name);
printf("Comments consumed:%20s\n", dent->d_name);
}
}
}
}
}
I have a problem, in that I need to get a list of the files in a Directory. Using this previous StackOverflow question as a base, I've currently got this code:
void get_files(int maxfiles) {
int count = 0;
DIR *dir;
struct dirent *ent;
dir = opendir(DIRECTORY);
if (dir != NULL) {
/* get all the files and directories within directory */
while ((ent = readdir(dir)) != NULL) {
if (count++ > maxfiles) break;
printf("%s\n", ent->d_name);
}
closedir(dir);
} else {
/* could not open directory */
printf("ERROR: Could not open directory");
exit(EXIT_FAILURE);
}
}
Now it works almost exactly how I want it too, but the problem is that its also listing directories in with he files, and I only want file entries. Is there a easy modification I can make to do this?
You can filter directories using code similar to
this one
POSIX defines fstat which can be used for the purpose of checking whether a file is a directory. It also has a macro to simplify the check.
http://linux.die.net/man/2/fstat
Note that for Windows you may have to use windows API here.
If your struct dirent contains the nonstandard-but-widely-available d_type member, you can use this to filter out directories. Worth having an option to use it and only falling back to stat on systems that don't, since using d_type rather than stat will possibly make your directory listing tens or hundreds of times faster.
I'm having some trouble with the 'if(S_IFDIR(stbuf.st_mode))' line. Is this the correct way to test for a directory to recurse into? The function at the moment seems to do it right for 1 or 2 loops and then fails and segmentation faults.
I've tried the following and probably more as the condition.
S_ISDIR(st_mode)
((st_mode & ST_IFMT) == S_IFDIR)
S_IFDIR(stbuf.st_mode)
I've included the whole function because I'm concerned the problem might be elsewhere.
void getFolderContents(char *source, int temp){
struct stat stbuf;
int isDir;
dirPnt = opendir(source);
if(dirPnt != NULL){
while(entry = readdir(dirPnt)){
char *c = entry->d_name;
if(strcmp(entry->d_name, cwd) == 0 || strcmp(entry->d_name, parent) == 0){
}
else{
stat(entry->d_name, &stbuf);
printf("%i %i ", S_IFMT, stbuf.st_mode);
if(S_IFDIR(stbuf.st_mode)){ //Test DIR or file
printf("DIR: %s\n", entry->d_name);
getFolderContents(entry->d_name, 0);
}
printf("FILE: %s\n", entry->d_name);
}
}
closedir(dirPnt);
}
Yes, that's correct. But since you never change into the directory, you will not find it.
Consider the following directory hierarchy:
a
|
+- b
| |
| +- c
...
Your code will scan its current directory, and find "a". It will determine that it is a directory, and call itself recursively, and open "a" for reading. This works. That scan will find a directory called "b", but trying to open it using the entry name only will fail, since the path is now "a/b".
I recommend changing into the directory (with chdir()) before opening it. That means you can just opendir("."). Store the old path, and chdir() out again when recursing that level is done (not before doing a recursive call to go deeper).
Where is entry defined ? is it a local variable ?
I can't see why it would segfault, but may be you should make it a local variable.
One example where it will bite you is here :
if(S_IFDIR(stbuf.st_mode)){ //Test DIR or file
printf("DIR: %s\n", entry->d_name);
getFolderContents(entry->d_name, 0);
}
printf("FILE: %s\n", entry->d_name);
The printf is gonna print the wrong name, so you should probably add an else here.
The same is true with dirpnt. When you go out of getFolderContents inside the while loop,
you end up calling readdir on a closed dirpoint, which should get you out of the loop.
But as stated by bahbar :
You can't recurse and store temporary variable in global variable