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!
Related
Is there any other way possible to read a file status in C without using stat() function? I'm doing some research around the Internet but can't find one? Any answers would help a lot! Thank you.
As my comment mentions, you can use the raw syscall if you know the calling convention. The question still remains: why would you do that? :)
Since you were asking for any other way than the usual route, the example below purposely doesn't use any symbolic constants or structures, but here are some hints...
I know the syscall number for stat is 4.
I know the call expects a pointer to a pathname and a pointer to memory to write the information to.
I know a struct stat is 144 bytes, so 256 bytes allocated on the stack is enough.
I know the offset of st_size in that structure is 48 bytes.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv) {
char buf[256];
if (argc < 2)
exit(2);
if (syscall(4, argv[1], buf) != 0)
exit(10);
printf("%s is %d bytes\n", argv[1], *(int *)(buf + 48));
}
Running in a Linux box:
root#4a4740c47031:/zoop# ls -la
total 8
drwxr-xr-x 3 root root 96 Oct 15 08:50 .
drwxr-xr-x 1 root root 4096 Oct 15 08:39 ..
-rw-r--r-- 1 root root 255 Oct 15 08:46 zoop.c
root#4a4740c47031:/zoop# gcc -Wall -o zoop ./zoop.c && ./zoop ./zoop.c
./zoop.c is 255 bytes
root#4a4740c47031:/zoop#
I am testing simple program to add timestamp to xattr.
lsetxattr system call returns error with EPERM. Is that expected behaviour?
How can I set xattr to symlink, not follow the link?
#include <sys/types.h>
#include <attr/xattr.h>
#include <errno.h>
#include <time.h>
#include <stdio.h>
int main(int argc, char **argv){
time_t t = time(NULL);
if(lsetxattr(argv[1], "user.ts", &t, sizeof(time_t), 0) == -1){
perror(argv[1]);
}
return(0);
}
example:
> ls -l a b c
-rw-r--r-- 1 saka users 0 Feb 1 09:08 a
-rw-r--r-- 1 saka users 0 Feb 1 09:08 b
lrwxrwxrwx 1 saka users 1 Feb 1 09:08 c -> b
> ./ts a
> ./ts c
c: Operation not permitted
I tested on 3.10.0-862.14.4.el7.x86_64 with xfs or ext3.
I guess it's this:
/*
* In the user.* namespace, only regular files and directories can have
* extended attributes. For sticky directories, only the owner and
* privileged users can write attributes.
*/
And in fact, trying to set a trusted.foo attribute on a symlink with lsetxattr(2) as root works, while a user.foo fails with EPERM.
This question already has answers here:
lstat: can't access files in another directory
(2 answers)
Closed 2 years ago.
When using opendir() on the following test directory
drwxrwxr-x 2 foo foo 4096 Mai 4 22:55 ./
drwxrwxr-x 5 foo foo 4096 Mai 4 22:59 ../
-rw-rw-r-- 1 foo foo 21 Mai 4 21:46 bar
-rw-rw-r-- 1 foo foo 18 Mai 4 21:46 baz.txt
-rw-rw-r-- 1 foo foo 12 Mai 4 21:45 foo
-rw-rw-r-- 1 foo foo 6 Mai 4 23:06 test.txt
and iterating over each of the files with
while (0 != (dirEntry = readdir(dirHandle))) { // loop over each file
using lstat on each file and store the result in the attr struct of type stat
struct stat attr;
lstat(dirEntry->d_name, &attr);
the following code results in not understandable behavior
if (attr.st_mode & S_IFREG) { ... }
false: on file bar and baz.txt and foo results in 0, no regular file.
if (attr.st_mode & S_IFDIR) { ... }
results in a number which is equal to S_IFDIR and the file is,
therefore, a folder, even if it just contains some text.
test.txt results 0 for both somehow.
Why is it like that? Why is a plain text file a directory and what is the difference to a regular file?
All are just textfiles created with vim.
I was thinking, that in Linux all folders are textfiles, however, I need to distinguish them with normal files, if possible, with the method above.
Source
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
// OPENDIR
#include <sys/types.h>
#include <dirent.h>
struct dirent *dirEntry;
int main(int argc, char *argv[]) {
struct stat mainStat;
// Check usage
if(argc < 2) {
printf("Usage\n");
return EXIT_FAILURE;
}
if (lstat(argv[1], &mainStat) == -1) {
perror("lstat");
exit(EXIT_FAILURE);
}
DIR *dirHandle;
// Open the directory
dirHandle = opendir(argv[1]);
double size;
// if 0 retrurn, error
if(dirHandle) {
while (0 != (dirEntry = readdir(dirHandle))) { // loop over each file
// Check if the file is a directory
struct stat attr;
lstat(dirEntry->d_name, &attr);
printf("%s: v:%d 1:%d2:%d \n", dirEntry->d_name, (attr.st_mode & S_IFREG), attr.st_mode, S_IFREG);
if (strcmp(dirEntry->d_name, "..") == 0 || strcmp(dirEntry->d_name, ".") == 0) {
continue;
}
printf("%s: v:%d 1:%d2:%d \n", dirEntry->d_name, (attr.st_mode & S_IFDIR), attr.st_mode, S_IFDIR);
}
}
}
The problem is that the path is not correct.
Using error handling, perror results in the error message lstat: No such file or directory
Prober error handling in C is very important, the solution is the following code with an absolute path rather than the relative:
if(lstat("/home/foo/CLionProjects/untitled2/testdir/", &attr)==0) {
perror("lstat");
}
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
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.