Get files access permissions using stat() in C - c

I just started learning linux/C, i just want to show the names of all the files of the directories given in argument, and their access permissions by using stat() which causes some problems.
It actually shows the correct name/mode of all the files included in the current directory, but for the other directories given in argument, it does give the right name but the same st_mode for all the files...
here's my code:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
printf("Hello, world!\n");
int status;
struct stat sbuf;
DIR *dirp;
struct dirent *dp;
dp = (struct dirent*) malloc(sizeof(struct dirent));
for (int i=1; i<argc; i++){
dirp = opendir(argv[i]);
if (dirp == NULL){/*perror("Argument invalide");*/printf("Argument %d invalid", i); exit(1);}
printf("\n\nOpening %s\n", argv[i]);
do{
dp = readdir(dirp);
if (dp != NULL && strcmp(dp->d_name,".") && strcmp(dp->d_name, "..")) {
status = stat(dp->d_name,&sbuf);
printf("The file is %s \tMode :%o\n", dp->d_name, (sbuf.st_mode & 0777));
}
} while (dp != NULL);
closedir(dirp);
}
return 0;
}
for example I did try this :
gcc -o test main.c then
./test . ..
And here's the result !
Opening .
The file is c.txt Mode :644
The file is d.txt Mode :644
The file is xxxx.txt Mode :777
The file is test Mode :755
Opening ..
The file is a.txt Mode :755
The file is b.txt Mode :755
The file is dossier Mode :755
The file is main Mode :755
The file is main.c Mode :755
The file is test Mode :755
As you can see, all the files of the " .. " directory have the same Mode, which is completely wrong... I did try with full paths and different directories, same problem.

Well, stat isn't giving you the file information because readdir gives you the file name, not the path. Try something like this to build up the path so you can actually call stat.
char *path = malloc(strlen(dp->d_name) + strlen(argv[i]) + 2);
stpcpy(stpcpy(stpcpy(path, argv[i]), "/"), dp->d_name);
status = stat(path,&sbuf);
free(path);

Related

lstat and ls permissions output is somehow differs

