Regarding checking for file or directory - c

I have a very simple program here, but it seems to be returning
a "true" value to the query S_ISDIR() even when the directory
entry is not a directory. Can any one pleeas help me. I am using QNX Neurtion RTOS
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <errno.h>
int main(int argc, char *argv[]) {
DIR *dir;
struct dirent *entry;
struct stat eStat;
char *root;
int i;
root = argv[1];
while((entry = readdir(dir)) != NULL) {
lstat(entry->d_name, &eStat);
if(S_ISDIR(eStat.st_mode))
printf("found directory %s\n", entry->d_name);
else
printf("not a dir\n");
}
return 0;
}
sample output:
found directory .
found directory ..
found directory NCURSES-Programming-HOWTO-html.tar.gz
found directory ncurses_programs
found directory ncurses.html
Following information may be helpful.
lstat for file is failing with errno set to 2. I am not sure why, can any one know this.

Just a guess; since you're not checking for an error after your lstat call, the eStat buffer could be containing the result of the last successful call. Try checking if lstat returns -1.
readdir() on Linux is fundamentally different, so I can't fully test on my system. See the sample programs at link text and link text. Modifying the lstat sample code, this seems to work for me:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int main( int argc, char **argv )
{
int ecode = 0;
int n;
struct stat sbuf;
for( n = 1; n < argc; ++n ) {
if( lstat( argv[n], &sbuf ) == -1 ) {
perror( argv[n] );
ecode++;
} else if( S_ISDIR( sbuf.st_mode ) ) {
printf( "%s is a dir\n", argv[n] );
} else {
printf( "%s is not a dir\n", argv[n] );
}
}
}
I don't know if that helps any. Note that the readdir() sample code uses opendir() as schot suggested. But I can't explain why your readdir() seems to work regardless.

My compiler says: "warning: 'dir' is used uninitialized in this function" You may want to add dir = opendir(root); after you initialize root. And don't forget to add some error checking.
I doubt this causes your problem, jcomeau_ictx is probably right. If lstat returns -1 it sets errno to a value that signifies the type of error. Look at its man page and the man page for strerror

Even though this question was asked long time ago, and I found it because this quesion. but the answers here didn't really solve the problem, so I decided to post the answer which I wrote on another post, such that if anyone had the same problem and used google to find this page, there is a clear answer.
The real reason of S_ISDIR not working as expected is dp->d_name contains only the name of the file, you need to pass the full path of the file to lstat().

Related

How does the ls command find the hard link count?

How does the command ls -l count the number of hard links of an inode?
Does it use the Linux API or is it code that requires deeper knowledge of the Linux kernel source code?
I am not, yet, able to understand the source code of ls, because I just started learning C.
It calls stat to get information about the file (in a struct stat). Then it looks at the st_nlink field of the struct stat.
Here is a real simple program illustrating the user of stat() to find hard-link counts:
#include <stdio.h>
#include <sys/stat.h>
int main ( int argc, char ** argv ) {
int i;
struct stat st; /* stat puts info here */
for (i = 1; i < argc; ++i) {
if (stat(argv[i], &st) == -1) perror(argv[i]);
else printf("%s has %d hard links\n", argv[i], st.st_nlink);
}
return 0;
}
(Pass it one or more file-names on the command line)

What is the method to check if the target of a symlink exists or not?

Using a c program I need to find and delete all symbolic links in a directory, with missing target.
What is the most efficient way to check whether the target of a symbolic link exist or not. Any method other than opening the symlink and check the return value. I'm working with linux and gcc.
stat or access, or indeed open. That's about all you can do.
From the man 3 stat man page
If the named file is a symbolic link, the stat() function shall continue pathname resolution using the
contents of the symbolic link, and shall return information pertaining to the resulting file if the
file exists.
So the following works nice:
#include <sys/stat.h>
#include <stdio.h>
int main() {
struct stat ctx;
int status = stat("test.txt", &ctx);
if(status != 0) {
perror("[stat]");
return 1;
}
else {
puts("works nice");
}
return 0;
}
The access () function with F_OK mode set will follow a symlink path.
The following code will print "Yes!" if both the symlink and the target file exist...
#include <stdio.h>
#include <unistd.h>
int
main (void)
{
if (access ("test.txt", F_OK) != -1) {
puts ("Yes!");
return 0;
}
puts ("No.");
return 1;
}

