Unexpected Results using fts_children() in C - c

I have been beating my head on a wall over this fts_children() question. In the man page, http://www.kernel.org/doc/man-pages/online/pages/man3/fts.3.html, it clearly states As a special case, if fts_read() has not yet been called for a hierarchy,
fts_children() will return a pointer to the files in the logical directory
specified to fts_open(), that is, the arguments specified to fts_open().
Which I take to mean that a linked list of all the files in the current directory are returned. Well, I am finding that not to be the case and I would really appreciate some help in the matter. I expected a linked list to be returned and then I would iterate through it to find the file with the matching file name (the end goal). However, right now, I am just trying to iterate through the linked list (baby steps). Right now, it will return one file and then exit the loop. This does not make sense to me. Any help would very much appreciated!!!
Opening of file system:
char* const path[PATH_MAX] = {directory_name(argv[argc-index]), NULL};
char* name = file_name(argv[argc-index]);
if ((file_system = fts_open(path, FTS_COMFOLLOW, NULL)) == NULL){
fprintf(stderr,"%s:%s\n", strerror(errno), getprogname());
exit(EXIT_FAILURE);
}/*Ends the files system check if statement*/
/*Displays the information about the specified file.*/
file_ls(file_system,name, flags);
For clarification, the directory_name parses the inputted path from the user and returns something like /home/tpar44. That directory is then opened.
Searching within the file system:
void
file_ls(FTS* file_system, char* file_name, int* flags){
FTSENT* parent = NULL;
//dint stop = 0;
parent = fts_children(file_system, 0);
while( parent != NULL ){
printf("parent = %s\n", parent->fts_name);
parent = parent->fts_link;
}
}
Thanks!

