stat() doesn't work on parent directory - c

I'm trying to code the ls command in C, but stat() refuse to open any other directory.
~/Desktop/ls$ cat bug.c
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <unistd.h>
int main(int ac, char **av)
{
DIR *d;
struct dirent *dir;
struct stat file;
d = opendir(av[1]);
if (d)
{
while ((dir = readdir(d)) != NULL)
{
printf("%s ->", dir->d_name);
if (lstat(dir->d_name, &file) < 0)
printf(" can't read file %s!", dir->d_name);
printf("\n");
}
}
closedir(d);
return (0);
}
When running ./a.out . or any subfolder, it works correctly.
But if I write ./a.out .. , it fails to open files...
~/Desktop/ls$ ./a.out ..
.. ->
fkdkfdjkfdkfjdfkdfjkdfjkdjkfdkjf -> can't read file fkdkfdjkfdkfjdfkdfjkdfjkdjkfdkjf!
ss -> can't read file ss!
ls -> can't read file ls!
. ->
tg -> can't read file tg!
./a.out /home/login/Desktop doesn't work either, but ./a.out /home/login/Desktop/ls/ display correctly the content of the current folder.
It looks like a.out can't open parents dir, but ls -l gives :
-rwxrwxr-x 1 hellomynameis hellomynameis 13360 nov. 25 09:56 a.out
Am I doing it the wrong way ?
Thanks !

Your lstat call is wrong. When you get a name from the opened directory, it is a relative name, so you need to convert it to a correct path to let lstat locate the file:
char path[...];
sprintf(path,"%s/%s",av[1],dir->d_name);
lstat(path,...);

The program a.out may has not permission to read all the files in that folder. Try to run a.out with root permission.
And, if you want to check the error, please print the errno to get the detail of error when the lstat function does not execute success.

Related

How to fix "recursively print subdirectories in c" [duplicate]