I'm trying to rewrite ls function with some of its flags, currently I'm implementing [-l] flag but output about permissions from original ls and lstat are different
Here is my code
void mx_strmode(mode_t mode, char * buf) {
const char chars[] = "rwxrwxrwx";
for (size_t i = 0; i < 9; i++) {
buf[i] = (mode & (1 << (8-i))) ? chars[i] : '-';
}
buf[9] = '\0';
}
int main(int ac, char **av) {
t_flags flags = mx_get_flags(ac, av);
char *dir_name = get_dir_name(ac, av);
DIR *dir;
struct dirent *entry;
dir = opendir(dir_name);
if (!dir) {
perror("diropen");
exit(1);
};
struct stat s_stat;
while ((entry = readdir(dir)) != NULL) {
lstat(entry->d_name, &s_stat);
char buf_perm[10];
mx_strmode(s_stat.st_mode, buf_perm);
printf("%s %s\n", buf_perm , entry->d_name);
};
closedir(dir);
}
And here is what i get from ls and my program. I'm opening directory that doesn't contain my executable(may be it's a root of a problem)
>drwxr-xr-x 3 fstaryk 4242 102 Jan 3 17:27 .
>drwxr-xr-x 11 fstaryk 4242 374 Jan 18 17:40 ..
>-rw-r--r-- 1 fstaryk 4242 4365 Jan 18 17:40 main.c
>rwxr-xr-x .
>rwx------ ..
>rwx------ main.c
As you've discovered from adding the error checking as suggested in comments, you're getting issues with 'No such file or directory'. This is because lstat() resolves relative paths like the filenames in struct dirent objects starting from the current working directory, which is not the directory you're trying to list the files of.
Luckily, modern unix/linux systems have a function fstatat() that lets you specify a directory to use as the base of relative paths, and you can get the required directory descriptor from a DIR struct with dirfd().
Simplified example of using it:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
int main(void) {
DIR *d = opendir("test/");
if (!d) {
perror("opendir");
return EXIT_FAILURE;
}
int dfd = dirfd(d); // Get the directory file descriptor for use with fstatat()
if (dfd < 0) {
perror("dirfd");
closedir(d);
return EXIT_FAILURE;
}
struct dirent *entry;
while ((entry = readdir(d))) {
if (entry->d_name[0] == '.') {
// Skip dotfiles
continue;
}
struct stat s;
// Resolve filenames relative to the directory being scanned
// and don't follow symlinks to emulate lstat()'s behavior
if (fstatat(dfd, entry->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
perror("fstatat");
closedir(d);
return EXIT_FAILURE;
}
printf("%s: %ld\n", entry->d_name, (long)s.st_size);
}
closedir(d);
return 0;
}
On older OSes that lack the *at() functions, you'd have to resort to creating a string holding the directory name + filename (With snprintf() or whatever) and use that as an argument to lstat().

Linux/ubuntu/No such file or directory in stat() but the file is exist

i made program that prints file size or directory size in current directory.
so i used stat() using absolute path of file. and file exists!!
however, whenever i executed program, there is a error 'no such file or directory', although the file exists. current directory is project2.
and i want to get size of 'check' file in project2.
char * filename = "check";//the file name that i have to print file size.
char * rPath = malloc(BUF_LEN);
char * realPath = malloc(BUF_LEN);
//make relative path
strcat(rPath, ".");
strcat(rPath, "/");
strcat(rPath, filename);
realpath(rPath, realPath);//get absolute path
if(stat(realPath, statbuf) < 0){
fprintf(stderr, "stat error\n");
exit(1);
}
printf("%ld\n", statbuf->st_size);
and when i change stat() like this,
if(stat("/home/minky/project/project2/check", statbuf) < 0){
fprintf(stderr, "stat error\n");
exit(1);
}
the program works. it prints size of the file.
Your code preparing relative path and assumes that the program runs with the same working directory as the file resides. Try print the current directory from your program to determine the actual directory it's trying to resolve the file path.
Assuming modification of your program:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/limits.h>
#define BUF_LEN PATH_MAX
int main()
{
char * filename = "check";//the file name that i have to print file size
char * rPath = malloc(BUF_LEN);
char * realPath = malloc(BUF_LEN);
char current_dir[BUF_LEN];
struct stat statbuf;
//make relative path
sprintf(rPath, "./%s", filename);
// Get current directory
getwd(current_dir);
printf("%s\n",current_dir);
realpath(rPath, realPath);//get absolute path
if(stat(realPath, &statbuf) < 0){
fprintf(stderr, "stat error\n");
} else {
printf("%ld\n", statbuf.st_size);
}
free(rPath);
free(realPath);
return 0;
}
Let's run it as following, assuming the code is in /tmp/main.c:
$ cd /tmp
$ gcc main.c
-------here be some warnings regarding getwd()
$ echo 1234567890 >check
$ ./a.out
/tmp
11
$ mkdir smth
$ cd smth
$ ../a.out
/tmp/smth
stat error

How to get device name on which a file is located from its path in c?

Let's say I have a file in Linux with this path:
/path/to/file/test.mp3
I want to know the path to its device. For example I want to get something like:
/dev/sdb1
How do I do this with the C programming language?
I know the terminal command to do it, but I need C functions that will do the job.
EDIT:
I have read this question before asking mine. It doesn't concretly mention code in C, it's more related to bash than to the C language.
Thanks.
You need to use stat on the file path, and get the device ID st_dev and match that to a device in /proc/partitions
Read this for how to interpret st_dev: https://web.archive.org/web/20171013194110/http://www.makelinux.net:80/ldd3/chp-3-sect-2
I just needed that inside a program I am writing...
So instead of running "df" and parsing the output, I wrote it from scratch.
Feel free to contribute!
To answer the question:
You first find the device inode using stat() then iterate and parse /proc/self/mountinfo to find the inode and get the device name.
/*
Get physical device from file or directory name.
By Zibri <zibri AT zibri DOT org>
https://github.com/Zibri/get_device
*/
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <libgen.h>
int get_device(char *name)
{
struct stat fs;
if (stat(name, &fs) < 0) {
fprintf(stderr, "%s: No such file or directory\n", name);
return -1;
}
FILE *f;
char sline[256];
char minmaj[128];
sprintf(minmaj, "%d:%d ", (int) fs.st_dev >> 8, (int) fs.st_dev & 0xff);
f = fopen("/proc/self/mountinfo", "r");
if (f == NULL) {
fprintf(stderr, "Failed to open /proc/self/mountinfo\n");
exit(-1);
}
while (fgets(sline, 256, f)) {
char *token;
char *where;
token = strtok(sline, "-");
where = strstr(token, minmaj);
if (where) {
token = strtok(NULL, " -:");
token = strtok(NULL, " -:");
printf("%s\n", token);
break;
}
}
fclose(f);
return -1;
}
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage:\n%s FILE OR DIRECTORY...\n", basename(argv[0]));
return -1;
}
get_device(argv[1]);
return 0;
}
output is just the device name.
Example:
$ gcc -O3 getdevice.c -o gd -Wall
$ ./gd .
/dev/sda4
$ ./gd /mnt/C
/dev/sda3
$ ./gd /mnt/D
/dev/sdb1
$
Use this command to print the partition path:
df -P <pathname> | awk 'END{print $1}'

Creating a new directory in C