I think this is entirely by design.
...that is, the arguments specified to fts_open()...
What it says is that it will list the root elements in the path_argv parameters for your convenenience. It treats the path_argv array as a logical directory itself.
In other words this:
int main(int argc, char* const argv[])
{
char* const path[] = { ".", "/home", "more/root/paths", NULL };
FTS* file_system = fts_open(path, FTS_COMFOLLOW | FTS_NOCHDIR, &compare);
if (file_system)
{
file_ls(file_system, "", 0);
fts_close(file_system);
}
return 0;
}
Will output
parent = .
parent = /home
parent = more/root/paths
Which, in fact, it does (see http://liveworkspace.org/code/c2d794117eae2d8af1166ccd620d29eb).
Here is a more complete sample that shows complete directory traversal:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fts.h>
#include<string.h>
#include<errno.h>
int compare (const FTSENT**, const FTSENT**);
void file_ls(FTS* file_system, const char* file_name, int* flags)
{
FTSENT* node = fts_children(file_system, 0);
if (errno != 0)
perror("fts_children");
while (node != NULL)
{
// TODO use file_name and flags
printf("found: %s%s\n", node->fts_path, node->fts_name);
node = node->fts_link;
}
}
int main(int argc, char* const argv[])
{
FTS* file_system = NULL;
FTSENT* node = NULL;
if (argc<2)
{
printf("Usage: %s <path-spec>\n", argv[0]);
exit(255);
}
char* const path[] = { argv[1], NULL };
const char* name = "some_name";
file_system = fts_open(path, FTS_COMFOLLOW | FTS_NOCHDIR, &compare);
if (file_system)
{
file_ls(file_system, name, 0); // shows roots
while( (node = fts_read(file_system)) != NULL)
file_ls(file_system, name, 0); // shows child elements
fts_close(file_system);
}
return 0;
}
int compare(const FTSENT** one, const FTSENT** two)
{
return (strcmp((*one)->fts_name, (*two)->fts_name));
}

Related

checking directory paths ends with ".", ".."

I have a programming problem that I hope someone out there can help me with. I am trying to learn C programming for a task at work and I have set myself a little project, which consists of reading down a file tree including all the sub directories obtaining information about each file.
The problem I get is that my program dosen't ignore the directory path ends with either /. or /.. and when it prints all the directories, I want to give space in front of the subdirectories for the readablity.
so the error is occured at this part:
int isDir(const char *parent, char *name) {
struct stat st_buf; // file info
char buf[BUF_SIZE];
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
return 0;
}
char *path = malloc(strlen(name) + strlen(parent) + 2);
//sprintf(char *buf, const char *format, [arg1],[arg2],...)
sprintf(path, "%s/%s", parent, name);
stat(path, &st_buf); //
return S_ISDIR(st_buf.st_mode); //directory
}
And this is main and list function:
int list(const char *name) {
DIR *dirp = opendir(name);
struct dirent *dentry;
char buf[BUF_SIZE];
while ((dentry = readdir(dirp)) != NULL) {
char *dir_name = dentry->d_name;
printf(" %s\n", dir_name);
//if it's dir, then go into dir
if (isDir(name, dir_name)) { //name : parent, dir_name : child
chdir(dir_name);
getcwd(buf, BUF_SIZE);
list(buf);
}
}
closedir(dirp);
}
int main()
{
list(".");
return 0;
}
The result is like this:
hm1.c
Data
lab1.txt
result1
lab3.txt
.
..
.
..
result2
lab3.txt
.
..
result3
lab3.txt
.
..
a.c
.
..
a.out
result I want to print
hm1.c
Data
lab1.txt
result1
lab3.txt
result2
lab3.txt
result3
lab3.txt
a.c
a.out
Your isDir is returning true/false where it returns false (or zero) if you have . oe .. and then the true/false value of S_ISDIR in other cases
What you really need is the function to return one of 3 values SKIP, isFILE or isDIR and then write your print logic based on that.
You also need to fix your memory leaks
Also note that chdir(dir_name); changes the actual directory of the process, so once you return fromlist within your loop you will no longer be able to open the files or directories that you are looping over (because you are now in a different directory)
This will fix your problems and print the format you want
enum { doSkip, isFile, isDir } testDir(char *path, char *name)
{
struct stat st_buf;
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
return doSkip;
}
stat(path, &st_buf);
if (S_ISDIR(st_buf.st_mode))
return isDir;
return isFile;
}
void list(const char *path, int indentlevel)
{
DIR *dirp = opendir(path);
struct dirent *dentry;
char buf[10000]; // Lets just make the buffer sufficently big for this example
if (!dirp) {
printf("%*sNo access\n",indentlevel,"");
return;
}
while ((dentry = readdir(dirp)) != NULL) {
sprintf(buf,"%s/%s", path, dentry->d_name);
switch (testDir(buf,dentry->d_name)) {
case doSkip:
/* do nothing */
break;
case isDir:
printf("%*s%s:\n",indentlevel,"",dentry->d_name);
list(buf,indentlevel+4);
break;
case isFile:
printf("%*s%s\n",indentlevel,"",dentry->d_name);
break;
}
}
closedir(dirp);
}
int main()
{
list(".", 0);
return 0;
}
Another way to do it, if you're willing to go to C++, is to use std::experimental::filesystem, also (mostly) known as Boost.Filesystem. With that, you would do something like:
#include <experimental/filesystem> // Could substitute <boost/filesystem.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>
using namespace std::experimental;
int main(int argc, char *argv[])
{
const auto path = filesystem::path{ argc > 1 ? argv[1] : "." };
if( filesystem::is_directory(path) )
{
std::cout << path << " is a directory containing:\n";
for( const auto& entry : boost::make_iterator_range( filesystem::recursive_directory_iterator{path}, {} ) )
{
std::cout << entry << "\n";
}
}
}
See it run here. Note that the directory iterators automatically skip . and ...

working with directories in POSIX with C

