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

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 ...

Related

Methods to iterate every file in a directory?

I've been looking around for methods by which a directory can be monitored for file creation/modification etc. however all the previous posts I've found for Windows are C++ specific.
Microsoft does list ReadDirectoryChangesW, but this too is for C++ (I haven't the knowledge to assess whether these are compatible for C). I've only knowledge with inotify for Linux, which is fairly straightforward, and wondered if there are any simple examples of the Windows equivalent? (I do not want to use inotify on Windows despite it technically being achievable).
If you are just looking for methods, maybe this will help a bit:
https://www.geeksforgeeks.org/c-program-list-files-sub-directories-directory/
(just copy-pasted the code in case)
Tested it on linux machine and it seems to work. Not recursive though.
int main(void)
{
struct dirent *de; /* Pointer for directory entry */
/* opendir() returns a pointer of DIR type. */
DIR *dr = opendir(".");
if (dr == NULL) /* opendir returns NULL if couldn't open directory */
{
printf("Could not open current directory" );
return 0;
}
/* Refer http://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
for readdir() */
while ((de = readdir(dr)) != NULL)
printf("%s\n", de->d_name);
closedir(dr);
return 0;
}
Also, see this question if you need to check if a listed file is a directory:
Checking if a dir. entry returned by readdir is a directory, link or file
This method may not be as portable as it seems, but worth a try.
Cheers!
Using FindFirstFile to hit the first node of certain directory, then to call FindNextFile to iterate files one by one inside one directory layer.
Here is my sample code for your reference, there is a recursive funcion.
#include "stdafx.h"
#include <string>
static void iterate_dir(std::string dir) {
WIN32_FIND_DATA fd;
HANDLE hFind;
std::wstring fn_ws;
std::string fn;
int pos = 0;
int count_bg = 0;
int count_fg = 0;
std::string dir_bkp = dir;
std::string dir_sub;
std::string str_wide_char_for_any = "*.*";
std::string str_folder_node = "..";
if (dir.length() - dir.find_last_of("\\") > 1) //dir ends without "\\"
dir += "\\";
dir += str_wide_char_for_any;
std::wstring dir_wstr = std::wstring(dir.begin(), dir.end());
LPCWSTR dir_wc = dir_wstr.c_str();
hFind = FindFirstFile(dir_wc, &fd);
if (hFind == INVALID_HANDLE_VALUE) {
FindClose(hFind);
return;
}
while(true) {
if (!FindNextFile(hFind, &fd)) {
break;
}
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
fn_ws = std::wstring(fd.cFileName);
fn = std::string(fn_ws.begin(), fn_ws.end());
if (fn.compare(str_folder_node) == 0) {
continue;
}
else {
if ((pos = dir.rfind(str_wide_char_for_any)) != std::string::npos) {
dir_sub = dir;
dir_sub = dir_sub.replace(dir_sub.begin()+pos, dir_sub.end(), fn.begin(), fn.end());
}
else if (dir.length() - (pos = dir.rfind("\\")) > 1) {
dir_sub = dir;
dir_sub += "\\";
dir_sub += fn;
}
else {
dir_sub = dir;
dir_sub += fn;
}
printf("[%s][directory]:%s\n", __func__, dir.c_str());
iterate_dir(dir_sub);
continue;
}
}
else if (fd.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
fn_ws = std::wstring(fd.cFileName);
fn = std::string(fn_ws.begin(), fn_ws.end());
printf("[%s][file]:%s\n", __func__, fn.c_str());
}
else {
fn_ws = std::wstring(fd.cFileName);
fn = std::string(fn_ws.begin(), fn_ws.end());
printf("[%s][unspecified attribute file]:%s\n", __func__, fn.c_str());
}
}
FindClose(hFind);
return;
}
You can have a main.cpp like:
int main() {
std::string dir_name("C:\\test");
iterate_dir(dir);
return 0;
}

C function to remove a directory [duplicate]

