My program reads the content of a directory specified at command line. It reads the directory recursively i.e. if we are reading contents of directory "test" and inside it we have another directory "inside", then it will also read the content of the directory named "inside". The issue is it works fine if i do not read hidden directories i.e. directories that start with "." . But in case i read hidden directories too it says Segmentation fault.
The code is as below:
The main file:
#include "helper.h"
/*
* Display's content of String array passed to it,
* that should conatin full path to files.
*/
void display(char **);
/*
* Free's the memory utilized by the program
*/
void cleanup(char **);
int main(int argc, char *argv[])
{
// ensure proper usage
if (argc != 2)
{
printf("Usage: %s [dir]\n\n", argv[0]);
return 1;
}
char **files = calloc(1, sizeof(char *));
// get files from the directory specified
getFiles(&files, argv[1]);
// display files
display(files);
// free memory utilized by files array
cleanup(files);
// that's all folks
return 0;
}
/*
* Display's content of String array passed to it,
* that should conatin full path to files.
*/
void display(char **files)
{
// Color Red
// printf("[0;31;40m");
// display files
for (int i = 0; files[i]; i++)
{
printf("%s\n", files[i]);
}
// turn off color
// printf("[0;37;40m");
}
/*
* Free's the memory utilized by the program
*/
void cleanup(char **files)
{
// free memory utilized by files array
for (int i = 0; files[i]; i++)
free(files[i]);
free(files);
}
The getFiles function is defined in helpers.c file which contain the following code as below:
#include "helper.h"
#include <dirent.h>
#include <stdlib.h>
#include <sys/types.h>
/*
* Stores the list of files present in direectory pointed by 'dir'
* in array of strings pointed by 'files'
*/
void getFiles(char ***files, const char* dir)
{
static int i;
// ensure directory is valid
if (dir == NULL)
{
printf("Error: Invalid Directory\n\n");
exit(1);
}
// declare and initialize directory handler
DIR *dd = opendir(dir);
if (dd == NULL)
{
printf("Error: Directory Not Found\n\n");
exit(2);
}
// structure that store file attributes read
struct dirent *content;
// read directory until all files are scanned
while ((content = readdir(dd)) != NULL)
{
// ignore '.' and '..' directories
if (strcmp(content->d_name, ".") == 0 ||
strcmp(content->d_name, "..") == 0)
continue;
/*if (content->d_name[0] == '.')
continue;*/
//store full file path from current directory
char temp[1024] = {0};
// make full path
makepath(temp, dir, content->d_name);
// recall itself if another directory found
if (isdir(temp))
{
// read this new directory found
getFiles(files, temp);
continue;
}
// allocate memory to store locations of char *
*files = realloc(*files, (i + 2)*(sizeof(char *)));
// allocate heap memory and store location
*(*(files + 0) + i) = (char *)strdup(temp);
// move to next location
i++;
}
// free directory handler
closedir(dd);
// set NULL after last file name
*(*(files + 0) + i) = '\0';
}
/*
* returns true if 'dir' refers to a directory, false otherwise
*/
bool isdir(const char * dir)
{
DIR *temp;
temp = opendir(dir);
if (temp != NULL)
{
closedir(temp);
return true;
}
return false;
}
/*
* appends dir and file/directory name to src,
* thus makes a full file/directory path, from current directory
*/
void makepath(char src[], const char *dir, const char *file)
{
// prepend directory name
strcat(src, dir);
strcat(src, "/");
// append file/directory name
strcat(src, file);
}
Necessary header files are included by me in helper.h file.
Also i wanted to know am i making mistake in memory allocation. (In realloc in getFiles Function).
Ignore hidden files line is commented by me at this time.
/*if (content->d_name[0] == '.')
continue;*/
If i uncomment the above line then program works fine.
If you are thinking why i am storing file names as read by readdir function, because those names are necessary for me later so that is why i am not right away displaying the file names.
Any suggestion how i can better implement this program and also how to fix the issue that occurs when i read hidden directories.
I don't know if that's the problem but here:
// set NULL after last file name
*(*(files + 0) + i) == '\0';
^ you are not setting to NULL, you are comparing
Related
I am trying to traverse a filesystem tree. When I come across a file with a certain extension I want to open the file and then count the lines in the file. It seems I am getting a segmentation fault I believe it is after/when I open the file and try to count the lines. Any help on why this is seg faulting would be appreciated.
EDIT:
I have deleted the old code because I fixed the seg fault. Now I changed the data to be fed in at the command line. But it seems either the files are not getting opened or it is not counting the lines correctly because when I run it the program will always return 0 lines.
Here is the updated code:
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
const char *get_filename_ext(const char *filename) {
const char *dot = strrchr(filename, '.');
if(!dot || dot == filename) return "";
return dot + 1;
}
int printdir(char *dir, char *targetFileExt, int depth)
{
DIR *dp;
struct dirent *entry;
struct stat statbuf;
int spaces = depth*4;
int totalLines=0;
if((dp = opendir(dir)) == NULL) {
fprintf(stderr,"cannot open directory: %s\n", dir);
return -1;
}
chdir(dir);
while((entry = readdir(dp)) != NULL) {
lstat(entry->d_name,&statbuf);
if(S_ISDIR(statbuf.st_mode)) {
/* Found a directory, but ignore . and .. */
if(strcmp(".",entry->d_name) == 0 || strcmp("..",entry->d_name) == 0){
continue;
}
printf("%*s%s/\n",spaces,"",entry->d_name);
/* Recurse at a new indent level */
totalLines = printdir(entry->d_name, targetFileExt, depth+1);
}
else {
printf("%*s%s\n",spaces,"",entry->d_name);
char *currentFileExt = get_filename_ext(entry->d_name);
if(*currentFileExt == *targetFileExt){
//open the file for reading
FILE *fPtr = fopen(entry->d_name, "r");
//traverse the file
while(!feof(fPtr)){
//if there is a new line character
int temp = fgetc(fPtr);
if(temp=='\n'){
//add a line to the total amount of lines
totalLines++;
}
}
//close the file
fclose(fPtr);
fPtr=NULL;
}
}
}
chdir("..");
closedir(dp);
return totalLines;
}
int main(int argc, char* argv[])
{
char *topdir, pwd[2]=".";
char *ext;
if (argc < 2 || argc > 3)
topdir=pwd;
else if(argc == 2){
topdir=argv[1];
}
else if(argc == 3){
topdir=argv[1];
ext=argv[2];
}
printf("Directory scan of %s\n",topdir);
int lines = printdir(topdir, ext, 0);
printf("You have written %d lines of %s code!\n", lines, ext);
return 0;
}
First of all, the filename extension check: if(*currentFileExt == *targetFileExt) Will only work for file extensions with a single character. Consider searching for ".com", and you encounter a ".c" file. get_filename_ext() will return a pointer to the first character after the dot. Then you would be comparing 'c' == 'c'. Consider using strcmp() instead, and make sure targetFileExt does not contain the leading dot, as this is how your code is set up as-is.
Second of all, printdir() in its current form does not accumulate the line count from the subdirectories.
Consider the scenario:
We're searching for .c files.
The directory you're searching in contains two subdirectories, A and B, and nothing else.
A contains a 10 LOC .c file, and B contains a 20 LOC .c file.
When you run the code:
You call printdir() from main(), let's say your code first encounters A
The function calls itself recursively and returns 10, so totalLines gets assigned a value of 10.
On the next loop iteration the function encounters B.
The function calls itself recursively, returns 20, so totalLines gets assigned a value of 20.
You have lost the 10 lines from the first loop iteration.
In order to fix this, you have three options:
Change the function signature to: int printdir(char *dir, char *targetFileExt, int depth, int totalLines); and remove int totalLines=0;. In the function call it like: totalLines = printdir(entry->d_name, targetFileExt, depth+1, totalLines); Call it from main() passing 0 for totalLines.
Change to function signature to accept a pointer to a line count variable, and increment it when you encounter lines. (impl. left as homework)
Use a global line count variable. (impl. left as homework)
I am trying to read a given directory filePath and get the names of all non folder files into a array of strings. So the issue that I need solved is how to specifically not get folder type files but also get all the other file type names and store them into the string array. Later I plan to use threads to read these individual files as well, but I need to be able to store the files names properly. The code that I am currently using is below. It should also be noted that this code is being executed by a child process from the fork() command, but I am not sure if that is relevant to the issue anyway. Any help would be appreciated. Thanks.
Example:
In the Home/Documents there are 4 files: hello.txt something.dat folder1 something2.dat
My string array should have the values hello.txt, something.dat, and something2.dat
Note: It is okay for me not to do the files as a I go through the directory as the files themselves are not going to be changed at all content wise.
//char* directory is an absolute filePath to the directory
void getFilesFromDirectory(char* directory, pid_t process)
{
int index =0;
DIR *dir;
struct dirent *ent;
//Can hold only 500 valid files in the folder
char *stringArray[500];
if ((dir = opendir (directory)) != NULL)
{
while ((ent = readdir (dir)) != NULL)
{
strcpy(stringArray[index],ent->d_name);
index++;
}
closedir (dir);
}
else
{
/* could not open directory */
perror ("");
}
//Everything Below is not related to the problem. Just what I am using it for.
pthread_t threadArray[index];
pthread_t senderThread;
//thread_param_t parameterSender;
thread_param_t paramterArray[index];
sem_init(&empty,0,bufferSize);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
//parameterSender.listItem = listHead;
pthread_create(&senderThread, NULL, senderFunction, NULL);
//int threadCounter = 0;
for(int i =0; i<index; i++)
{
paramterArray[i].fileLocation = strcat(directory, stringArray[i]);
pthread_create(&threadArray[i], NULL, threadFunction, paramterArray + i);
}
}
You could check stringArray for files without "." and set this pointer to Null. (There are files missing)
A better option is this:
#include <stdio.h>
#include <dirent.h>
int main()
{
DIR *folder;
struct dirent *entry;
int files = 0;
folder = opendir(".");
if(folder == NULL)
{
perror("Unable to read directory");
return(1);
}
printf("debug\n");
while( (entry=readdir(folder)) )
{
files++;
printf("File %3d: %s :: %s\n",
files,
entry->d_name,
(entry->d_type == DT_DIR)?"Directory" : "File"
);
}
closedir(folder);
return(0);
}
I have a C-program built on macOS High Sierra using gcc and a makefile. The first thing the program does is read a binary inputfile. The filename can be either specified from the terminal command line together with the shell command or the program will ask for it when it’s not specified.
My issue is that when the input file is not specified together with the shell command, the program returns an error, saying it cannot open the file.
Here’s what works and what doesn’t:
(program and input file are in the same directory)
open terminal
from the command line type:
./program_name –i input.dat
=> works fine
open terminal
from the commandline type:
./program_name
program prompts:
Inputfile:
I type: input.dat
=> error opening file
open Finder and go to directory with program and input file
doubleclick on program_name icon
program starts in terminal and prompts:
Inputfile:
I type: input.dat
=> error opening file
I run the very same source code on linux and windows where it works ok, so I think it must be an OS thing that I don't understand?
I can add that the program was untrusted because it doesn't come from the app store. CTRL-click on the icon solved that.
--EDIT - sorry for not adding the verifyable code.
To clarify: the argc/argv part works fine. it's the last section of the routine where it prompts for the file name where it goes wrong. Maybe it's indeed the path as Jabberwocky suggested. I'll check on that tonight and will follow-up here.
void GetFileName(nr_args, args, filename, json)
int nr_args;
char **args;
char *filename;
int* json;
{
int i = 1;
filename[0] = '\0';
/* the command 'interpreter' itself is stored in argv[0] */
while (i<nr_args) {
if (strcmp(args[i], "-e") == 0) {
/* we cannot set the json flag here, because */
/* flags have not been initialized yet */
*json = 1;
i++;
}
else {
if (strcmp(args[i], "-i") == 0) {
if (nr_args > i+1) {
/* inputfile was specified */
strncpy(filename, args[++i], MAX_ID_LENGTH);
i++;
}
}
else {
PrintError(41, NULL, args[i]);
i++;
}
}
}
if (filename[0] == '\0') {
printf("\n\nInputfile: ");
scanf("%19s", filename);
filename[MAX_ID_LENGTH] = '\0';
/* clear the input buffer, to prevent parsing an */
/* empty string as the first user command */
/* always do a getchar() independent of OS */
getchar();
printf("\n");
}
}
And this is the part where the file is opened (from main() )
/* Get filename */
GetFileName(argc, argv, inputfile, &json);
/* Open the datafile */
if ((datafile = fopen(inputfile, "rb")) == NULL) {
PrintError(40, NULL, inputfile);
ExitProgram();
return(OK);
}
EDIT2-- As per Andrew Henle's reply, this is the prototype.
void GetFileName(int, char**, char*, int*);
The function is called from the same file as it is defined in.
I run the very same source code on linux and windows where it works ok
That is meaningless.
This is an old K&R-style function definition:
void GetFileName(nr_args, args, filename, json)
int nr_args;
char **args;
char *filename;
int* json;
{
...
You can not call that function safely if the calling code uses a function prototype. A K&R-defined function expects all of its arguments to have underdone default argument promotion. A prototype for the function means the caller won't perform those promotions. The mismatch will result in undefined behavior.
Don't use such ancient functions. You can't use them with a prototype, and without a prototype you have no type safety in the function call.
Use a proper, C standard-compliant function definition:
void GetFileName( int nr_args, char **args, char *filename, int* json )
{
...
And then provide a proper prototype for all calls to the function.
I made the following routine that works for me.
So, as an example:
if the program is in location /usr/mac/my-name/programs/
and the input filename that was entered is input.dat
Then this routine will return: /usr/mac/my-name/programs/input.dat
Thanks for all your help.
#include <sys/param.h> /* realpath() */
#include <limits.h> /* PATH_MAX */
#include <mach-o/dyld.h> /* _NSGetExectablePath() */
char *GetFullPath(const char*);
#define MAX_FILENAME_LEN 100 /* or another value */
char *GetFullPath(const char *filename)
{
char path_buf[PATH_MAX + 1];
char resolved_name[PATH_MAX + 1];
char *real_path;
char *return_path;
uint32_t buf_size = sizeof(path_buf);
int index = 1;
/* this functions returns the full path of the current */
/* running application appended with var 'filename' at */
/* the end. In case of an error it returns NULL. */
if ((return_path = (char *) malloc(MAX_FILENAME_LEN)) == NULL) {
printf("GetFullPath(): error in Malloc()\n");
return(NULL);
}
/* get relative path */
if (_NSGetExecutablePath(path_buf, &buf_size) != 0) {
/* buffer too small */
printf("File Path too long.");
free(return_path);
return(NULL);
}
/* convert to absolute path */
if ( (real_path = realpath(path_buf, resolved_name)) == NULL) {
printf("Could not determine path.\n");
free(return_path);
return(NULL);
}
/* strip the application name from the end of the path */
index = strlen(real_path) - 1;
while (real_path[index] != '/') {
index--;
}
/* now check if there's enough room in return_path */
if (strlen(real_path) + strlen(filename) >= MAX_FILENAME_LEN) {
printf("File path too long.\n");
free(return_path);
return(NULL);
}
/* there's enough room, copy path and filename to return_path */
strncpy(return_path, real_path, index+1);
/* do not try to free() real_path */
return_path[index+1] = '\0';
strncat(return_path, filename, strlen(filename));
return(return_path); /* caller must free() return_path */
}
I am working on a simple project to implement "ls -R" from scratch. Whenever I run what I have, my program just keeps searching the root directory over and over again. What am I doing wrong?
void lsR(char dirName[]) {
/*
The recursive function call.
*/
DIR *dir;
struct dirent *directory;
struct stat fileStat;
char type;
char **nameList[MAX_RECURSIVE_FILES];
struct passwd *user;
int count = 0;
int i = 0;
printf("\n");
printf("./%s :\n", dirName);
printf("\n");
if ((dir = opendir(dirName)) == NULL) {
perror("opendir error:");
return;
}
while ((directory = readdir(dir)) != NULL) {
if (stat(directory->d_name, &fileStat) < 0) {
perror("fstat error:");
return;
}
if (fileStat.st_uid == 1) {
continue;
}
user = getpwuid(fileStat.st_uid);
printf("%s ", directory->d_name);
fileType(&fileStat, &type);
if ((type == 'd') && (count < MAX_RECURSIVE_FILES)) {
nameList[count] = malloc(sizeof(char)*MAX_STRING_LENGTH);
strncpy(nameList[count++], directory->d_name, MAX_STRING_LENGTH);
}
}
closedir(dir);
printf("\n");
for (i=0; i<count; i++) {
printf("Calling lsR on: %s\n", nameList[i]);
lsR(nameList[i]);
}
}
When it executes, I get the following output:
"./. :
., .., ... all other files in my current working directory ....
./. :
., .., ... all other files in my current working directory...
"
Among the list of files in the current directory you've noticed . and .. The first one is a hardlink to the current directory and the second one to the parent directory. So when you recurse through your dir entries you will want to skip those two. Otherwise the first directory you will recurse into will be ., in other words the directory you've just gone through.
This is the reason of your program current behavior, but once you fix that you will run into the issue lurker mentioned in his answer.
Additional notes :
Are you sure about the char **nameList[MAX_RECURSIVE_FILES]; variable? Seems to me you want an array of char * not an array of char **.
Are you aware you can use the S_ISDIR macro on the st_mode field of your stat struct, in order to check that the current file is not a directory instead of your custom function?
You need to include the path relative to your program's current directory. Each nameList element will need to be dirName + "/" + directory->d_name.
If you started out calling lsR on the local directory, ./foo and foo has directory named bar under it, then to open bar you need to open ./foo/bar since your program is running from the directory represented by ..
My program iterates through a single directory (non-recursively) and stores the names of all the files in that directory inside an array. Then, it uses that array in the second part of my program and returns some information about each file. I can iterate through the directory, and I can process a single file, but I'm having trouble combining the two parts of the program. Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int getArraySize(char* arr[]);
int getArraySize(char* arr[]) {
return sizeof(&arr);
}
char *filesArray[200];
int main (int argc, char* argv[])
{
DIR *dir;
struct dirent *ent;
int filesCtr = 0;
if ((dir = opendir ("/home/dshah/Documents/CECS 420/Project 3")) != NULL) {
while ((ent = readdir (dir)) != NULL) { /* print all the files and directories within directory */
if (strcmp(ent->d_name, ".") == 0) {
continue;
} else if (strcmp(ent->d_name, "..") == 0) {
continue;
} else if (ent->d_type == 4) { // if a directory
continue;
} else {
filesArray[filesCtr] = ent->d_name;
printf("%s\n", filesArray[filesCtr]);
filesCtr++;
}
}
closedir (dir);
} else { /* could not open directory */
perror ("Could not open directory");
}
int i;
for (i = 0; i < getArraySize(filesArray); i++) {
char* filename = filesArray[i];
FILE *file = fopen (filename, "r");
if (file != NULL) {
char line [128]; /* or other suitable maximum line size */
int ctr = 1;
while (fgets(line, sizeof line, file) != NULL) { /* read a line */
if (strstr(line, "is") != NULL) {
printf("%s:%d:%s", filename, ctr, line);
}
ctr++;
}
fclose (file);
} else {
perror (filename); /* why didn't the file open? */
}
}
return 0;
}
The line I am having trouble with is:
char* filename = filesArray[i];
Is this line of code correct? It works when I set filename to a string like "file.txt", so shouldn't this also work when I do printf("n %s\n", filesArray[i]);? Is filesArray[i] in this line of code a string?
EDIT:
Thanks, that fixed the problem. One more quick question: I'm trying to append the full path on
FILE *file = fopen (filename, "r");`
line by changing it to
FILE *file = fopen (strcat("/home/dshah/Documents/CECS 420/Project 3/", filename), "r");
but it gives me a segmentation fault. Shouldn't this work cause I'm just specifying the path?
When you pass an array to a function, it decays to a pointer, so when you do e.g. &arr you actually get a pointer to that pointer, and the size of a pointer is most likely not the size of the original array. If (and I mean really if) the array is actually a string, you can use strlen to get the length of the string (not including the string terminator character).
In your case, you don't actually need the getArraySize function, as you already have a counter telling you how many strings there is in the filesArray array: The filesCtr variable.
Also, when using a function such as readdir the d_name field of the returned entry may actually be pointing to a static array so you can't really just copy the pointer, you have to copy the complete string. This is done with the strdup function:
filesArray[filesCtr] = strdup(ent->d_name);
Remember that when done you have to free this string.
Oh, and avoid using "magic numbers" in your code, for example when checking if the directory entry is a sub-directory (ent->d_type == 4). Use the macros available to use (end->d_type == DT_DIR).
And a final thing, the d_name field of the readdir entry only contains the actual filename, not the full path. So if you want the full path you have to append the path and the filename.