I want to write a program that checks for the existence of a directory; if that directory does not exist then it creates the directory and a log file inside of it, but if the directory already exists, then it just creates a new log file in that folder.
How would I do this in C with Linux?
Look at stat for checking if the directory exists,
And mkdir, to create a directory.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
struct stat st = {0};
if (stat("/some/directory", &st) == -1) {
mkdir("/some/directory", 0700);
}
You can see the manual of these functions with the man 2 stat and man 2 mkdir commands.
You can use mkdir:
$ man 2 mkdir
#include <sys/stat.h>
#include <sys/types.h>
int result = mkdir("/home/me/test.txt", 0777);
I want to write a program that (...) creates the directory and a (...) file inside of it
because this is a very common question, here is the code to create multiple levels of directories and than call fopen. I'm using a gnu extension to print the error message with printf.
void rek_mkdir(char *path) {
char *sep = strrchr(path, '/');
if(sep != NULL) {
*sep = 0;
rek_mkdir(path);
*sep = '/';
}
if(mkdir(path, 0777) && errno != EEXIST)
printf("error while trying to create '%s'\n%m\n", path);
}
FILE *fopen_mkdir(char *path, char *mode) {
char *sep = strrchr(path, '/');
if(sep) {
char *path0 = strdup(path);
path0[ sep - path ] = 0;
rek_mkdir(path0);
free(path0);
}
return fopen(path,mode);
}
int mkdir (const char *filename, mode_t mode)
#include <sys/types.h>
#include <errno.h>
#include <string.h>
if (mkdir("/some/directory", S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
printf("Error: %s\n", strerror(errno));
}
For best practice it is recommended to use an integer-alias for mode. The argument mode specifies the file permissions for the new directory.
Read + Write + Execute: S_IRWXU (User), S_IRWXG (Group), S_IRWXO (Others)
Source:
https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
If you want to know that the directory exists, lookup the errno's for EEXIST.

Infinite recursion while listing directories in linux

I try to write program where part of it is listing all directories (especially starting from /), but I have a problem with /proc/self which is infinitely recursive (I get /proc/self/task/4300/fd/3/proc/self/task/4300/fd/3/proc/self/task/4300/fd/3/proc/... and so on). What is nice way to deal with it?
EDIT: Program is written in C language and I use opendir(), readdir()
You can use the S_ISLNK macro to test the st_mode field returned by a call to lstat. If the file is a symbolic link, do not try to follow it.
[user#machine:~]:./list | grep link
/proc/mounts is a symbolic link
/proc/self is a symbolic link
Example code
#include <stdio.h> // For perror
#include <stdlib.h>
#include <sys/types.h> // For stat, opendir, readdir
#include <sys/stat.h> // For stat
#include <unistd.h> // For stat
#include <dirent.h> // For opendir, readdir
const char *prefix = "/proc";
int main(void)
{
DIR *dir;
struct dirent *entry;
int result;
struct stat status;
char path[PATH_MAX];
dir = opendir(prefix);
if (!dir)
{
perror("opendir");
exit(1);
}
entry = readdir(dir);
while (entry)
{
result = snprintf(path, sizeof(path), "%s", prefix);
snprintf(&path[result], sizeof(path) - result, "/%s", entry->d_name);
printf("%s", path);
result = lstat(path, &status);
if (-1 == result)
{
printf("\n");
perror("stat");
exit(2);
}
if (S_ISLNK(status.st_mode))
{
printf("%s", " is a symbolic link");
}
printf("\n");
entry = readdir(dir);
}
return(0);
}
From path_resolution(7):
Length limit
There is a maximum length for pathnames. If the pathname (or some intermediate pathname obtained while resolving symbolic links) is too long, an ENAMETOOLONG error
is returned ("File name too long").
I think you should employ similar behaviour: check for too long pathnames.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/param.h>
/* Short & sweet recursive directory scan, finds regular files only.
Good starting point, should work on Linux OS.
Pass the root path, and returns number of dirs and number of files
found.
*/
char *tree_scan( const char *path, int *ndirs, int *nfiles){
DIR *dir;
struct dirent *entry;
char spath[MAXPATHLEN] = "";
if( !(dir = opendir( path))){ perror("opendir"); exit(1);}
for( entry = readdir( dir); entry; entry = readdir( dir)){
sprintf( spath, "%s/%s", path, entry->d_name);
if( entry->d_type == DT_REG){ (*nfiles)++; printf( "%s\n", spath);}
if( entry->d_type == DT_DIR &&
(strcmp( ".", entry->d_name)) &&
(strcmp( "..", entry->d_name))){
(*ndirs)++; tree_scan( spath, ndirs, nfiles);
}
}
closedir( dir);
return(0);
}
/* Call it like so */
int i = 0, l = 0;
tree_scan( "/path", &i, &l);
printf("Scanned %d directories, %d files.\n", i, l);
I don't have a *nix terminal handy, but you could always take a look at the source for ls.c and see how it's done.
The source as part of the gnu core utils can be found here.
I created a ls clone a few years ago in school, and I think I got around it by watching the pathname size as ulidtko mentioned.

Resources