How to delete a non empty directory in C or C++? Is there any function? rmdir only deletes empty directory. Please provide a way without using any external library.
Also tell me how to delete a file in C or C++?
You want to write a function (a recursive function is easiest, but can easily run out of stack space on deep directories) that will enumerate the children of a directory. If you find a child that is a directory, you recurse on that. Otherwise, you delete the files inside. When you are done, the directory is empty and you can remove it via the syscall.
To enumerate directories on Unix, you can use opendir(), readdir(), and closedir(). To remove you use rmdir() on an empty directory (i.e. at the end of your function, after deleting the children) and unlink() on a file. Note that on many systems the d_type member in struct dirent is not supported; on these platforms, you will have to use stat() and S_ISDIR(stat.st_mode) to determine if a given path is a directory.
On Windows, you will use FindFirstFile()/FindNextFile() to enumerate, RemoveDirectory() on empty directories, and DeleteFile() to remove files.
Here's an example that might work on Unix (completely untested):
int remove_directory(const char *path) {
DIR *d = opendir(path);
size_t path_len = strlen(path);
int r = -1;
if (d) {
struct dirent *p;
r = 0;
while (!r && (p=readdir(d))) {
int r2 = -1;
char *buf;
size_t len;
/* Skip the names "." and ".." as we don't want to recurse on them. */
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, ".."))
continue;
len = path_len + strlen(p->d_name) + 2;
buf = malloc(len);
if (buf) {
struct stat statbuf;
snprintf(buf, len, "%s/%s", path, p->d_name);
if (!stat(buf, &statbuf)) {
if (S_ISDIR(statbuf.st_mode))
r2 = remove_directory(buf);
else
r2 = unlink(buf);
}
free(buf);
}
r = r2;
}
closedir(d);
}
if (!r)
r = rmdir(path);
return r;
}
Many unix-like systems (Linux, the BSDs, and OS X, at the very least) have the fts functions for directory traversal.
To recursively delete a directory, perform a depth-first traversal (without following symlinks) and remove every visited file:
int recursive_delete(const char *dir)
{
int ret = 0;
FTS *ftsp = NULL;
FTSENT *curr;
// Cast needed (in C) because fts_open() takes a "char * const *", instead
// of a "const char * const *", which is only allowed in C++. fts_open()
// does not modify the argument.
char *files[] = { (char *) dir, NULL };
// FTS_NOCHDIR - Avoid changing cwd, which could cause unexpected behavior
// in multithreaded programs
// FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
// of the specified directory
// FTS_XDEV - Don't cross filesystem boundaries
ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
if (!ftsp) {
fprintf(stderr, "%s: fts_open failed: %s\n", dir, strerror(errno));
ret = -1;
goto finish;
}
while ((curr = fts_read(ftsp))) {
switch (curr->fts_info) {
case FTS_NS:
case FTS_DNR:
case FTS_ERR:
fprintf(stderr, "%s: fts_read error: %s\n",
curr->fts_accpath, strerror(curr->fts_errno));
break;
case FTS_DC:
case FTS_DOT:
case FTS_NSOK:
// Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
// passed to fts_open()
break;
case FTS_D:
// Do nothing. Need depth-first search, so directories are deleted
// in FTS_DP
break;
case FTS_DP:
case FTS_F:
case FTS_SL:
case FTS_SLNONE:
case FTS_DEFAULT:
if (remove(curr->fts_accpath) < 0) {
fprintf(stderr, "%s: Failed to remove: %s\n",
curr->fts_path, strerror(curr->fts_errno));
ret = -1;
}
break;
}
}
finish:
if (ftsp) {
fts_close(ftsp);
}
return ret;
}
If you are using a POSIX compliant OS, you could use nftw() for file tree traversal and remove (removes files or directories). If you are in C++ and your project uses boost, it is not a bad idea to use the Boost.Filesystem as suggested by Manuel.
In the code example below I decided not to traverse symbolic links and mount points (just to avoid a grand removal:) ):
#include <stdio.h>
#include <stdlib.h>
#include <ftw.h>
static int rmFiles(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb)
{
if(remove(pathname) < 0)
{
perror("ERROR: remove");
return -1;
}
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr,"usage: %s path\n",argv[0]);
exit(1);
}
// Delete the directory and its contents by traversing the tree in reverse order, without crossing mount boundaries and symbolic links
if (nftw(argv[1], rmFiles,10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
{
perror("ERROR: ntfw");
exit(1);
}
return 0;
}
The easiest way to do this is with remove_all function of the Boost.Filesystem library. Besides, the resulting code will be portable.
If you want to write something specific for Unix (rmdir) or for Windows (RemoveDirectory) then you'll have to write a function that deletes are subfiles and subfolders recursively.
EDIT
Looks like this question was already asked, in fact someone already recommended Boost's remove_all. So please don't upvote my answer.
C++17 has <experimental\filesystem> which is based on the boost version.
Use std::experimental::filesystem::remove_all to remove recursively.
If you need more control, try std::experimental::filesystem::recursive_directory_iterator.
You can also write your own recursion with the non-resursive version of the iterator.
namespace fs = std::experimental::filesystem;
void IterateRecursively(fs::path path)
{
if (fs::is_directory(path))
{
for (auto & child : fs::directory_iterator(path))
IterateRecursively(child.path());
}
std::cout << path << std::endl;
}
You can use opendir and readdir to read directory entries and unlink to delete them.
//======================================================
// Recursely Delete files using:
// Gnome-Glib & C++11
//======================================================
#include <iostream>
#include <string>
#include <glib.h>
#include <glib/gstdio.h>
using namespace std;
int DirDelete(const string& path)
{
const gchar* p;
GError* gerr;
GDir* d;
int r;
string ps;
string path_i;
cout << "open:" << path << "\n";
d = g_dir_open(path.c_str(), 0, &gerr);
r = -1;
if (d) {
r = 0;
while (!r && (p=g_dir_read_name(d))) {
ps = string{p};
if (ps == "." || ps == "..") {
continue;
}
path_i = path + string{"/"} + p;
if (g_file_test(path_i.c_str(), G_FILE_TEST_IS_DIR) != 0) {
cout << "recurse:" << path_i << "\n";
r = DirDelete(path_i);
}
else {
cout << "unlink:" << path_i << "\n";
r = g_unlink(path_i.c_str());
}
}
g_dir_close(d);
}
if (r == 0) {
r = g_rmdir(path.c_str());
cout << "rmdir:" << path << "\n";
}
return r;
}
How to delete a non empty folder using unlinkat() in c?
Here is my work on it:
/*
* Program to erase the files/subfolders in a directory given as an input
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void remove_dir_content(const char *path)
{
struct dirent *de;
char fname[300];
DIR *dr = opendir(path);
if(dr == NULL)
{
printf("No file or directory found\n");
return;
}
while((de = readdir(dr)) != NULL)
{
int ret = -1;
struct stat statbuf;
sprintf(fname,"%s/%s",path,de->d_name);
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
continue;
if(!stat(fname, &statbuf))
{
if(S_ISDIR(statbuf.st_mode))
{
printf("Is dir: %s\n",fname);
printf("Err: %d\n",ret = unlinkat(dirfd(dr),fname,AT_REMOVEDIR));
if(ret != 0)
{
remove_dir_content(fname);
printf("Err: %d\n",ret = unlinkat(dirfd(dr),fname,AT_REMOVEDIR));
}
}
else
{
printf("Is file: %s\n",fname);
printf("Err: %d\n",unlink(fname));
}
}
}
closedir(dr);
}
void main()
{
char str[10],str1[20] = "../",fname[300]; // Use str,str1 as your directory path where it's files & subfolders will be deleted.
printf("Enter the dirctory name: ");
scanf("%s",str);
strcat(str1,str);
printf("str1: %s\n",str1);
remove_dir_content(str1); //str1 indicates the directory path
}
This code will open particular directory and iterate over all files and delete them which are under that directory. After that it will delete empty directory at the end.
/**
* #file RemoveDir.c
* #author Om Patel (ompatel1861#gmail.com)
* #brief This program will remove non empty directory.
* #version 0.1
* #date 2022-05-31
*
* #copyright Copyright (c) 2022
*
*/
#include<stdio.h>
#include<string.h>
#include<dirent.h>
#include <unistd.h>
int main()
{
DIR* dir = opendir("OUTPUT/Aakash");
struct dirent* entity;
entity = readdir(dir);
while(entity != NULL){
char path[30] ="OUTPUT/Aakash/";
printf("%s\n",entity->d_name);
strcat(path,entity->d_name);
printf("PAth: %s\n",path);
remove(path);
entity = readdir(dir);
}
char path1[30] ="OUTPUT/Aakash";
rmdir(path1);
closedir(dir);
char out[20]="OUTPUT/";
char fol_file[30];
sprintf(fol_file,"%s\\",out);
printf("%s",fol_file);
return 0;
}

How to get total size of a folder in C with recursivity?

All the folders have the size 4096 B.
How do I get the total size of a folder with all the file size inside?
For example:
> Dir1 (4096)
> -- File1.txt (100)
> -- Dir 2 (4096)
> ---- File2.txt (100)
When I try to get the size of Dir1, it gives me 4096.
The expected answer I wish to get is 8392
Another example:
DirA (4096)
-- FileA (100)
-- FileB (100)
The total should be 4296
I'm trying to figure out the algorithm but I can't find a way to detect if it's a folder or not. Sorry for the "vulgar" code below...
DIR *dir;
struct dirent *dp;
struct stat fileStat;
int getTotalDirSize()
{
while()
{
if(/*Detect Folder/Directory*/)
{
totalSize += 4096;
getTotalDirSize();
}
else
{
totalSize += fileSize;
}
}
return totalSize;
}
Note: I'm using the struct stat to get the size and the file/folder name.
Its working using with stat but only you have to do is give complete path to the stat like in your example Dir1->4096B then if you want to know size of the File1.txt use in stat like this:
strcpy(str,Dir1); //str is a string
strcat(str,"/");
strcat(str,filename);
Then use str in stat to get size of that file. I think here you have to use dirent structure and DIR for folder and its contents.
For finding folder use d_type = 4 for dirent structure pointer. For recursive action put all in one separate function and call it recursively until folder search completes.
struct stat buf;
DIR *dptr;
struct dirent *sdir;
int size=0;
char str[100];
dptr=opendir(folder);
while(sdir=readdir(dptr))
{
if(sdir->d_type==4)
{
if(sdir->d_name[0]!='.')
{
stat(sdir->d_name,&buf);
size=buf.st_size;
pf("size=%d\n",size);
}
}
else
{
strcpy(str,folder);
strcat(str,"/");
strcat(str,sdir->d_name);
stat(str,&buf);
size+=buf.st_size;
pf("size=%d\n",size);
}
}
I was just searching for a solution to this same problem when I encountered this thread and some others here on SO dealing with equal or similar problems (I found at the end 3 different codes aimed to calculate the total size of a folder with pure C). Unfortunately none of them worked by themselves or were incomplete, so I had to improve them (merging one into the other) and I came to the following complete and working code-and-example (should be put inside a main.c file, the exact folder changed and then just run). I hope it helps!
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
int readFolderSize(int* totalSize, char* folder)
{
char fullPath[256];
struct dirent *dirData;
struct stat buffer;
int exists;
DIR *poDir;
int resp = EXIT_SUCCESS;
poDir = opendir(folder);
if (poDir == NULL)
{
perror("general_getFolderSize: poDir fail!");
return EXIT_FAILURE;
}
while ((dirData = readdir(poDir)))
{
if (dirData == NULL)
{
const unsigned int err = errno;
fprintf(stderr, "general_getFolderSize: Failed in readdir (%u)\n",err);
resp = EXIT_FAILURE;
continue;
}
//
if (dirData->d_type == DT_DIR)
{
if (dirData->d_name[0] != '.')
{
//printf("general_getFolderSize: Is a directory: %s\n", dirData->d_name);
strcpy(fullPath,folder);
strcat(fullPath,"/");
strcat(fullPath,dirData->d_name);
//printf("general_getFolderSize: Accessing dir: %s\n", fullPath);
if (readFolderSize(totalSize,fullPath) == EXIT_FAILURE)
resp = EXIT_FAILURE;
}
}
else
{
strcpy(fullPath,folder);
strcat(fullPath,"/");
strcat(fullPath,dirData->d_name);
exists = stat(fullPath,&buffer);
if (exists < 0)
{
const unsigned int err = errno;
fprintf(stderr, "general_getFolderSize: Failed in stat (file) %s: %u\n", fullPath,err);
resp = EXIT_FAILURE;
continue;
}
else
{
(*totalSize) += buffer.st_size;
//printf("general_getFolderSize: Item size (file) (%s): %d\n",fullPath,(int)buffer.st_size);
}
}
}
closedir(poDir);
return resp;
}
/*!
* \brief general_getFolderSize returns the size of a folder in bytes.
* \param folder is the name of the folder (preferentially without a '/' at the end)
* \param totalSize is a pointer where the result value is given
* \return
*/
int general_getFolderSize(char* folder, int* totalSize)
{
printf("general_getFolderSize: Start\n");
//
if (readFolderSize(totalSize,folder) == EXIT_FAILURE)
{
perror("general_getFolderSize: Call to readFolderSize failed!");
return EXIT_FAILURE;
}
//
printf("general_getFolderSize: Stop\n");
return EXIT_SUCCESS;
}
int main()
{
int folderSize;
if (general_getFolderSize("/home/maiquel/Documents/TEMP/maindir",&folderSize) == EXIT_FAILURE)
{
printf("Error reading folder size!");
}
else
{
printf("Folder size: %d b (%d kb / %d Mb)\n", folderSize, folderSize/1024, folderSize/(1024 * 1024));
}
return EXIT_SUCCESS;
}
P.s.: Although I'm aware that EXIT_SUCCESS and EXIT_FAILURE are not supposed to be used as function return values, I preferred to do so instead of creating new defines in the name of code simplicity.

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

Determine file/folder in C

I have the following code in C:
DIR *mydir = opendir("/");
struct dirent *entry = NULL;
while((entry = readdir(mydir)))
{
printf("%s\n", entry->d_name);
//printf("%i\n", entry->d_type);
}
closedir(mydir);
It works and shows the files/folders in the location, correctly.
However, I want to tell if it is a folder or a file. How can I do this? I tried with d_type (as you can see on the code) but no success.
Use stat():
struct stat st;
stat("nodename", &st);
int isDirectory = S_ISDIR(st.st_mode);
You should use stat() function wich get you a stat structure.
struct stat s;
if( stat(path,&s) == 0 )
{
if( s.st_mode & S_IFDIR )
{
//it's a directory
}
else if( s.st_mode & S_IFREG )
{
//it's a file
}
else
{
//something else
}
}
else
{
//error
}
You can use built in macros like this:
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
DIR *mydir = opendir(/);
struct dirent *entry = NULL;
struct stat buf;
while((entry = readdir(mydir)))
{
printf("%s\n", entry->d_name);
if (stat(entry, &buf))
{
perror("stat");
exit(-1);
}
if ( S_ISDIR(buf.st_mode) )
{
printf("%s is a directory\n", entry);
}
if ( S_ISREG(buf.st_mode) )
{
printf("%s is a regular file\n", entry);
}
}
closedir(mydir);
return 0;
}
See man 2 stat in a shell for more information.
This is system-specific and we cannot give a completely solid answer without knowing your OS and possibly other settings.
d_type in fact does work for at least some systems. Your code gives a useful answer on RedHat Linux with a value of 4 for directories, 8 for regular files, and other values for other file types. This is with a fix to the typo to surround the name / in the call to opendir with quotes.

Resources