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

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

Related

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.

stat() doesn't work on parent directory

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.

How to trigger a kernel module from shell?

I am using Ubuntu with VirtualBox.
I am defining a new command to my shell to output some characteristics(like sibling tree etc.) of child processes. In order to output these characteristics, I created a kernel module and used task_struct. I also tested my kernel module outside of my shell and it works.
Now my problem is how to trigger this kernel module inside my shell(in C code) so that my kernel module will be loaded?
I searched and find that I need to use system calls like modprobe or insmod but did not understand how to use them. I tried the code below, but it did not work:
setuid(0);
system("/sbin/insmod /.../mymodule.ko");
Thank you for your help.
Loading module using system()
You are trying to become root in your application (by executing setuid(0)), but you don't have permissions to do that (if you run your program as regular user). Instead, you should check if your program was run from root (using getuid()). Also, it's good idea to test if your module file exists at all. Here is an example of such code (it's tested and does all checking needed):
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define ROOT_UID 0
#define INSMOD_PATH "/sbin/insmod"
#define MOD_PATH "/.../mymodule.ko"
int main(void)
{
uid_t uid;
int res;
/* Check if program being run by root */
uid = getuid();
if (uid != ROOT_UID) {
fprintf(stderr, "Error: Please run this program as root\n");
return EXIT_FAILURE;
}
/* Check if module file exists */
if (access(MOD_PATH, F_OK) == -1) {
fprintf(stderr, "Error: File \"%s\" doesn't exist\n", MOD_PATH);
return EXIT_FAILURE;
}
/* Load module */
res = system(INSMOD_PATH " " MOD_PATH);
if (res != 0) {
fprintf(stderr, "Error loading module: %d\n", res);
return EXIT_FAILURE;
}
printf("Module \"%s\" was successfully loaded\n", MOD_PATH);
return EXIT_SUCCESS;
}
Save this code as main.c file. Be sure to replace MOD_PATH definition with actual path of your module file.
Compile it using next command:
$ gcc -Wall -O2 main.c -o load_module
Now do the next:
$ su
# ./load_module
First command switches your user to root (you will be asked to enter root password). If you don't know root password, try using sudo -s command instead of su.
Second command runs your program.
Pay your attention to the last character at the command prompt:
# means you have root permissions at this point
$ means you only have regular user permissions.
Loading module using finit_module()
Using system() function in C is usually considered a bad practice (because it takes a lot of time for execution and basically just trying to replace a much more simple Bash script).
If you want to load kernel module in C without using system(), you can look into source code of insmod tool. See libkmod/libkmod-module.c file, kmod_module_insert_module() function. You can see those sources here.
Pay attention to finit_module() function call. A good explanation about this system call can be found at manual pages:
$ man finit_module
Here is an example how you can use finit_module() system call:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/stat.h>
#include <fcntl.h>
#define ROOT_UID 0
#define MOD_PATH "/.../mymodule.ko"
static inline int finit_module(int fd, const char *uargs, int flags)
{
return syscall(__NR_finit_module, fd, uargs, flags);
}
int main(void)
{
uid_t uid;
long res;
int fd;
/* Check if program being run by root */
uid = getuid();
if (uid != ROOT_UID) {
fprintf(stderr, "Error: Please run this program as root\n");
return EXIT_FAILURE;
}
/* Check if module file exists */
if (access(MOD_PATH, F_OK) == -1) {
fprintf(stderr, "Error: File \"%s\" doesn't exist\n", MOD_PATH);
return EXIT_FAILURE;
}
/* Load module */
fd = open(MOD_PATH, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
perror("Unable to open module file");
return EXIT_FAILURE;
}
res = finit_module(fd, "", 0);
if (res != 0) {
perror("Error when loading module");
close(fd);
return EXIT_FAILURE;
}
close(fd);
printf("Module \"%s\" was successfully loaded\n", MOD_PATH);
return EXIT_SUCCESS;
}

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!

How to modify the number of XFS pre-allocated blocks?

I wrote a simple program and ran the program on ext4 and xfs.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
int
main(int argc, char *argv[])
{
int fd;
char *file_name = argv[1];
struct stat buf;
fd = open (file_name, O_RDWR|O_CREAT);
if (fd == -1) {
printf ("Error: %s\n", strerror(errno));
return -1;
}
write (fd, "hello", sizeof ("hello"));
fstat (fd, &buf);
printf ("st_blocks: %lu\n", buf.st_blocks);
stat (file_name, &buf);
printf ("st_blocks: %lu\n", buf.st_blocks);
close (fd);
stat (file_name, &buf);
printf ("st_blocks: %lu\n", buf.st_blocks);
return 0;
}
output on ext4:
st_blocks: 8
st_blocks: 8
st_blocks: 8
output on xfs:
st_blocks: 128
st_blocks: 128
st_blocks: 8
Then I explored about xfs and found an option for changing the extent size while running mkfs.xfs.
example: mkfs.xfs -r extsize=4096 /dev/sda1
But still I get the same output on XFS. Can anyone provide more insight on how to change the st_blocks. Thanks in advance.
I found the answer, posting the answer here so that others facing the problem can refer it.
mount -t xfs -o allocsize=4096 device mount-point
The allocsize option is used to tune the buffer size.
What you are seeing is xfs speculative preallocation, which is a heuristic which is used to avoid fragmentation of files as they grow.
For more info, see this FAQ entry.
You are correct that the "-o allocsize=XXX" option disables that heuristic. Your attempt at using "-r extsize=XXX" failed because that option is only for the realtime subvolume, which you are almost certainly not using.

Resources