How to determine files and directories in parent/other directories

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.

detecting loops in symbolic links (c programming)

I'm looking to detect loops in symbolic links in a C program:
$ ln -s self self
$ ln -s a b
$ ln -s b a
Here's what I've got so far:
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int
main(int argc, char *argv[])
{
struct stat buffer;
int status;
if (argc != 2) {
fprintf(stderr, "error: file name required\n");
return 0;
}
errno = 0;
status = lstat(argv[1], &buffer);
if (errno == ELOOP) {
fprintf(stderr, "loop found");
}
return 1;
}
I'm running my program like this:
$ findloops self
$ findloops a
Any idea what I'm doing wrong?
This is NOT homework.
This is where I got the idea from.
The trouble is that 'lstat()' looks at the symlink and its properties, and the symlinks actually exist.
If you replace the call with 'stat()', then you will get the ELOOP error. This tries to get the information at the far end of the symlink, and that cannot be found because of the ELOOP condition.
You should only test errno after you have verified that status indicates a failure. With a genuine system call, it is unlikely that errno would be set when the call succeeds, but with library functions, you can find errno is set even though the call succeeds. For example, with some standard I/O library implementations, you can have errno == ENOTTY even after a successful function call; the code checks whether the file descriptor represents a terminal and errno is set to indicate that it isn't, but since the function succeeded, it is not legitimate to check errno.
I would take a look at the buffer returned. According to the documentation of lstat the buffer contains two items that would be relevant:
st_ino - The inode for the file (note that this number is unique to each distinct file and all directories on a Linux file system, but the same inode number can appear in different file systems).
st_dev - The device that the file currently resides on.
If you create a list containing these two items per element+the directory where the link is located as the previously visited elements, you could detect loops. Also don't forget to pop them off when you leave the directory that they were created in.
I'm not convinced that ELOOP is the value that you think it is. According to this, it identifies the maximum links tolerated in the class path, but it won't tell you which link looped first.
The documentation on the page claimed this: "ELOOP: Too many symbolic links were encountered in translating the pathname. "
ELOOP doesn't have to mean that there is a loop. It can also mean that there are too many symbolic links from source to target, as in
a -> b -> c -> d -> e ... -> z
do this enough times and the OS kernel (particularily for some cases on linux) will give up trying to follow the links, even if they are all valid and non-cyclic.
You may also be interested in man 2 readlink.
After some playing with code, it looks like you've found either a feature or a bug with lstat(2). According to the man page on lstat, which is also stat and fstat, the difference between stat and lstat is:
stat() stats the file pointed to by
path and fills in buf.
lstat() is identical to stat(), except
that if path is a symbolic link, then
the link itself is stat-ed, not the
file that it refers to
I took your program and played with it a little. I used lstat, stat, and fopen to check the link. Code is below. The bottom line is that both stat and fopen detected the link properly, while lstat failed. I have no explanation for this.
The program below, executed on file bar created as 'ln -s bar bar', gave the following output:
./foo ./bar
Errno as returned from lstat = 0
Errno as returned from stat = 92
loop found
Errno as returned from fopen = 92
loop found
Code:
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int
main(int argc, char *argv[])
{
struct stat buffer;
int status;
int savedErrno1;
int savedErrno2;
int savedErrno3;
FILE *theFile;
if (argc != 2) {
printf("error: file name required\n");
return 0;
}
errno = 0;
status = lstat(argv[1], &buffer);
savedErrno1 = errno;
printf("Errno as returned from lstat = %d\n", savedErrno1);
if (savedErrno1 == ELOOP) {
printf("loop found\n");
}
errno = 0;
status = stat(argv[1], &buffer);
savedErrno2 = errno;
printf("Errno as returned from stat = %d\n", savedErrno2);
if (savedErrno2 == ELOOP) {
printf("loop found\n");
}
errno = 0;
theFile = fopen(argv[1], "w");
savedErrno3 = errno;
printf("Errno as returned from fopen = %d\n", savedErrno3);
if (savedErrno3 == ELOOP) {
printf("loop found\n");
}
return 1;
}

fopen in C on solaris

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.

Resources