I will go ahead and say this is a homework assignment for an intro to Linux class. I would not be posting it without extensive attempts on my own, and seeing as I am a distance student this semester, I cannot make it to campus for tutoring. I need some help finding out what the issue is.
Essentially the assignment asks us to make a program that serves the same basic function as the pwd command in POSIX, to show the absolute path for the current directory. We are to use three functions along with main. We are not to use the getcwd command as well. I'll list them and their purpose
inum_to_filename: Accepts three arguments (inode number to translate, a pointer to a buffer where the name is written, and the size of the buffer). Returns nothing. It is to:
Open the current directory,
Read the first directory entry,
If the inode of the current directory matches the one passed in, copy name to buffer and return.
Otherwise read the next directory entry and repeat the previous step.
filename_to_inum: Accepts one argument (a char * representing the filename). It returns the corresponding inode number. It is to:
Read the information from the files inode into a structure in memory.
If there is any problem, display the appropriate error.
Return the inode number from the structure.
display_path: Accepts one argument (inode from the current working directory). It returns nothing. It is to:
Create an array of characters to use as a buffer for the name of the directory.
Get the inode for the parent directory using filename_to_inode.
If the parent inode is equal to the current inode, we have reached root and can return.
Otherwise, change to the parent directory and use inum_to_filename to find the name for the inode that was passed into the function. Use the buffer from step 1 to store it.
Recursively call display_path to display the absolute path.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
while (counter != 1) {
if (inode_arg == dirent_ptr->d_ino) {
strcat(pathBuffer, "/");
strcat(pathBuffer, dirent_ptr->d_name);
counter = counter + 1;
return;
} else {
dirent_ptr = readdir(dir_ptr);
}
}
closedir(dir_ptr);
}
int filename_to_inum (char *src) {
int res = 0;
struct stat info;
int result = stat(src, &info);
if (result != 0) {
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
} else {
res = info.st_ino;
}
return res;
}
void display_path (int ino_src) {
int bufSize = 4096;
char pathBuffer[bufSize];
int ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt) {
//print for test
inum_to_filename(ino_src, pathBuffer, bufSize);
printf("%s", pathBuffer);
return;
} else {
//print for test
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
}
int main (int argc, char *argv[]) {
int c_ino = filename_to_inum(".");
display_path(c_ino);
printf("\n");
}
As of right now it is displaying "/./MyName" with MyName being my personal named directory on the server. It is the directory I am running the program from. When using pwd I return "/home/MyName". I'm not really sure what my next step to getting the absolute path correct is.
The code is mostly set up to print one name at a time in the correct order, so the primary problem is the use of strcat() rather than strcpy(). Also, detecting when you're in the root directory at the start is important; if you don't, you can end up with /. or something similar (depending on exactly how you coordinate the printing) when the current directory is the root directory.
This version of your code has:
Squished the loop in inum_to_filename(), but also added error reporting. Remember, a process can be run in a directory which it does not have permission to get to (it requires a setuid program, usually — although permissions could be changed after the program is launched). In that case, it may fail to open .. (or .).
Lost variable count; it wasn't serving a useful purpose. Using the assign-and-test idiom allows the code to contain a single call to readdir().
Use strcpy() instead of strcat().
Use type ino_t to store inode numbers. Use size_t for sizes.
Reduce number of intermediate variables in filename_to_inum().
Note that the code in the if (ino_src == ino_prnt) statement body is for the root directory; in the absence of the testing print, it would do nothing.
Note that the printing in the else part is a major part of the operations, not just test printing.
Error check chdir("..");
Detect root in main().
Observe that this code is not directly suitable for rewriting into a function because it changes the process's current directory to / when it succeeds.
Revised code:
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
static void inum_to_filename(ino_t inode_arg, char *pathBuffer, size_t size_arg)
{
assert(size_arg > 0);
DIR *dir_ptr = opendir(".");
if (dir_ptr == 0)
{
fprintf(stderr, "Failed to open directory '.' (%d: %s)\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
struct dirent *dirent_ptr;
while ((dirent_ptr = readdir(dir_ptr)) != 0)
{
if (inode_arg == dirent_ptr->d_ino)
{
if (strlen(dirent_ptr->d_name) >= size_arg)
{
fprintf(stderr, "File name %s too long (%zu vs %zu max)\n",
dirent_ptr->d_name, strlen(dirent_ptr->d_name), size_arg);
exit(EXIT_FAILURE);
}
strcpy(pathBuffer, dirent_ptr->d_name);
break;
}
}
closedir(dir_ptr);
}
static ino_t filename_to_inum(char *src)
{
struct stat info;
if (stat(src, &info) != 0)
{
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
}
return info.st_ino;
}
static void display_path(ino_t ino_src)
{
size_t bufSize = 4096;
char pathBuffer[bufSize];
ino_t ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt)
{
// print for test
inum_to_filename(ino_src, pathBuffer, bufSize);
printf("%s", "(root): /\n");
}
else
{
// print for real
if (chdir("..") != 0)
{
fprintf(stderr, "Failed to chdir to .. (%d: %s)\n",
errno, strerror(errno));
}
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("/%s", pathBuffer);
}
}
int main(void)
{
ino_t c_ino = filename_to_inum(".");
ino_t r_ino = filename_to_inum("/");
if (r_ino == c_ino)
putchar('/');
else
display_path(c_ino);
printf("\n");
}
There are undoubtedly other ways to fix this.
Caveat: this is giving me some grief when working in /Volumes/CRUZER/Sub-Directory which is a memory stick. It fails to find the inode (1, which is surprising) when scanning /Volumes, and I've not worked out why. One of my programs — a getpwd implementation — is working fine; another is having a different problem. I expect I'll get to the bottom of it all. Testing on Mac OS X 10.10.5 with GCC 5.1.0.
this is really nice assignment :).
I read and tried your code, and it is almost correct. There were two small issues which were causing the incorrect behaviour.
First issue
When display_path reaches the root folder you don't need to call inum_to_filename and print the name of the folder because you have already printed the first folder of the path in the previous iteration. This prevents your code from showing a "./" in the beginning of the path.
That is, the if condition becomes:
if (ino_src == ino_prnt) {
return;
} else {
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
Second Issue:
You're not initializing propertly the buffer where you save the name of the directory. This causes random values to be displayed. To solve this issue you can just set the initial value of the buffer to zero by using memset.
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
memset(pathBuffer, 0, size_arg);
while (counter != 1) {
...
}
closedir(dir_ptr);
}
Full code working :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
DIR *dir_ptr = opendir(".");
struct dirent *dirent_ptr = readdir(dir_ptr);
int counter = 0;
memset(pathBuffer, 0, size_arg);
while (counter != 1) {
if (inode_arg == dirent_ptr->d_ino) {
strcat(pathBuffer, "/");
strcat(pathBuffer, dirent_ptr->d_name);
counter = counter + 1;
return;
} else {
dirent_ptr = readdir(dir_ptr);
}
}
closedir(dir_ptr);
}
int filename_to_inum (char *src) {
int res = 0;
struct stat info;
int result = stat(src, &info);
if (result != 0) {
fprintf(stderr, "Cannot stat ");
perror(src);
exit(EXIT_FAILURE);
} else {
res = info.st_ino;
}
return res;
}
/*
- Create an array of characters to use as a buffer for the name of the directory.
- Get the inode for the parent directory using filename_to_inode.
- If the parent inode is equal to the current inode, we have reached root and can return.
- Otherwise, change to the parent directory and use inum_to_filename to find the name for
the inode that was passed into the function. Use the buffer from step 1 to store it.
- Recursively call display_path to display the absolute path.
*/
void display_path (int ino_src) {
int bufSize = 4096;
char pathBuffer[bufSize];
int ino_prnt = filename_to_inum("..");
if (ino_src == ino_prnt) {
return;
} else {
chdir("..");
inum_to_filename(ino_src, pathBuffer, bufSize);
display_path(ino_prnt);
printf("%s", pathBuffer);
}
}
int main (int argc, char *argv[]) {
int c_ino = filename_to_inum(".");
display_path(c_ino);
printf("\n");
}
Output :
ubuntu#ubuntu-VirtualBox:~/dev$ vi pwd.c
ubuntu#ubuntu-VirtualBox:~/dev$ gcc pwd.c
ubuntu#ubuntu-VirtualBox:~/dev$ ./a.out
/home/ubuntu/dev
ubuntu#ubuntu-VirtualBox:~/dev$ pwd
/home/ubuntu/dev
ubuntu#ubuntu-VirtualBox:~/dev$

