C - Ignore subdirectories names and only print files names - c

With this code I'm able to print recursively all files and subdirectories from a given path.
What I want is to ignore (not print) all the subdirectorynames, and only print the file names.
This is the code:
#include <stdio.h>
#include <dirent.h>
#include <string.h>
void listFilesRecursively(char *basePath)
{
char path[1000];
struct dirent *dp;
DIR *dir = opendir(basePath);
if (!dir)
return;
while ((dp = readdir(dir)) != NULL)
{
if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
{
strcpy(path, basePath);
strcat(path, "/");
strcat(path, dp->d_name);
listFilesRecursively(path);
printf("%s\n", path);
}
}
closedir(dir);
}
int main()
{
char path[100];
printf("Enter path to list files: ");
scanf("%s", path);
listFilesRecursively(path);
return 0;
}

There are macros that tells you what type of file it is:
S_ISREG(): regular file
S_ISDIR(): directory file
S_ISCHR(): character special file
S_ISBLK(): block special file
S_ISFIFO(): pipe or FIFO
-S_ISLNK(): symbolic
S_ISSOCK(): link socket
First of all you can use one of the following functions to get informaration:
#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf );
int fstat(int fd, struct stat *buf);
int lstat(const char *restrict pathname, struct stat *restrict buf );
int fstatat(int fd, const char *restrict pathname, struct stat *restrict buf, int flag);
From the Advanced Programming in Unix Environment book:
Given a pathname, the stat function returns a structure of information
about the named file. The fstat function obtains information about the
file that is already open on the descriptor fd. The lstat function is
similar to stat, but when the named file is a symbolic link, lstat
returns information about the symbolic link, not the file referenced
by the symbolic link.
You can try something like the following:
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret, n;
/* fullpath contains full pathname for every file */
if (lstat(fullpath, &statbuf) < 0)
{
printf("error\n");
//return if you want
}
if (S_ISDIR(statbuf.st_mode) == 0)
{
/* not a directory */
}
else
{
//a directory
}
Historically, early versions of the UNIX System didn’t provide the S_ISxxx macros. Instead, we had to logically AND the st_mode value with the mask S_IFMT and then compare the result with the constants whose names are S_IFxxx. Most systems define this mask and the related constants in the file .
For example:
struct stat *statptr;
if (lstat(fullpath, statptr) < 0)
{
printf("error\n");
//return if you want
}
switch (statptr->st_mode & S_IFMT) {
case S_IFREG: ...
case S_IFBLK: ...
case S_IFCHR: ...
case S_IFIFO: ...
case S_IFLNK: ...
case S_IFSOCK: ...
case S_IFDIR: ...
}

Related

lstat and ls permissions output is somehow differs