This question already has answers here:
stat() error 'No such file or directory' when file name is returned by readdir()
(2 answers)
Closed 3 years ago.
I have the following files/directories in my current directory. test_folder1 is a directory and there is one more directory in that directory. My C code is supposed to print all the files/directories in the current directory recursively. However, it only prints the current directory and one level down subdirectory, it does not go beyond that. Please help.
Current Directory:
a.out at.c dt dt.c main.c README test.c test_folder1.
Subdirectory of test_folder1:
ahmet.txt mehmet.txt test_folder2.
Subdirectory of test_folder2:
mahmut.txt
This for mac terminal C code.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <limits.h>
void depthFirst(DIR *dir){
struct dirent *sd;
char path[PATH_MAX];
if(dir == NULL){
printf("Error, unable to open\n");
exit(1);
}
while( (sd = readdir(dir)) != NULL){
if(strcmp(sd->d_name, ".") != 0 && strcmp(sd->d_name, "..") != 0){
printf("%s\n", sd->d_name);
realpath(sd->d_name,path);
if(isdirectory(path)){
depthFirst(opendir(sd->d_name));
}
}
}
}
int isdirectory(char *path) {
struct stat statbuf;
if (stat(path, &statbuf) == -1)
return 0;
else
return S_ISDIR(statbuf.st_mode);
}
int main(int argc, char *argv[]){
if(argc<2){
printf("No arguments");
DIR *dir;
dir = opendir(".");
depthFirst(dir);
closedir(dir);
}
This is the output
README
main.c
test.c
test_folder1
ahmet.txt
mehmet.txt
test_folder2
a.out
at.c
dt
dt.c
At the point where you're calling realpath(sd->d_name, path) for test_folder2, your current working directory is still . rather than test_folder1, so realpath() is using for ./test_folder2 rather than ./test_folder1/test_folder2.
As a result, path is the absolute path to a would-be ./test_folder2 and not ./test_folder1/test_folder2, and so your stat() call fails, meaning that test_folder2 is not a directory and therefore depthFirst() isn't called for it.
What you need to do is:
Upon entry to depthFirst(), save the current working directory (getcwd()) in some local variable and change directory (chdir()) to the directory you have as a parameter.
Before exiting depthFirst(), change directory back to the previous working directory.
You may want to have depthFirst() receive a path as a string and do the opendir() call by itself.
Let's suppose you have the following structure (program starts outside of dir_origin with dir_origin as argument):
dir_origin
README.md
dir_p0:
file1.txt
dir_p1:
file_1_1.txt
file_1_2.txt
dir_p2:
single.txt
some_other_files.txt
at this point (first recursive call):
if(isdirectory(path)){
depthFirst(opendir(sd->d_name));
You're trying to operate on dir_p0 but the process still working in the directory dir_origin/.., so you need to enter the parent of the directory you want to process first (which is dir_origin), you can do this by calling chdir on the parent directory before every recursive call to the depthFirst() and restore the working directory after the recursive call by calling chdir again with ..
Another solution to avoid changing working directory is to keep building the full path for subdirectories by joining the current path, file separator ('/') and the sub-directory to be processed before the recursive call.

execv vs execvp, why just one of them require the exact file's path?

I have two files in the same directory.
directory/
| a.c
| b.c
a.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
pid_t pid;
int status;
int wret;
if ((pid = fork()) < 0)
printf("error");
else if(pid == 0)
{
printf("%s", argv[1]);
execv(argv[1], &argv[1]);
}
else
{
/* respawn */
if ((wret = wait(&status)) != -1)
execv(argv[1], &argv[1]);
}
return 0;
}
b.c is just a simple program that print "hello".
I want to run ./a b from the command line to make the a program call exexXX to execute the b program.
I don't understand why if I use execv I can write just ./a b in the command line, instead if I use execvp I have to write ./a ./b.
The man exec page is not clear because it reports
"The initial argument for these functions is the name of a file that
is to be executed."
Thanks
If the program name argument contains no slashes, the execvp() function looks for the program to execute in the directories listed on your PATH environment variable. If you don't have . (the current directory) on your PATH and you aren't in one of the directories listed on your path, a plain name like b will not be executed, even if b is in the current directory. If the name contains a slash, it can be relative (./b) or absolute (/home/someone/src/programs/b) and it will be interpreted as a file name to be executed without consulting the PATH environment variable.
By contrast, execv() treats a plain b in the program name argument as ./b — the name of the file in the current directory and executes it if it is present, and fails if it is located somewhere else.
At one time, there was a comment that asked:
Are you saying if you have an executable b in . and you do execv("b", b_args), it will get executed?
On a normal Unix box, yes.
Code b.c:
#include <stdio.h>
int main(void)
{
puts("Hello");
return 0;
}
Code a.c:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
char *argv[] = { "b", 0 };
execv(argv[0], argv);
fprintf(stderr, "failed to execute '%s'\n", argv[0]);
return 1;
}
Running these:
$ (PATH=$(clnpath "$PATH" ".:$PWD"); echopath PATH; ./a)
/Users/jleffler/bin
/opt/informix/12.10.FC6/bin
/Users/jleffler/oss/bin
/Users/jleffler/oss/rcs/bin
/usr/local/mysql/bin
/opt/gcc/v7.3.0/bin
/Users/jleffler/perl/v5.24.0/bin
/usr/local/bin
/usr/bin
/bin
/opt/gnu/bin
/usr/sbin
/sbin
Hello
$
The clnpath script modifies the string provided as its first argument ("$PATH") by removing any occurrences of any of the directory names listed in its second path-like argument (".:$PWD") — it's how I edit my PATH on the fly when I need to. The echopath script echoes the directories on PATH (or any other path-like variable, or it will process the result of expanding a pathlike variable, such as "$PATH"), one per line — the output shows that neither . nor /Users/jleffler/soq (which is where I run the program) is on $PATH in the sub-shell. The ./a runs the code from a.c (it would not be executed without that ./ in front), which in turn runs the code from b.c, which produces the Hello. (If there is some system where this does not work, please identify it.)
I could also arrange for b.c to be:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
puts("Hello");
const char *env = "PATH";
char *val = getenv(env);
if (val == 0)
val = "<nothing>";
printf("%s=%s\n", env, val);
return 0;
}
which would print the value of $PATH directly from the executable (to verify that neither . nor the value of the current working directory is listed).

Set owner of the new created file in C

I have a program write.c, which creates a new file. I compiled that through root user and set the sticky bit for setuid using chmod u+s write.
Now, if a user2 executes this program. A new file is created with the root as owner, why ? The owner of the file should be user2.
For that, I changed the uid using setuid() and seteuid() to user2. And then created the file. But this also creates the file with root as owner. I want to create the file as user2 as owner.
Post an mcve. What you describe works just fine on my system. This:
#!/bin/sh -e
cat > main.c <<EOF
#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int fd;
uid_t ruid,euid,suid;
struct stat sb;
getresuid(&ruid,&euid,&suid);
printf("ruid=%ld euid=%ld suid=%ld\n", (long)ruid,(long)euid,(long)suid);
if(0>(fd = open(argv[1], O_CREAT|O_RDWR, 0660))){
perror(0);
exit(1);
}
fstat(fd,&sb);
printf("owner=%ld\n", (long)sb.st_uid);
close(fd);
seteuid(ruid);
getresuid(&ruid,&euid,&suid);
printf("ruid=%ld euid=%ld suid=%ld\n", (long)ruid,(long)euid,(long)suid);
if(0>(fd = open(argv[2], O_CREAT|O_RDWR, 0660))){
perror(0);
exit(1);
}
fstat(fd,&sb);
printf("owner=%ld\n", (long)sb.st_uid);
close(fd);
}
EOF
gcc main.c
sudo chown root a.out
sudo chmod u+s a.out
rm -f roots mine
./a.out roots mine
gets me:
ruid=1008 euid=0 suid=0
owner=0
ruid=1008 euid=1008 suid=0
owner=1008
i.e., the seteuid call succesfully resets my uid and the second file
is no longer owner by root.

Linux stat(2) call gives non-existing device ID

