tags:

views:

137

answers:

4

I am trying to simulate linux command ls using linux api from c. Looking at the code it does make sense, but when I run it I get "stat error: No such file or directory". I have checked that opendir is working ok. I think the problem is in stat, which is returning -1 even though I think it should return 0.

What am I missing?

Thanks for your help.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>

int main(int argc, char *argv[])
{
 DIR *dirp;
 struct dirent *direntp;
 struct stat stat_buf;
 char *str;

 if (argc != 2)
 {
  fprintf( stderr, "Usage: %s dir_name\n", argv[0]);
  exit(1);
 }

 if ((dirp = opendir( argv[1])) == NULL)
 {
  perror(argv[1]);
  exit(2);
 }

 while ((direntp = readdir( dirp)) != NULL)
 {
  if (stat(direntp->d_name, &stat_buf)==-1)
  {
   perror("stat ERROR");
   exit(3);
  }
  if (S_ISREG(stat_buf.st_mode)) str = "regular";
  else if (S_ISDIR(stat_buf.st_mode)) str = "directory";
  else str = "other";
  printf("%-25s - %s\n", direntp->d_name, str);
 }

 closedir(dirp);
 exit(0);
}
+2  A: 

Add

#include <unistd.h>
...
chdir(argv[1]);

or call stat with the full pathname like this

...
char fullpath[MAXPATHLEN];
snprintf(fullpath, sizeof(fullpath), "%s/%s", argv[1], direntp->d_name);
if (stat(fullpath, &stat_buf) == -1)
...
Tuomas Pelkonen
It's possible the `strn` family functions would be better to use. Less error checking. OTOH, it also makes it difficult to correctly report on an (admittedly rare) error condition.
Omnifarious
using chdir seems much more simpler. Is it a bad practice to use it?
nunos
btw, I the snprintf statment is causing a segmentation fault...
nunos
@nunos, I don't like using it. It's sort of a global variable, and I avoid using those too. In the case of this sort of application, it's likely a fine thing to do. But for others, there might be parts of the code that become very confused when the working directory keeps changing. Also, there are often other reasons to know the full path, so using chdir to avoid having to construct it is a false savings.
Omnifarious
This methods are subject to a race condition - if the directory is renamed during the execution of your program, you may look in the wrong place. You can make the `chdir()` method work as long as you also do `opendir(".")` after the `chdir(argv[1])`, rather than `opendir(argv[1])`.
caf
+2  A: 

It's because you aren't stating the actual file. It's in a different directory. If you want the real filename, combine argv[1] and direntp->d_name with a '/' between them.

Also, hungarian naming is icky, even the minor bit like 'p' on the end. If you have so many variables you need to keep track of their types in their names you're doing something wrong.

Here is a revised version of your program:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <sys/param.h>

int main(int argc, char *argv[])
{
 DIR *dirp;
 struct dirent *direntp;
 struct stat stat_buf;
 char *str;
 char fullpath[MAXPATHLEN + 1];
 size_t dirnamelen;

 if (argc != 2)
 {
  fprintf( stderr, "Usage: %s dir_name\n", argv[0]);
  exit(1);
 }
 strncpy(fullpath, argv[1], MAXPATHLEN - 1); /* account for trailing '/' */
 fullpath[MAXPATHLEN - 1] = '\0';
 dirnamelen = strlen(fullpath);
 if (strlen(argv[1]) > dirnamelen) {
  fprintf( stderr, "Directory name is too long: %s", argv[1] );
  exit(2);
 }

 fullpath[dirnamelen++] = '/';
 fullpath[dirnamelen] = '\0';

 if ((dirp = opendir( argv[1])) == NULL)
 {
  perror(argv[1]);
  exit(2);
 }

 while ((direntp = readdir( dirp)) != NULL)
 {
  fullpath[dirnamelen] = '\0';
  if ((dirnamelen + strlen(direntp->d_name)) > MAXPATHLEN) {
   fprintf(stderr, "File %s + directory %s is too long.", direntp->d_name, fullpath);
   continue;
  } else {
   /* strncpy is mild overkill because the if statement has verified that
      there's enough space.  */
   strncpy(fullpath + dirnamelen, direntp->d_name, MAXPATHLEN - dirnamelen);
   fullpath[MAXPATHLEN] = '\0';
  }
  if (stat(fullpath, &stat_buf)==-1)
  {
   perror("stat ERROR");
   exit(3);
  }
  if (S_ISREG(stat_buf.st_mode)) str = "regular";
  else if (S_ISDIR(stat_buf.st_mode)) str = "directory";
  else str = "other";
  printf("%-25s - %s\n", direntp->d_name, str);
 }

 closedir(dirp);
 exit(0);
}

Note that I use MAXPATHLEN (from <limits.h>) and carefully check to make sure there aren't any buffer overflows. You should do the same in your code.

Edit: Changed code to use strn family functions for added safety.

Omnifarious
Why not use snprintf instead of strcpy?
Tuomas Pelkonen
@Thomas, for two reasons. First, I can report on errors more accurately, secondly my method is a bit more efficient.
Omnifarious
I understand your point, but I don't think that minor gain in efficiency is more important that code readability here. Besides doing string handling manually always adds a risk of introducing a bug; more lines of code -> more bugs...
Tuomas Pelkonen
@Thomas, also, I've been mostly a C++ programmer for many years and haven't gotten into the habit of using the `strn` functions because there's no need for such things with the C++ `::std::string` type.
Omnifarious
OK, BTW my name is Tuomas :)
Tuomas Pelkonen
@Tuomas, *laugh* That mistake is the reason I'm a horribly copy editor. Sorry about that.
Omnifarious
This method is subject to a race condition - if the directory is renamed while you're in the middle of reading it, you may end up `stat()` ing files in a different directory than the one you are reading.
caf
@caf, this is true. Using chdir does get rid of that problem.
Omnifarious
Personally I would use `fstatat()`.
caf
@caf, That's the first I've ever seen that call. Does it exist on anything besides Linux and possibly Solaris? Of course, anything but those two and the BSD derivatives matters less and less over time, but still. I mean, it's a neat call. It solves the problem better than chdir + stat or stat with a full path name. But it would be nice if it were really portable.
Omnifarious
It's in the latest version of POSIX (IEEE Std 1003.1-2008): http://www.opengroup.org/onlinepubs/9699919799/functions/stat.html (though I probably wouldn't have mentioned it if the question didn't have a Linux tag).
caf
A: 

If you are using on unix, then you may use the system command.

system("ls -ltr | grep -d");
Sachin Chourasiya
A: 

Others have suggested building a full path for stat(), or using chdir(). Both those will work (although they are subject to a race condition, if the directory is renamed while you are in the middle of reading it).

An alternative, which is not subject to the race condition, and is therefore arguably more "correct", is to use fstatat(). Just replace your existing stat() call with:

fstatat(dirfd(dirp), direntp->d_name, &stat_buf, 0)

(The chdir() method can be made race-condition-free too: either by using fchdir(dirfd(dirp)) instead of chdir(), or by changing directory to argv[1] and then opening "." with opendir(). The pathname construction method can't be made race-condition-free).

caf