Why is the lstat() function failing? - c

I'm having problem understanding how lstat() in c actually works. I have already read the following documentation but still have some problems understating how lstat() really works and why doesn't it work the way i
I use it.
https://man7.org/linux/man-pages/man0/sys_stat.h.0p.html
https://www.ibm.com/docs/en/zos/2.1.0?topic=functions-lstat-get-status-file-symbolic-link
https://man7.org/linux/man-pages/man2/lstat.2.html
https://man7.org/linux/man-pages/man3/lstat.3p.html
https://man7.org/linux/man-pages/man3/fstatat.3p.html
Basically what I want to do is write a program similar to the command find, that list all files/directories and subdirectories. I want to use lstat() to check if a file is a symbolic link and if so skip it. The st_mode gives information about the file type.
#includes ...
void error_handling(char* msg){
perror(msg);
exit(1);
}
void print_cwd(){} //prints the current working directory
void print_path(char* path){} //prints the current path
void handle_path(char * path) {
int success = chdir(path);
if (success != 0) {
if (errno == ENOTDIR || errno == ENOENT) {
return; // OK, just ignore non-directory entries for recursion
} else {
error_handling("chdir");
}
}
DIR *current_dir = opendir(".");
if (current_dir == NULL) {
error_handling("opendir");
}
struct dirent *current_dirent;
while (1) {
errno = 0;
current_dirent = readdir(current_dir);
if (current_dirent == NULL) {
if (errno != 0) { // readdir doesn't change errno on success, hence, it should be zero
error_handling("readdir");
}
break;
}
char *name = current_dirent->d_name;
if (strcmp(name, "..") == 0 || strcmp(name, ".") == 0) {
continue;
}
print_path(current_dirent->d_name);
handle_path(current_dirent->d_name);
}
closedir(current_dir);
/* it doesn't matter where i call lstat() it always falis, be it before opening the directory or anywhere else. As soon as this is calledit fails
struct stat buf;
int s = lstat(path, &buf);
if(s != 0)
error_handling("lstat");
*/
chdir("..");
}
It fails with the following error: lstat: No such file or directory
As described here https://man7.org/linux/man-pages/man2/lstat.2.html in the return value section, these function return 0 on success and -1 on error.
According to this post I found on here Symlinks and File Redirection you should call lstat() after you call open(). Still fails with the same error

Related

Open function : how to protect against directory opening?