Execv failing even when command is available

I'm trying to call execv after manually saerching for the program to execute.
In my case,
c is a struct which has args as an array of strings having the arguments passed while receiving input. nargs is the number of arguments.
c->args[0] would contain "ls","cat" etc.
I tried printing the value of the args[0], fullPath etc. in my child process. They all show values like "/bin/ls","/bin/cat" etc. But when I call execv, it returns -1 with an errno of 2, which I understand is the error for "No such file or directory". But I'm sure the file is there because thats what my PathResolver is returning after checking all permissions.
Can anyone point where I might have made a mistake.
//The part happening inside child
char *fullPath = PathResolver(c->args[0],1,&permission);
printf("FullPath: %s -- Permission: %d\n",fullPath,permission);
if(permission==0)
{
fprintf(stderr, "%s: Command not found\n",c->args[0]);
}
else if(permission==-1)
{
fprintf(stderr, "%s: Permission denied\n",c->args[0]);
}
else
{
char* args[c->nargs+1];
int m=0;
for(m=0;m<c->nargs;m++)
{
strcpy(args[m],c->args[m]);
}
args[c->nargs] = NULL;
printf("%d\n",execv(args[0], args));
printf("errno: %d\n",errno);
}
PathResolver function
char* PathResolver(char *command, int ResolverMode, int *Permission)
{
*Permission = 0;
char *returnString;
returnString = malloc((sizeof(char)));
char *strPath = getenv("PATH");
char *del = ":";
char *strToken = strtok(strPath,del);
FILE *f;
while(strToken)
{
char filePath[100];
sprintf(filePath,"%s/%s",strToken,command);
if(access(filePath,F_OK)>=0)
{
if(access(filePath,X_OK)>=0)
{
*Permission = 1;
sprintf(returnString,"%s%s ",returnString,filePath);
if(ResolverMode == 1)
break;
}
else
{
*Permission = -1;
}
}
strToken = strtok(NULL,del);
}
sprintf(returnString,"%s\b",returnString);
return returnString;
}
strcpy(args[m],c->args[m]); is undefined behaviour, because args[m] is not a pointer to valid memory.
The following might be simpler:
char * args[c->nargs + 1];
for (size_t m = 0; m != c->nargs; ++m)
{
args[m] = c->args[m];
}
args[c->nargs] = NULL;
There's no need to copy the strings.
(This may not be your actual problem, but it certainly prevents your program from being correct.)
execv() expects the program name to be prefixed by a full path as 1st parameter.
To have PATH searched instead of providing a path use execvp().
Update:
Also this line
returnString = malloc((sizeof(char)));
does only allocate 1 byte to returnString, which is way to few for how you use returnString.

