First of all, I'm a complete novice at programming, especially in C. The example I'm going to show is code I've snagged from this site and
kicked until it didn't quite do what I wanted. :)
Second, I'm using a dual-core Celeron with Linux Mint and the GNU C Compiler.
What I want to do is list the subdirectories within a directory, skipping the parent directory if any. Unfortunately, the code I have
doesn't work. I tried strcpy and strncpy, but I couldn't figure out how to use them and I just screwed up the code so that I got a
"segmentation fault" when I tried to run the program.
Here's the code:
/*
* This program displays the names of all files in the current directory.
*/
#include <dirent.h>
#include <string.h>
#include <stdio.h>
int main(void)
{
DIR *d;
struct dirent *dir;
char dirname[255];
d = opendir(".");
if (d)
{
while ((dir = readdir(d)) != NULL)
{
if (dir->d_type == DT_DIR)
{
strncpy(dirname, dir->d_name, 254);
dirname[254] = '\0';
if (dirname != "..") /* I want to skip this, but it doesn't work */
{
printf ("%s\n", dirname);
}
}
}
closedir(d);
}
return(0);
}
Thanks for any help you can give. I'd especially be interested in links to sites that can offer tutorials that would help me with this
little project, as I want a better understanding of what the heck I'm doing. :)
In C, to compare strings you need to use either strcmp or strncmp.
if (strcmp(dirname, "..") != 0) { ... }
Related
Final update- Answer is in the comments of the accepted answer.
First of all I realize there are a lot of other answers to this question. I've been through most of them and this code is a combination of going through many other answers. All I want to do is get to the full path to every file in a directory.
#include <limits.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
DIR *d;
struct dirent * dir;
char fullpath[PATH_MAX + 1];
d = opendir("/home/adirectory");
if(d != NULL)
{
while((dir = readdir(d)) != NULL)
{
realpath(dir->d_name, fullpath);
printf("[%s]\n", fullpath);
printf("%s\n", dir->d_name);
}
// addition of the following line yields
// Value too large for defined data type
perror("Something isn't working: ");
closedir(d);
}
return 0;
}
Update #3:
The call that fails is dir = readdir(d), which is why I have perror
immediately after the while loop.
Update #2:
This works just fine on CentOS, and Ubuntu gcc 4.8.5 +. Does not work on
Solaris gcc 4.5.2.
Update:
There is an error message:
Value too large for defined data type
...but I'm not sure what could cause this.
This always just prints the current working directory that I'm running the program from. Even so, it doesn't actually list any of the files in that directory besides "." and ".." . What gives? Is there some kind of permission issue? Does this solution not work in 2017?
the d_name field contains the name of the file in the context of the directory it traverses. So, it does not contain any path, just the name.
So, in order for you to play with its path, you need to append the d_name to the name of the directory, something like the following:
char *myHomeDir = "/home/adirectory";
d = opendir(myNomDir);
. . .
while((dir = readdir(d)) != NULL) {
char filepath[PATH_MAX + 1] ;
strcpy(filepath, myHomeDir);
strcat(filepath, "/");
strcat(filepath, dir->d_name);
realpath(filepath, fullpath);
Of course the stuff above is just a skeleton code for clarity. It could be optimized better and you should use strncpy family of functions.
I must note that developing code for windows platform is a mystery for me at the moment.
I have an issue with going through a directory and reading up files, where polish characters are present. Following code snippets work on windows in english and polish version, but not in german. What's the reason? I've tried with POSIX opendir() / readdir() and with winapi FindFirstFile() / FindNextFile() functions. They gave same results.
Consider following program. It reads up files ( in POSIX and winapi way ) within test directory and prints them out.
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <dirent.h>
int main(void)
{
char* name;
name = _getcwd(NULL, 0);
char* tmp = realloc(name, strlen(name)*2);
if (tmp != NULL) {
name = tmp;
}
else {
perror("realloc");
free(name);
exit(1);
}
strcat(name, "\\test");
DIR* dir = opendir(name);
struct dirent* s;
while ((s = readdir(dir)) != NULL) {
printf("%s\n", s->d_name);
}
closedir(dir);
free(s);
strcat(name, "\\*");
HANDLE dir_w;
WIN32_FIND_DATA s_w;
if ((dir_w = FindFirstFile(name, &s_w)) != INVALID_HANDLE_VALUE) {
while (FindNextFile(dir_w, &s_w) != 0) {
printf("%s\n", s_w.cFileName);
}
}
FindClose(dir_w);
free(name);
return 0;
}
ISSUE : on windows with german language polish characters are interpreted as ASCII characters. If, for example, file has name:
ąęćś.txt
then on english language windows it is read up correctly, but on german one is read as
aecs.txt
but such file does not exist. Should I go with wchar_t type and functions specific to it (FindNextFilew) ? I don't have german windows so it is hard for me to reproduce the error. I've been trying to change code page with command chcp 1250 but that didn't help. Setting locale to pl also did not help.
Compiler: gcc 5.0.0
The C standard library functions you call will be using ANSI encoded text which as I'm sure you know cannot handle international text.
So you'll want to use the native Unicode Windows APIs rather than the legacy ANSI versions. That means wchar_t based functions with W prefixes like FindFirstFileW. For a start you should enable the Unicode conditionals so that the FindFirstFile macro expands to FindFirstFileW.
I found the answer to another question here to be very helpful.
There seems to be a limitation of the sys/stat.h library as when I tried to look in other directories everything was seen as a directory.
I was wondering if anyone knew of another system function or why it sees anything outside the current working directory as only a directory.
I appreciate any help anyone has to offer as this is perplexing me and various searches have turned up no help.
The code I made to test this is:
#include <sys/stat.h>
#include <dirent.h>
#include <stdio.h>
int main(void) {
int status;
struct stat st_buf;
struct dirent *dirInfo;
DIR *selDir;
selDir = opendir("../");
// ^ or wherever you want to look
while ((dirInfo = readdir(selDir))) {
status = stat (dirInfo->d_name, &st_buf);
if (S_ISREG (st_buf.st_mode)) {
printf ("%s is a regular file.\n", dirInfo->d_name);
}
if (S_ISDIR (st_buf.st_mode)) {
printf ("%s is a directory.\n", dirInfo->d_name);
}
}
return 0;
}
You need to check the status of the stat call; it is failing.
The trouble is that you're looking for a file the_file in the current directory when it is actually only found in ../the_file. The readdir() function gives you the name relative to the other directory, but stat() works w.r.t the current directory.
To make it work, you'd have to do the equivalent of:
char fullname[1024];
snprintf(fullname, sizeof(fullname), "%s/%s", "..", dirInfo->d_name);
if (stat(fullname, &st_buf) == 0)
...report on success...
else
...report on failure...
If you printed out stat, you'll notice there's an error (File not found).
This is because stat takes the path to the file, but you're just providing the file name.
You then call IS_REG on garbage values.
So, suppose you have a file ../test.txt
You call stat on test.txt...That isn't in directory ./test.txt, but you still print out the results from IS_REG.
I have a C program that, at one point in the program has this:
system("rm -rf foo");
Where foo is a directory. I decided that, rather than calling system, it would be better to do the recursive delete right in the code. I assumed a piece of code to do this would be easy to find. Silly me. Anyway, I ended up writing this:
#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>
#include <libgen.h>
int recursiveDelete(char* dirname) {
DIR *dp;
struct dirent *ep;
char abs_filename[FILENAME_MAX];
dp = opendir (dirname);
if (dp != NULL)
{
while (ep = readdir (dp)) {
struct stat stFileInfo;
snprintf(abs_filename, FILENAME_MAX, "%s/%s", dirname, ep->d_name);
if (lstat(abs_filename, &stFileInfo) < 0)
perror ( abs_filename );
if(S_ISDIR(stFileInfo.st_mode)) {
if(strcmp(ep->d_name, ".") &&
strcmp(ep->d_name, "..")) {
printf("%s directory\n",abs_filename);
recursiveDelete(abs_filename);
}
} else {
printf("%s file\n",abs_filename);
remove(abs_filename);
}
}
(void) closedir (dp);
}
else
perror ("Couldn't open the directory");
remove(dirname);
return 0;
}
This seems to work, but I'm too scared to actually use it in production. I'm sure I've done something wrong. Does anyone know of a C library to do recursive delete I've missed, or can someone point out any mistakes I've made?
Thanks.
POSIX has a function called ftw(3) (file tree walk) that
walks through the directory tree that is located under the directory dirpath, and calls fn() once for each entry in the tree.
kudos for being scared to death, that's a healthy attitude to have in a case like this.
I have no library to suggest in which case you have two options:
1) 'run' this code exhaustively
a) not on a machine; on paper, with pencil. take an existing directory tree, list all the elements and run the program through each step, verify that it works
b) compile the code but replace all of the deletion calls with a line that does a printf - verify that it does what it should do
c) re-insert the deletion calls and run
2) use your original method (call system())
I would suggest one additional precaution that you can take.
Almost always when you delete multiple files and/or directories it would be a good idea to chroot() into the dir before executing anything that can destroy your data outside this directory.
I think you will need to call closedir() before recursiveDelete() (because you don't want/need all the directories open as you step into them. Also closedir() before calling remove() because remove() will probably give an error on the open directory. You should step through this once carefully to make sure that readdir() does not pickup the '..'. Also be wary of linked directories, you probably wouldn't want to recurse into directories that are
symbolic or hard links.
I've been trying to get this code to work for hours! All I need to do is open a file to see if it is real and readable. I'm new to C so I'm sure there is something stupid I'm missing. Here is the code (shorthand, but copied):
#include <stdio.h>
main() {
char fpath[200];
char file = "/test/file.this";
sprintf(fpath,"~cs4352/projects/proj0%s",file);
FILE *fp = fopen(fpath,"r");
if(fp==NULL) {
printf("There is no file on the server");
exit(1);
}
fclose(fp);
//do more stuff
}
I have also verified that the path is correctly specifying a real file that I have read permissions to. Any other ideas?
Edit 1: I do know that the fpath ends up as "~cs4352/projects/proj0/test/file.this"
Edit 2: I have also tried the using the absolute file path. In both cases, I can verify that the paths are properly built via ls.
Edit 3: There errno is 2... I'm currently trying to track what that means in google.
Edit 4: Ok, errno of 2 is "There is no such file or directory". I am getting this when the reference path in fopen is "/home/courses1/cs4352/projects/proj0/index.html" which I verified does exist and I have read rights to it. As for the C code listed below, there may be a few semantic/newbie errors in it, but gcc does not give me any compile time warnings, and the code works exactly as it should except that it says that it keeps spitting errno of 2. In other words, I know that all the strings/char array are working properly, but the only thing that could be an issue is the fopen() call.
Solution: Ok, the access() procedure is what helped me the most (and what i am still using as it is less code, not to mention the more elegant way of doing it). The problem actually came from something that I didn't explain to you all (because I didn't see it until I used access()). To derrive the file, I was splitting strings using strtok() and was only splitting on " \n", but because this is a UNIX system, I needed to add "\r" to it as well. Once I fixed that, everything fell into place, and I'm sure that the fopen() function would work as well, but I have not tested it.
Thank you all for your helpful suggestions, and especially to Paul Beckingham for finding this wonderful solution.
Cheers!
The "~" is expanded by the shell, and is not expanded by fopen.
To test the existence and readability of a file, consider using the POSIX.1 "access" function:
#include <unistd.h>
if (access ("/path/to/file", F_OK | R_OK) == 0)
{
// file exists and is readable
}
First, file needs to be declared as char* or const char*, not simply char as you've written. But this might just be a typo, the compiler should at least give a warning there.
Secondly, use an absolute path (or a path relative to the current directory), not shell syntax with ~. The substitution of ~cs4352 by the respective home directory is usually done by the shell, but you are directly opening the file. So you are trying to open a file in a ~cs4352 subdirectory of your current working directory, which I guess is not what you want.
Other people have probably produced the equivalent (every modern shell, for example), but here's some code that will expand a filename with ~ or ~user notation.
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif
#include <assert.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char *relfname(const char *name, char *buffer, size_t bufsiz)
{
assert(name != 0 && buffer != 0 && bufsiz != 0);
if (name[0] != '~')
strncpy(buffer, name, bufsiz);
else
{
const char *copy;
struct passwd *usr = 0;
if (name[1] == '/' || name[1] == '\0')
{
usr = getpwuid(getuid());
copy = &name[1];
}
else
{
char username[PATH_MAX];
copy = strchr(name, '/');
if (copy == 0)
copy = name + strlen(name);
strncpy(username, &name[1], copy - &name[1]);
username[copy - &name[1]] = '\0';
usr = getpwnam(username);
}
if (usr == 0)
return(0);
snprintf(buffer, bufsiz, "%s%s", usr->pw_dir, copy);
}
buffer[bufsiz-1] = '\0';
return buffer;
}
#ifdef TEST
static struct { const char *name; int result; } files[] =
{
{ "/etc/passwd", 1 },
{ "~/.profile", 1 },
{ "~root/.profile", 1 },
{ "~nonexistent/.profile", 0 },
};
#define DIM(x) (sizeof(x)/sizeof(*(x)))
int main(void)
{
int i;
int fail = 0;
for (i = 0; i < DIM(files); i++)
{
char buffer[PATH_MAX];
char *name = relfname(files[i].name, buffer, sizeof(buffer));
if (name == 0 && files[i].result != 0)
{
fail++;
printf("!! FAIL !! %s\n", files[i].name);
}
else if (name != 0 && files[i].result == 0)
{
fail++;
printf("!! FAIL !! %s --> %s (unexpectedly)\n", files[i].name, name);
}
else if (name == 0)
printf("** PASS ** %s (no match)\n", files[i].name);
else
printf("** PASS ** %s -> %s\n", files[i].name, name);
}
return((fail == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
#endif
You could try examining errno for more information on why you're not getting a valid FILE*.
BTW-- in unix the global value errno is set by some library and system calls when they need to return more information than just "it didn't work". It is only guaranteed to be good immediately after the relevant call.
char file = "/test/file.this";
You probably want
char *file = "/test/file.this";
Are you sure you do not mean
~/cs4352/projects/proj0%s"
for your home directory?
To sum up:
Use char *file=/test/file.this";
Don't expect fopen() to do shell substitution on ~ because it won't. Use the full path or use a relative path and make sure the current directory is approrpriate.
error 2 means the file wasn't found. It wasn't found because of item #2 on this list.
For extra credit, using sprintf() like this to write into a buffer that's allocated on the stack is a dangerous habit. Look up and use snprintf(), at the very least.
As someone else here mentioned, using access() would be a better way to do what you're attempting here.