I'm trying to rewrite ls function with some of its flags, currently I'm implementing [-l] flag but output about permissions from original ls and lstat are different
Here is my code
void mx_strmode(mode_t mode, char * buf) {
const char chars[] = "rwxrwxrwx";
for (size_t i = 0; i < 9; i++) {
buf[i] = (mode & (1 << (8-i))) ? chars[i] : '-';
}
buf[9] = '\0';
}
int main(int ac, char **av) {
t_flags flags = mx_get_flags(ac, av);
char *dir_name = get_dir_name(ac, av);
DIR *dir;
struct dirent *entry;
dir = opendir(dir_name);
if (!dir) {
perror("diropen");
exit(1);
};
struct stat s_stat;
while ((entry = readdir(dir)) != NULL) {
lstat(entry->d_name, &s_stat);
char buf_perm[10];
mx_strmode(s_stat.st_mode, buf_perm);
printf("%s %s\n", buf_perm , entry->d_name);
};
closedir(dir);
}
And here is what i get from ls and my program. I'm opening directory that doesn't contain my executable(may be it's a root of a problem)
>drwxr-xr-x 3 fstaryk 4242 102 Jan 3 17:27 .
>drwxr-xr-x 11 fstaryk 4242 374 Jan 18 17:40 ..
>-rw-r--r-- 1 fstaryk 4242 4365 Jan 18 17:40 main.c
>rwxr-xr-x .
>rwx------ ..
>rwx------ main.c
As you've discovered from adding the error checking as suggested in comments, you're getting issues with 'No such file or directory'. This is because lstat() resolves relative paths like the filenames in struct dirent objects starting from the current working directory, which is not the directory you're trying to list the files of.
Luckily, modern unix/linux systems have a function fstatat() that lets you specify a directory to use as the base of relative paths, and you can get the required directory descriptor from a DIR struct with dirfd().
Simplified example of using it:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
int main(void) {
DIR *d = opendir("test/");
if (!d) {
perror("opendir");
return EXIT_FAILURE;
}
int dfd = dirfd(d); // Get the directory file descriptor for use with fstatat()
if (dfd < 0) {
perror("dirfd");
closedir(d);
return EXIT_FAILURE;
}
struct dirent *entry;
while ((entry = readdir(d))) {
if (entry->d_name[0] == '.') {
// Skip dotfiles
continue;
}
struct stat s;
// Resolve filenames relative to the directory being scanned
// and don't follow symlinks to emulate lstat()'s behavior
if (fstatat(dfd, entry->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
perror("fstatat");
closedir(d);
return EXIT_FAILURE;
}
printf("%s: %ld\n", entry->d_name, (long)s.st_size);
}
closedir(d);
return 0;
}
On older OSes that lack the *at() functions, you'd have to resort to creating a string holding the directory name + filename (With snprintf() or whatever) and use that as an argument to lstat().

Trying to create code in C that print all directories starting from a root directory passed

EXAMPLE
Imagine I have a directory called Alpha and I want it as root.
Alpha contains: some files and other two directories Beta and Gamma,
Beta contains: some files and another directory called Theta,
Gamma contains: some files,
Theta contains: some files.
INPUT/OUTPUT
Using input as: ./myfind Alpha
I'll want as output:
Alpha
Beta
Gamma
Theta
(I don't care about the order).
MY CODE
I tried with this code but it doesn't work. I'll want to do a recursive function to do it and i can't use POSIX.
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <errno.h>
#if !defined(NAME_MAX)
#define NAME_MAX 256
#endif
int find(const char *passed_dir_name) {
if (chdir(passed_dir_name) == -1) {
perror("FATAL ERROR CHANGING DIRECTORY");
return -1;
}
DIR *current_directory;
if ((current_directory = opendir(".")) == NULL) {
perror("FATAL ERROR OPENING CURRENT WORKING DIRECTORY");
return -1;
}
struct dirent *dir;
while ((dir = readdir(current_directory)) != NULL) {
struct stat statbuf;
stat(dir->d_name, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
fprintf(stdout, "%s\n", dir->d_name);
find(dir->d_name);
}
}
if (closedir(current_directory) == -1) {
perror("FATAL ERROR CLOSING CURRENT WORKING DIRECTORY");
exit(EXIT_FAILURE);
}
}
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "ERROR: Run as ./myfind directory\n");
exit(EXIT_FAILURE);
}
const char *dir = argv[1];
struct stat statbuf;
stat(dir, &statbuf);
if (!S_ISDIR(statbuf.st_mode)) {
fprintf(stderr, "FATAL ERROR: %s IS NOT A DIRECTORY\n", dir);
exit(EXIT_FAILURE);
}
find(dir);
exit(EXIT_SUCCESS);
}
The problem is you change the current directory when you recurse into a subdirectory but you do not change back to the parent directory when returning from the recursive function.
You could add a chdir(".."); at the end of the find function, but it might not work in all cases:
if a directory has more than 2 hard links
if you traverse symbolic links
It is preferable to compute the path of the destination directory for the recursive call to find() by concatenating the passed_dir_name, a / and dir->d_name and avoid changing the current directory.
Here is a modified version of find() for the simplistic approach:
int find(const char *passed_dir_name) {
if (chdir(passed_dir_name) == -1) {
perror("FATAL ERROR CHANGING DIRECTORY");
return -1;
}
DIR *current_directory;
if ((current_directory = opendir(".")) == NULL) {
perror("FATAL ERROR OPENING CURRENT WORKING DIRECTORY");
chdir("..");
return -1;
}
struct dirent *dir;
while ((dir = readdir(current_directory)) != NULL) {
struct stat statbuf;
stat(dir->d_name, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
fprintf(stdout, "%s\n", dir->d_name);
find(dir->d_name);
}
}
if (closedir(current_directory) == -1) {
perror("FATAL ERROR CLOSING CURRENT WORKING DIRECTORY");
exit(EXIT_FAILURE);
}
chdir("..");
}