Search for a file in $PATH on Linux in C

I would like to test whether GNUPlot is installed on the system on which my program is running.
For that, I figured I'll test for the existence of the gnuplot executable in the user's install locations through stat() call.
However, I don't know how to read the $PATH environment variable in C so I can test for the existence of the file in those locations.
Use the getenv() function.
char *paths = getenv("PATH");
To loop through the parts of the column-separated list of paths, use strchr():
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *dup = strdup(getenv("PATH"));
char *s = dup;
char *p = NULL;
do {
p = strchr(s, ':');
if (p != NULL) {
p[0] = 0;
}
printf("Path in $PATH: %s\n", s);
s = p + 1;
} while (p != NULL);
free(dup);
Use getenv() to inspect the value of a particular environment variable.
To read the PATH environment variable, use getenv("PATH").
However, if you just want to run gnuplot if it's available, and perform some fallback action if it's not, then you should just try to run it (e.g. with fork and execvp or posix_spawnp) and handle the failure case.
Let which do the work for you
if (system("which gnuplot"))
/* not installed or not in path or not executable or some other error */
If you need the full path for some reason, run which with popen.
Or run gnuplot with some flag which makes it return immediately with 0 */
if (system("gnuplot --version"))
/* not installed ... */
I had a similar need and resolved it by copying libc execvp code source. I did in the most cross platform I could think of(I have no guatanty and tested just on linux). If it's not such a matter to you and you care about performances, you should use acess or _acess. Note that there is no error check whatsoever and it will just return NULL or a founded openable file in path.
The accepted answer is sometime not acceptable, when you are willing to run the same small binary over and over, redoing the path search every time by calling execvp can be non negligable overhead.
So here is the code and associated tests, you will be mainely interested in the search_in_path_openable_file function.
.h file:
bool is_openable_file(char* path);
/*Return true if path is a readable file. You can call perror if return false to check what happened*/
char* search_in_path_openable_file(char* file_name);
/*Search into PATH env variable a file_name and return the full path of the first that is openable, NULL if not in path*/
char* search_executable(char* file_name);
/*Search file, if not openable and not absolute path(contain /), look for opennable file in the path. If nothing is openable, return NULL. If something is openable, return it as it is (not guaratented to have a full path, but garatanted to be openable)*/
.c file:
#include "file_info.h"
#include <stdio.h>
#include <string.h> //strcpy
/*I wanted to do a really cross platform way. access or _acess may be better*/
bool is_openable_file(char *path) {
FILE *fp = fopen(path, "r");
if (fp) {
// exists
fclose(fp);
return true;
}
return false;
}
bool is_openable_file_until(char *path_begin, size_t until) {
char old = path_begin[until];
path_begin[until] = 0;
bool res = is_openable_file(path_begin);
path_begin[until] = old;
return res;
}
/*You may thinks that libc would have done this function and use it to implement execp function family, but you would be wrong. They just hardcoded the search in every execp function. Unbelievable.
*
* So this function is a modification of their execvp function.
*
* */
char* search_in_path_openable_file(char* file){
char *path = getenv("PATH");
if (path == NULL)
return NULL;
size_t pathlen = strlen(path);
size_t len = strlen(file) + 1;
int total_max_size=pathlen + len;
char* buf=malloc(sizeof(char)*total_max_size);
if (*file == '\0') {
return NULL;
}
char *name, *p;
/* Copy the file name at the top. */
name = memcpy(buf + pathlen + 1, file, len);
/* And add the slash. */
*--name = '/';
p = path;
do {
char *startp;
path = p;
//Let's avoid this GNU extension.
//p = strchrnul (path, ':');
p = strchr(path, ':');
if (!p)
p = strchr(path, '\0');
if (p == path)
/* Two adjacent colons, or a colon at the beginning or the end
of `PATH' means to search the current directory. */
startp = name + 1;
else
startp = memcpy(name - (p - path), path, p - path);
/* Try to execute this name. If it works, execv will not return. */
if (is_openable_file(startp))
return startp;
} while (*p++ != '\0');
/* We tried every element and none of them worked. */
return NULL;
}
char* search_executable(char* file_name){
if (is_openable_file(file_name)){//See realpath manual bug. Watch out
return file_name;
}
if (strchr (file_name, '/') != NULL) //Don't search when it contains a slash.
return NULL;
return search_in_path_openable_file(file_name);
}
tests (As you see I did not test a lot this function, there may exist some problem, use at your risk):
#include "file_info.h"
#include "munit.h"
#include <stdbool.h>
#include <unistd.h>
static void generate_search_executable(char* test_str, char* expected){
char* res= search_executable(test_str);
if (res==NULL)
munit_assert_ptr(expected,==,NULL );
else
munit_assert_string_equal(expected,res);
}
static void generate_openable(char* test_str, bool expected){
bool res= is_openable_file(test_str);
munit_assert_true(expected==res);
}
static void generate_path_search(char* test_str, char* expected_res){
char* res= search_in_path_openable_file(test_str);
if (res==NULL)
munit_assert_ptr(expected_res,==,NULL );
else
munit_assert_string_equal(expected_res,res);
}
//TODO do for other platform, better test would also set path to a custom folder that we control
#define EXISTING_FILE_NOT_IN_PATH "/usr/include/stdlib.h"
#define EXISTING_FILE_IN_PATH "ls"
#define EXISTING_FILE_IN_PATH_FULL "/bin/ls"
#define NOT_EXISTING_FILE "/usrarfzsvdvwxv/ixvxwvnxcvcelgude/ssdvtdbool.h"
int main() {
generate_openable(EXISTING_FILE_IN_PATH, false);
generate_openable(EXISTING_FILE_NOT_IN_PATH, true);
generate_openable(NOT_EXISTING_FILE, false);
generate_path_search(EXISTING_FILE_IN_PATH, EXISTING_FILE_IN_PATH_FULL);
generate_path_search(NOT_EXISTING_FILE, NULL);
generate_path_search(EXISTING_FILE_NOT_IN_PATH, NULL);
generate_search_executable(EXISTING_FILE_IN_PATH, EXISTING_FILE_IN_PATH_FULL);
generate_search_executable(NOT_EXISTING_FILE, NULL);
generate_search_executable(EXISTING_FILE_NOT_IN_PATH, EXISTING_FILE_NOT_IN_PATH);
generate_search_executable("", NULL );
//test current folder existence(maybe it just depend on path containing .,I am not sure, in that case we should remove thoses tests
generate_search_executable("file_info_test", "file_info_test" );
}
To build on one of the previous answers, you can use getenv to get the contents of PATH and then iterate over its components. Instead of using strchr you can use strsep:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
bool exists(const char fname[])
{
return access(fname, F_OK | X_OK) != -1;
}
bool find_in_path(const char name[], char *fullpath, size_t sz) {
char *paths = strdup(getenv("PATH"));
char *tmp = paths; // to use in free
const char *item;
bool found = false;
while ((item = strsep(&paths, ":")) != NULL) {
snprintf(fullpath, sz, "%s/%s", item, name);
if (exists(fullpath)) {
found = true;
break;
}
}
free(tmp);
return found;
}
int main() {
char fullpath[512];
bool found = find_in_path("uname", fullpath, sizeof(fullpath));
if (found) {
printf("found: %s\n", fullpath);
}
return 0;
}
Using C++17 to get a vector of path elements.
% a.out ls
/bin/ls
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
using namespace std;
vector<string> get_paths (string str)
{
vector<string> result;
while(!str.empty())
{
if (auto pos { str.find_first_of (':') }; pos == string::npos)
{
result.push_back(str);
break;
}
else
{
result.emplace_back(str.substr(0, pos));
str.erase(0, pos + 1);
}
}
return move(result);
}
bool exist(const string& fname, int perm=F_OK) { return access(fname.c_str(), perm) == 0; }
int main (int argc, char *argv[])
{
auto result { get_paths(getenv("PATH")) };
for (auto pp : result)
{
string npath { pp };
if (*npath.rbegin() != '/')
npath += '/';
npath += argv[1];
if (exist(npath))
cout << npath << endl;
}
return 0;
}