My test program is calling stat(2) to obtain a device the file resides on.
stat.c (built with cc stat.c -o stat)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/sysmacros.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main()
{
char *path = "/home/smoku/test.txt";
unsigned int maj, min;
struct stat sb;
if (stat(path, &sb) < 0) {
fprintf(stderr, "Error getting stat for '%s': %d %s\n", path, errno, strerror(errno));
return 1;
}
maj = major(sb.st_dev);
min = minor(sb.st_dev);
fprintf(stderr, "Found '%s' => %u:%u\n", path, maj, min);
return 0;
}
Got 0:44
$ ls -l /home/smoku/test.txt
-rw-r--r-- 1 smoku smoku 306 08-30 09:33 /home/smoku/test.txt
$ ./stat
Found '/home/smoku/test.txt' => 0:44
$ /usr/bin/stat -c "%d" /home/smoku/test.txt
44
But... there is no such device in my system and /home is 0:35
$ grep /home /proc/self/mountinfo
75 59 0:35 /home /home rw,relatime shared:30 - btrfs /dev/bcache0 rw,ssd,space_cache,subvolid=258,subvol=/home
Why do I get a device ID that does not exist in my system?
stat(2) in fs/stat.c uses inode->i_sb->s_dev to fill stat.st_dev
/proc/self/mountinfo in fs/proc_namespace.c uses mnt->mnt_sb->s_dev
Apparently struct inode.i_sb superblock may be different to struct vfsmount.mnt_sb superblock in case of mount of btrfs subvolume.
This is an issue inherent to btrfs implementation, which "requires non-trivial changes in the VFS layer" to fix: https://mail-archive.com/linux-btrfs#vger.kernel.org/msg57667.html

Implementing the ls -al command in C

As a part of an assignment from one of my classes, I have to write a program in C to duplicate the results of the ls -al command. I have read up on the necessary materials but I am still not getting the right output. Here is my code so far, its only supposed to print out the file size and the file name, but the file sizes its printing are not correct.
Code:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char* argv[])
{
DIR *mydir;
struct dirent *myfile;
struct stat mystat;
mydir = opendir(argv[1]);
while((myfile = readdir(mydir)) != NULL)
{
stat(myfile->d_name, &mystat);
printf("%d",mystat.st_size);
printf(" %s\n", myfile->d_name);
}
closedir(mydir);
}
These are my results after executing the code:
[root#localhost ~]# ./a.out Downloads
4096 ..
4096 hw22.c
4096 ankur.txt
4096 .
4096 destination.txt
Here are the correct sizes:
[root#localhost ~]# ls -al Downloads
total 20
drwxr-xr-x. 2 root root 4096 Nov 26 01:35 .
dr-xr-x---. 24 root root 4096 Nov 26 01:29 ..
-rw-r--r--. 1 root root 27 Nov 21 06:32 ankur.txt
-rw-r--r--. 1 root root 38 Nov 21 06:50 destination.txt
-rw-r--r--. 1 root root 1139 Nov 25 23:38 hw22.c
Can anyone please point out my mistake.
Thanks,
Ankur
myfile->d_name is the file name not the path, so you need to append the file name to the directory "Downloads/file.txt" first, if it's is not the working directory:
char buf[512];
while((myfile = readdir(mydir)) != NULL)
{
sprintf(buf, "%s/%s", argv[1], myfile->d_name);
stat(buf, &mystat);
....
As to why it prints 4096 that is the size of the links . and .. from the last call to stat().
Note: you should allocate a buffer large enough to hold the directory name, the file name the NULL byte and the separator, something like this
strlen(argv[1]) + NAME_MAX + 2;
This is the final code I got to work for anyone interested. It prints the correct file sizes. Credit goes to asker and mux for answering, just putting the code together. Input I got this to work for is "./main ." .
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(int argc, char* argv[])
{
DIR *mydir;
struct dirent *myfile;
struct stat mystat;
char buf[512];
mydir = opendir(argv[1]);
while((myfile = readdir(mydir)) != NULL)
{
sprintf(buf, "%s/%s", argv[1], myfile->d_name);
stat(buf, &mystat);
printf("%zu",mystat.st_size);
printf(" %s\n", myfile->d_name);
}
closedir(mydir);
}
I believe you'll observe that if you ./a.out . you will get the behaviour you expect.
You have a slightly subtle bug, observable if you examine the return code of your call to stat(2).
The fundamental mistake: the dirents returned by readdir(2) (the myfile in your code) will have a d_name relative to mydir. Your code will stat .. first, succeed, and so mystat will contain valid data for .., then all subsequent calls to stat(2) will fail, returning -1, which you do not check for, so mystat will not be modified, and you will print the st_size for the old value, i.e. that of ...
The trouble is that when you stat("ankur.txt", &mystat), you are not working on the file "Downloads/ankur.txt". Most likely, the stat() is failing; alternatively, it is reporting on a different file.
Consequently, you need to look at whether your system supports fstatat() — new in POSIX 2008 — or arrange to prefix the name of the file with name of the directory.
or maybe just system("ls -al") will also work!

Resources