Reading txt files in a choosen directory

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main (void)
{
DIR *dp;
struct dirent *ep;
dp = opendir ("./");
if (dp != NULL)
{
while (ep = readdir (dp))
puts (ep->d_name);
(void) closedir (dp);
}
else
perror ("Couldn't open the directory");
return 0;
}
This code gives me all things in that directory. It works like "ls" command. For example let's say i have one folder which is name is "folder" and one .txt file which is name is "input" (names are could be different btw) , in that directory. I want to decide if it is folder or txt file.If it is txt file how can i read it?
]You can use scandir() function to open and scan the entries in a directory.
The example comes from man 3 scandir
#define _SVID_SOURCE
/* print files in current directory in reverse order */
#include <dirent.h>
int
main(void)
{
struct dirent **namelist;
int n;
n = scandir(".", &namelist, NULL, alphasort);
if (n < 0)
perror("scandir");
else {
while (n--) {
printf("%s\n", namelist[n]->d_name);
free(namelist[n]);
}
free(namelist);
}
}
Note the struct dirent:
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported
by all file system types */
char d_name[256]; /* filename */
};
you can check if current entry is a regular file, directory or others by d_type field.
Available file types are :
#define DT_UNKNOWN 0
#define DT_FIFO 1
#define DT_CHR 2
#define DT_DIR 4 // directory type
#define DT_BLK 6
#define DT_REG 8 // regular file, txt file is a regular file
#define DT_LNK 10
#define DT_SOCK 12
#define DT_WHT 14
After checking the name and type of file, you can safely open it with open()(system call) or fopen()(glib function) and read the contents by read()(if you opened file via open() or fread()(fopen() counterpart).
DON'T forget to close file after read.
Besides, if you just want to check the existence and accessibility of a directory, access() is handle.
The code below tests if the dir exist.
int exist_dir (const char *dir)
{
DIR *dirptr;
if (access(dir, F_OK) != -1) {
// file exists
if ((dirptr = opendir(dir)) != NULL) {
// do something here
closedir (dirptr);
} else {
// dir exists, but not a directory
return -1;
}
} else {
// dir does not exist
return -1;
}
return 0;
}
All information about a file except the contents can be get by calling function stat. stat has signature
int stat(const char *pathname, struct stat *buf);
and returns information about the file in the buffer pointed to by buf.
struct stat encodes information of file type and file permission in the field st_mode.
Some additional macros are defined to test the file type:
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
Here is a piece of sample code:
stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {
/* Handle regular file */
}
As for reading, concatenate directory path and filename to get file path, using function sprintf, like this:
sprintf(file_path, "%s/%s", dir_path, file_name);

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);

Infinite recursion while listing directories in linux

I try to write program where part of it is listing all directories (especially starting from /), but I have a problem with /proc/self which is infinitely recursive (I get /proc/self/task/4300/fd/3/proc/self/task/4300/fd/3/proc/self/task/4300/fd/3/proc/... and so on). What is nice way to deal with it?
EDIT: Program is written in C language and I use opendir(), readdir()
You can use the S_ISLNK macro to test the st_mode field returned by a call to lstat. If the file is a symbolic link, do not try to follow it.
[user#machine:~]:./list | grep link
/proc/mounts is a symbolic link
/proc/self is a symbolic link
Example code
#include <stdio.h> // For perror
#include <stdlib.h>
#include <sys/types.h> // For stat, opendir, readdir
#include <sys/stat.h> // For stat
#include <unistd.h> // For stat
#include <dirent.h> // For opendir, readdir
const char *prefix = "/proc";
int main(void)
{
DIR *dir;
struct dirent *entry;
int result;
struct stat status;
char path[PATH_MAX];
dir = opendir(prefix);
if (!dir)
{
perror("opendir");
exit(1);
}
entry = readdir(dir);
while (entry)
{
result = snprintf(path, sizeof(path), "%s", prefix);
snprintf(&path[result], sizeof(path) - result, "/%s", entry->d_name);
printf("%s", path);
result = lstat(path, &status);
if (-1 == result)
{
printf("\n");
perror("stat");
exit(2);
}
if (S_ISLNK(status.st_mode))
{
printf("%s", " is a symbolic link");
}
printf("\n");
entry = readdir(dir);
}
return(0);
}
From path_resolution(7):
Length limit
There is a maximum length for pathnames. If the pathname (or some intermediate pathname obtained while resolving symbolic links) is too long, an ENAMETOOLONG error
is returned ("File name too long").
I think you should employ similar behaviour: check for too long pathnames.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/param.h>
/* Short & sweet recursive directory scan, finds regular files only.
Good starting point, should work on Linux OS.
Pass the root path, and returns number of dirs and number of files
found.
*/
char *tree_scan( const char *path, int *ndirs, int *nfiles){
DIR *dir;
struct dirent *entry;
char spath[MAXPATHLEN] = "";
if( !(dir = opendir( path))){ perror("opendir"); exit(1);}
for( entry = readdir( dir); entry; entry = readdir( dir)){
sprintf( spath, "%s/%s", path, entry->d_name);
if( entry->d_type == DT_REG){ (*nfiles)++; printf( "%s\n", spath);}
if( entry->d_type == DT_DIR &&
(strcmp( ".", entry->d_name)) &&
(strcmp( "..", entry->d_name))){
(*ndirs)++; tree_scan( spath, ndirs, nfiles);
}
}
closedir( dir);
return(0);
}
/* Call it like so */
int i = 0, l = 0;
tree_scan( "/path", &i, &l);
printf("Scanned %d directories, %d files.\n", i, l);
I don't have a *nix terminal handy, but you could always take a look at the source for ls.c and see how it's done.
The source as part of the gnu core utils can be found here.
I created a ls clone a few years ago in school, and I think I got around it by watching the pathname size as ulidtko mentioned.

Resources