Traversing a Filesystem with fts(3)

I have a question on fts(3). I am getting a segmentation fault whenever I try to access any members of the fts_children() function. When I read the man page at http://www.kernel.org/doc/man-pages/online/pages/man3/fts.3.html it claims to fill itself after the read function runs and returns a linked list linked through the link field in the structure. My suspicion is that the child_function is returning nothing but I feel like that doesn't line up with the man page. Am I supposed to be adding these files to the child buffer because I thought that was being done automatically? My code is below,
Thanks!
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fts.h>
#include<string.h>
int compare (const FTSENT**, const FTSENT**);
int main(int argc, char* const argv[])
{
FTS* file_system = NULL;
FTSENT* child = NULL;
FTSENT* parent = NULL;
FTSENT* temp = NULL;
file_system = fts_open(argv + 1,FTS_COMFOLLOW | FTS_NOCHDIR,&compare);
while( (parent = fts_read(file_system)) != NULL)
{
child = fts_children(file_system,0);
printf("%s\n", child->fts_path);
}
// while (child ->fts_link != NULL)
// child = child->fts_link;
fts_close(file_system);
return 0;
}
int compare(const FTSENT** one, const FTSENT** two){
return (strcmp((*one)->fts_name, (*two)->fts_name));
}
"test_fs.c" 43L, 1108C
If you are only interested in traversing all directories and files for the specified path(s), just repeatedly call fts_read.
If you want to only iterate through the stream, #sehe's example could be rewritten as:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<fts.h>
#include<string.h>
#include<errno.h>
int compare (const FTSENT**, const FTSENT**);
void indent (int i);
int main(int argc, char* const argv[])
{
FTS* file_system = NULL;
FTSENT *node, = NULL;
if (argc<2)
{
printf("Usage: %s <path-spec>\n", argv[0]);
exit(255);
}
file_system = fts_open(argv + 1,FTS_COMFOLLOW|FTS_NOCHDIR,&compare);
if (NULL != file_system)
{
while( (node = fts_read(file_system)) != NULL)
{
switch (node->fts_info)
{
case FTS_D :
case FTS_F :
case FTS_SL:
indent(node->fts_level);
printf("%s\n", node->fts_name);
break;
default:
break;
}
}
fts_close(file_system);
}
return 0;
}
int compare(const FTSENT** one, const FTSENT** two)
{
return (strcmp((*one)->fts_name, (*two)->fts_name));
}
void indent(int i)
{
for (; i > 0; i--)
printf(" ");
}
When you run it, it iterates through the stream and lists all files and directories in order:
★ mkdir -p test/A/1 test/A/2 test/B/1 test/B/2
★ tree test
test
├── A
│   ├── 1
│   └── 2
└── B
├── 1
└── 2
★ ./fts test
test
A
1
2
B
1
2
Call fts_children only if you want a list of child nodes of a specific directory. In that case you must call fts_read at least once before calling fts_children; otherwise fts_children will only return nodes specified in argv parameter to fts_open.
You simply need to add a NULL check.
You might want to
add one for file_system
check for command line arguments
Add more errorhandling:
The fts_children() function returns a pointer to an FTSENT structure describing the first entry in a NULL terminated linked list of files in the directory, if successful. The fts_children() function may fail and set errno for any of the errors that the chdir(), malloc(), opendir(), readdir(), and stat() functions specify.
Update To the new question(s) in the comment:
The while loop for linked list traversal was misplaced (outside the outer loop?)
The printf displayed only the path... not the filename.
while you're at it:
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fts.h>
#include<string.h>
#include<errno.h>
int compare (const FTSENT**, const FTSENT**);
int main(int argc, char* const argv[])
{
FTS* file_system = NULL;
FTSENT* child = NULL;
FTSENT* parent = NULL;
if (argc<2)
{
printf("Usage: %s <path-spec>\n", argv[0]);
exit(255);
}
file_system = fts_open(argv + 1,FTS_COMFOLLOW | FTS_NOCHDIR,&compare);
if (NULL != file_system)
{
while( (parent = fts_read(file_system)) != NULL)
{
child = fts_children(file_system,0);
if (errno != 0)
{
perror("fts_children");
}
while ((NULL != child)
&& (NULL != child->fts_link))
{
child = child->fts_link;
printf("%s%s\n", child->fts_path, child->fts_name);
}
}
fts_close(file_system);
}
return 0;
}
int compare(const FTSENT** one, const FTSENT** two)
{
return (strcmp((*one)->fts_name, (*two)->fts_name));
}
Sample output fragment:
./.profiles/sehe/.opera/icons/cache/g_0000
./.profiles/sehe/.opera/icons/cache/g_0000/opr00002.tmp
./.profiles/sehe/.opera/icons/cache/g_0000/opr00003.tmp
./.profiles/sehe/home/sehe/.mozilla
fts_children: Permission denied
./.vbox-sehe-ipc/lock

Resources