I am using the open function like this in my file to get some coordinates from a file :
t_coo *get_buffer(char **av, t_coo **head)
{
int ret;
int fd;
char *line;
int y;
t_coo *cur;
cur = NULL;
*head = NULL;
y = 0;
ret = 0;
fd = open(av[1], O_RDONLY);
while ((ret = get_next_line(fd, &line) > 0))
{
head = get_map(line, head, y);
y++;
}
close(fd);
cur = *head;
return (cur);
}
It is working perfectly but the problem is when I try to make it open a directory, my program segfault. I want to protect it against directory opening so that I dont segault anymore. I tried to look at the flags on the internet and tried many of them but I could not find this one. Can anybody tell me which one it is? Thank you.
You need to use the lstat function to tell you whether the given file name represents a regular file or a directory.
struct stat statbuf;
int rval;
rval = lstat(argv[1], &statbuf);
if (rval == -1) {
perror("error getting file status");
exit(1);
}
if (S_ISREG(statbuf.st_mode)) {
printf("%s is a regular file\n", argv[1]);
} else if (S_ISDIR(statbuf.st_mode)) {
printf("%s is a directory\n", argv[1]);
} else {
printf("%s is something else\n", argv[1]);
}
I would suggest to open the file (which could be a directory) so get a file descriptor, then use fstat(2) on that file descriptor (and check the result of fstat by using statresult.st_mode & S_IFMT == S_IFDIR ...)
This would avoid an (improbable) race condition with the lstat (or stat) then open approach (suggested in Dbush's answer): some other process might (with very bad luck) remove or rename the file between these two system calls. You might also opendir or else open (but that also suffers from a similar race condition).
PS. The race conditions I am suggesting here are so improbable that we can normally ignore them... But they might be a security flaw (than an attacker could use)

How to iterate in a directory and stop at a particular level using C?

I am trying to print the names of all the processes currently in the system, in the terminal. For that I have to get into all the directories named after the process ID in the "proc" directory. So I am looping till before the "acpi" directory and trying to read the status file in each process directory. But I don't exactly understand how to read a file in a directory which is inside a directory. On running my code below :
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, const char *argv[])
{
DIR* FD;
struct dirent* in_file;
FILE *process_file;
char ch, pname[1024];
int i=0;
FD = opendir ("/proc");
while ((in_file = readdir(FD)))
{
if (strcmp (in_file->d_name, "acpi") == 0)
break;
else
{
opendir(in_file->d_name);
process_file = fopen("status", "r");
while( ( ch = fgetc(process_file) ) != '\n' )
{
pname[i] = ch;
i++;
}
printf(" %s \n",pname);
fclose(process_file);
closedir(in_file->d_name);
}
}
closedir(FD);
return 0;
}
I get the error :
myps.c: In function ‘main’:
myps.c:38:13: warning: passing argument 1 of ‘closedir’ from incompatible pointer type
closedir(in_file->d_name);
^
In file included from myps.c:5:0:
/usr/include/dirent.h:149:12: note: expected ‘struct DIR *’ but argument is of type ‘char *’
extern int closedir (DIR *__dirp) __nonnull ((1));
^
This is a good example of when to use a recursive function.
The function would take a directory name, open that directory, and loop through the results. For each result that is not . or .., call the stat function to get status on each entry. Then use the S_ISREG and S_ISDIR macros against the file mode to see if it's a regular file or a directory. If it's a directory, build a new string from the parent directory and the one you just found and pass that to the recursive function call.
So the function would look something like this:
void processDirectory(char dirname[])
{
struct stat statbuf;
DIR *dir;
struct dirent *de;
char *subdirname;
int rval, ;
if ((dir = opendir(dirname)) == NULL) {
perror("Failed to open directory %s", dirname);
exit(1);
}
while ((errno = 0, de = readdir(dir)) != NULL) {
rval = stat(de->d_name, &statbuf);
if (rval == -1) {
perror("stat failed");
exit(1);
}
if (S_ISREG(statbuf.st_mode)) {
// process as a regular file
} else if (S_ISDIR(statbuf.st_mode)) {
if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
subdirname = malloc(strlen(dirname) + strlen(de->d_name) + 2);
if (subdirname == NULL) {
perror("malloc failed");
exit(1);
}
strcpy(subdirname, dirname);
strcat(subdirname, "/");
strcat(subdirname, de->d_name);
processDirectory(subdirname);
free(subdirname);
}
}
}
if (errno && (errno != ENOENT)) {
perror("Failed to read directory %s", dirname);
exit(1);
}
closedir(dir);
}
To solve the error, save the directory pointer you open. Then use that to close the directory.
DIR *process_dir = opendir(in_file->d_name);
closedir(process_dir);

Read only files with certain extension from directories and sub-directories in C.

I have this code I modified but the problem is, I can't get it to jet me only files with certain extensions. I tried so many things and every time I don't get the desired output. Any help would be appreciated. This part of a very big project and I got stuck at this specific function.
static void list_dir (const char * dir_name)
{
DIR * d;
/* Open the directory specified by "dir_name". */
d = opendir (dir_name);
/* Check it was opened. */
if (! d) {
fprintf (stderr, "Cannot open directory '%s': %s\n",
dir_name, strerror (errno));
exit (EXIT_FAILURE);
}
while (1) {
struct dirent * entry;
char * d_name;
/* "Readdir" gets subsequent entries from "d". */
entry = readdir (d);
if (! entry) {
/* There are no more entries in this directory, so break
out of the while loop. */
break;
}
d_name = entry->d_name;
if(!strstr (d_name, ".jpg") == 0 && !strstr (d_name, ".JPG") == 0){
continue;
}
/* Print the name of the file and directory. */
printf ("%s\n", d_name);
/* If you don't want to print the directories, use the
following line: */
//if (! (entry->d_type & DT_DIR)) {
//printf ("%s/%s\n", dir_name, d_name);
//}
if (entry->d_type & DT_DIR) {
/* Check that the directory is not "d" or d's parent. */
if (strcmp (d_name, "..") != 0 && strcmp (d_name, ".") != 0) {
int path_length;
char path[PATH_MAX];
path_length = snprintf (path, PATH_MAX,
"%s/%s", dir_name, d_name);
//printf ("%s\n", path);
if (path_length >= PATH_MAX) {
fprintf (stderr, "Path length has got too long.\n");
exit (EXIT_FAILURE);
}
/* Recursively call "list_dir" with the new path. */
if(!strstr (d_name, ".jpg") == 0 && !strstr(d_name, ".JPG") == 0){
continue;
}
list_dir (path);
}
}
}
/* After going through all the entries, close the directory. */
if (closedir (d)) {
fprintf (stderr, "Could not close '%s': %s\n",
dir_name, strerror (errno));
exit (EXIT_FAILURE);
}
}
You should know that case sensitivity is not solved by just using ".jpg" and ".JPG" since there could be more like ".jPg" and so on.
You can try this
int endsWith(const char *const filename, const char *const extension)
{
size_t fileNameLength;
size_t extensionLength;
if ((filename == NULL) || (extension == NULL))
return 0;
fileNameLength = strlen(filename);
extensionLength = strlen(extension);
return (strcasecmp(extension, filename + filenameLength - extensionLength) == 0);
}
then you can do
if (endsWidth(d_name, ".jpg") != 0) /* it has jpeg extension */
Note: strcasecmp() is not standard, you can find the correct alternative for your c library googling.
And also, there are libraries that examine the file type by using the so called magic bytes, they are normally the first 4 or so bytes in the file content, there is even one called libmagic, although I have never worked with it, so I can't give you advice on how to use it.
I would replace the line
if(!strstr (d_name, ".jpg") == 0 && !strstr (d_name, ".JPG") == 0){
with more readable code:
if( fileIsNotJPGFile(d_name) ) {
And implement the function as:
int fileIsJPGFile(char const* name)
{
return (strstr(name, ".jpg") || strstr(name, ".JPG"));
}
int fileIsNotJPGFile(char const* name)
{
return !(fileIsJPGFile(name));
}

Seemingly random opendir() failure C

So I've written a short C program that explores the files on my computer to look for a certain file. I wrote a simple function that takes a directory, opens it an looks around:
int exploreDIR (char stringDIR[], char search[])
{
DIR* dir;
struct dirent* ent;
if ((dir = opendir(stringDIR)) == NULL)
{
printf("Error: could not open directory %s\n", stringDIR);
return 0;
}
while ((ent = readdir(dir)) != NULL)
{
if(strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
if (strlen(stringDIR) + 1 + strlen(ent->d_name) > 1024)
{
perror("\nError: File path is too long!\n");
continue;
}
char filePath[1024];
strcpy(filePath, stringDIR);
strcat(filePath, "/");
strcat(filePath, ent->d_name);
if (strcmp(ent->d_name, search) == 0)
{
printf(" Found it! It's at: %s\n", filePath);
return 1;
}
struct stat st;
if (lstat(filePath, &st) < 0)
{
perror("Error: lstat() failure");
continue;
}
if (st.st_mode & S_IFDIR)
{
DIR* tempdir;
if ((tempdir = opendir (filePath)))
{
exploreDIR(filePath, search);
}
}
}
closedir(dir);
return 0;
}
However, I keep getting the output:
Error: could not open directory /Users/Dan/Desktop/Box/Videos
Error: could not open directory /Users/Dan/Desktop/compilerHome
The problem is, I have no idea what it is about these files that could cause opendir() to fail. I don't have them open in any program. They're just simple folders I created on my desktop. Does anyone have any idea what the problem could be?
You are calling opendir() twice for each closedir(). Maybe you are running out of resources.

Directory traversal with chdir() instead of with absolute paths

In Chapter 4 of the book "Advanced Programming in the Unix Environment," which covers files and directories, there is a code sample which aims to be like the ftw command and traverse a file hierarchy. It uses a pointer to an absolute file path, as well as a recursive function with a callback to traverse the directory, using calls to opendir() and readdir() in the process.
There is an exercise in which readers are asked to use chdir() and file names instead of using the absolute paths to accomplish the same task and to compare the times of the two programs. I wrote a program using chdir() and did not notice a difference in the time. Is this expected? I would have thought that the additional call to chdir() would add some overhead. Is it maybe a relatively trivial call? Any insight would be appreciated.
Here's the recursive function using absolute paths:
static int /* we return whatever func() returns */
dopath(Myfunc* func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret;
char *ptr;
if (lstat(fullpath, &statbuf) < 0) /* stat error */
return(func(fullpath, &statbuf, FTW_NS));
if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
return(func(fullpath, &statbuf, FTW_F));
/*
* It's a directory. First call func() for the directory,
* then process each filename in the directory.
*/
if ((ret = func(fullpath, &statbuf, FTW_D)) != 0)
return(ret);
ptr = fullpath + strlen(fullpath); /* point to end of fullpath */
*ptr++ = '/';
*ptr = 0;
if ((dp = opendir(fullpath)) == NULL) /* can't read directory */
return(func(fullpath, &statbuf, FTW_DNR));
while ((dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue; /* ignore dot and dot-dot */
strcpy(ptr, dirp->d_name); /* append name after slash */
if ((ret = dopath(func)) != 0) /* recursive */
break; /* time to leave */
}
ptr[-1] = 0; /* erase everything from slash onwards */
if (closedir(dp) < 0)
err_ret("can't close directory %s", fullpath);
return(ret);
}
And here's the function with my changes:
static int /* we return whatever func() returns */
dopath(Myfunc* func, char* path)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret;
if (lstat(path, &statbuf) < 0) /* stat error */
return(func(path, &statbuf, FTW_NS));
if (S_ISDIR(statbuf.st_mode) == 0) /* not a directory */
return(func(path, &statbuf, FTW_F));
/*
* It's a directory. First call func() for the directory,
* then process each filename in the directory.
*/
if ((ret = func(path, &statbuf, FTW_D)) != 0)
return(ret);
if ( chdir(path) < 0 )
return(func(path, &statbuf, FTW_DNR));
if ((dp = opendir(".")) == NULL) /* can't read directory */
return(func(path, &statbuf, FTW_DNR));
while ((dirp = readdir(dp)) != NULL) {
if (strcmp(dirp->d_name, ".") == 0 ||
strcmp(dirp->d_name, "..") == 0)
continue; /* ignore dot and dot-dot */
if ((ret = dopath(func, dirp->d_name)) != 0) /* recursive */
break; /* time to leave */
}
if ( chdir("..") < 0 )
err_ret("can't go up directory");
if (closedir(dp) < 0)
err_ret("can't close directory %s", fullpath);
return(ret);
}
I don't think you should expect a substantial time performance difference between the absolute path version and the chdir() version. Rather, the pros and cons of both versions are as follows:
The full pathname version might not be able to traverse very deep directory structures because the length of the full pathname eventually exceeds PATH_MAX. The chdir() version does not have this problem.
The chdir() version manipulates the pwd, which is generally considered bad practice if you can avoid it: it's not thread-safe, and the end user might expect it to be left alone. For example filenames given on the command line and used by a different part of the program might be relative to what the user thought the pwd was, which breaks when you change it.
The chdir() version might go out of control when backing up to a higher directory (chdir("..")) if special care is not taken and the directory structure changes while it is being traversed. Then again the full pathname version might break in a different way under these circumstances...
The openat() family of functions available on modern POSIX systems offer the best of both worlds. If these functions are available, openat() together with fdopendir(), fstatat(), etc... make for a really nice implementation of directory walking.

Resources