Imitating ls in ansi C - c

I have a code to mimic ls -la in ansi C, but when I change the directory from . (current directory) to any other it keep saying No such file or directory, any ideas why?
code:
DIR * mydir;
struct dirent * mydirent;
struct stat st;
char outstr[100];
struct tm *tmp;
mydir = opendir("..");
while ((mydirent=readdir(mydir))!=NULL)
if ( stat(mydirent->d_name,&st) != -1 ) {
tmp = localtime(&st.st_mtime);
if (tmp == NULL)
perror("localtime ERROR: ");
else {
strftime(outstr, sizeof(outstr), "%d/%m/%Y", tmp);
printf("%o\t%d\t%d\t%d\t%d\t%s\t%s\n",
st.st_mode, st.st_nlink, st.st_uid, st.st_gid,
st.st_size, outstr, mydirent->d_name);
}
}
else
perror("stat ERROR: ");
closedir(mydir);

You need to concatenate the directory path and the file name.
stat(mydirent->d_name,&st) /* d_name is just the name, not the full path. */
Use s(n)printf or something like that:
sprintf(fullpath, "%s/%s", dirpath, mydirent->d_name);
stat(fullpath, &st);

The problem is, as already stated by #cnicutar, that stat wants the filename in the form dir/file. The problems are:
The / may not work on every operating system.
You need to allocate memory for the composed string, do overflow checks ...
If you don't insist on ANSI and can live with POSIX instead, then you can try fstatat and dirfd:
int dirfd(DIR *dirp); // convert DIR * from opendir() to dirhandle
int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags);
With fstatat the pathname is relative to the directory handle and you can point pathname directly to struct dirent.d_name.

Related

Get absolute path from dentry_path_raw

I try to get the target of symlink dentries in a kernel module, i.e. the dentry on which my dentry points.
I use this approach:
int print_dentry(struct dentry *d) {
struct path p;
char *buffer, *path_name;
int ret;
buffer = (char *)__get_free_page(GFP_KERNEL);
if (!buffer)
return -ENOMEM;
path_name = dentry_path_raw(d, buffer, PAGE_SIZE);
if (IS_ERR(path_name))
printk(KERN_ERR "ERR");
if ((ret=kern_path(path_name, LOOKUP_FOLLOW, &p))) {
printk("kern_path returned %d for path_name \"%s\", inode %ld\n", ret, path_name, d->d_inode->i_ino);
return 0;
}
printk_once(KERN_INFO "Path %s -> dentry_uid %ld\n", path_name, p.dentry->d_inode->i_ino);
free_page((unsigned long)buffer);
return 0;
}
However, dentry_path_raw doesn't return the absolute path, but a path relative to the vfsmount.
Hence I get errors like this when there is a vfsmount e.g.
kern_path returned -2 for path_name "/self", inode 4026531841
Which corresponds to /proc/self
ls -ial /proc/self
4026531841 lrwxrwxrwx 1 root root 0 5 déc. 04:28 /proc/self -> 1341
Is there a way to get the absolute path so that I can give it to kern_path()? Or maybe another approach to follow the symlink and get the associated dentry?
I don't think I can use directly d_absolute_path or prepend_path since they take as input a struct path* or a char* and I only have a access to a struct dentry* or a char*
Looking at the source code here, it seems that when you call dentry_path_raw() it actually calls another function that does the following:
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Write full pathname from the root of the filesystem into the buffer.
*/
static char *__dentry_path(const struct dentry *d, struct prepend_buffer *p)
{
const struct dentry *dentry;
struct prepend_buffer b;
int seq = 0;
rcu_read_lock();
restart:
dentry = d;
b = *p;
read_seqbegin_or_lock(&rename_lock, &seq);
while (!IS_ROOT(dentry)) {
const struct dentry *parent = dentry->d_parent;
prefetch(parent);
if (!prepend_name(&b, &dentry->d_name))
break;
dentry = parent;
}
if (!(seq & 1))
rcu_read_unlock();
if (need_seqretry(&rename_lock, seq)) {
seq = 1;
goto restart;
}
done_seqretry(&rename_lock, seq);
if (b.len == p->len)
prepend_char(&b, '/');
return extract_string(&b);
}
And everything seems to be in the description of this function. It writes the full path into the buffer, not in its return value, which share it's return with dentry_path_raw().
So, you should use the value pushed into your buffer instead of file_name, which is just the name returned by the function dentry_path_raw().
Note: dentry_path_raw() doesn't resolve symlinks, if you really want to resolve them you should probably use dentry_path() instead.
All these functions are available since linux v2.6.26/v2.6.38

How to use scandir() in C to list sorted subdirectories recursively

I'm implementing parts of the Linux ls command in C. I want to sort the contents of directories lexicographically, which I've been doing using scandir(). This is easy enough for listing single directories, but I'm having trouble doing it for listing subdirectories recursively. My current code: (results in a segmentation faults once a directory type is reached)
void recursive(char* arg){
int i;
struct dirent **file_list;
int num;
char* next_dir;
num = scandir(arg, &file_list, NULL, alphasort);
for(i = 0; i < num; i++) {
if(file_list[i]->d_type == DT_DIR) {
if(strcmp(".", file_list[i]->d_name) != 0 && strcmp("..", file_list[i]->d_name) != 0) {
// Directories are printed with a colon to distinguish them from files
printf("%s: \n", file_list[i]->d_name);
strcpy(next_dir, arg);
strcat(next_dir, "/");
strcat(next_dir, file_list[i]->d_name);
printf("\n");
recursive(next_dir);
}
} else {
if(strcmp(".", file_list[i]->d_name) != 0 && strcmp("..", file_list[i]->d_name) != 0) {
printf("%s \n", file_list[i]->d_name);
}
}
}
}
int main(void) {
recursive(".");
return 0;
}
There are two recommended methods for traversing entire filesystem trees in Linux and other POSIXy systems:
nftw(): man 3 nftw
Given an initial path, a callback function, the maximum number of descriptors to use, and a set of flags, nftw() will call the callback function once for every filesystem object in the subtree. The order in which entries in the same directory is called is not specified, however.
This is the POSIX.1 (IEEE 1003) function.
fts_open()/fts_read()/fts_children()/fts_close(): man 3 fts
The fts interface provides a way to traverse filesystem hierarchies. The fts_children() provides a linked list of filesystem entries sorted by the comparison function specified in the fts_open() call. It is rather similar to how scandir() returns an array of filesystem entries, except that the two use very different structures to describe each filesystem entry.
Prior to glibc 2.23 (released in 2016), the Linux (glibc) fts implementation had bugs when using 64-bit file sizes (so on x86-64, or when compiling with -D_FILE_OFFSET_BITS=64).
These are BSD functions (FreeBSD/OpenBSD/macOS), but are available in Linux also.
Finally, there is also the atfile version of scandir(), scandirat(), that returns the filtered and sorted filesystem entries from a specific directory, but in addition to the pathname, it takes a file descriptor to the relative root directory to be used as a parameter. (If AT_FDCWD is used instead of a file descriptor, then scandirat() behaves like scandir().)
The simplest option here is to use nftw(), store all walked paths, and finally sort the paths. For example, walk.c:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <locale.h>
#include <ftw.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
struct entry {
/* Insert additional properties like 'off_t size' here. */
char *name; /* Always points to name part of pathname */
char pathname[]; /* Full path and name */
};
struct listing {
size_t max; /* Number of entries allocated for */
size_t num; /* Number of entries in the array */
struct entry **ent; /* Array of pointers, one per entry */
};
#define STRUCT_LISTING_INITIALIZER { 0, 0, NULL }
/* Locale-aware sort for arrays of struct entry pointers.
*/
static int entrysort(const void *ptr1, const void *ptr2)
{
const struct entry *ent1 = *(const struct entry **)ptr1;
const struct entry *ent2 = *(const struct entry **)ptr2;
return strcoll(ent1->pathname, ent2->pathname);
}
/* Global variable used by nftw_add() to add to the listing */
static struct listing *nftw_listing = NULL;
static int nftw_add(const char *pathname, const struct stat *info, int typeflag, struct FTW *ftwbuf)
{
const char *name = pathname + ftwbuf->base;
/* These generate no code, just silences the warnings about unused parameters. */
(void)info;
(void)typeflag;
/* Ignore "." and "..". */
if (name[0] == '.' && !name[1])
return 0;
if (name[0] == '.' && name[1] == '.' && !name[2])
return 0;
/* Make sure there is room for at least one more entry in the listing. */
if (nftw_listing->num >= nftw_listing->max) {
const size_t new_max = nftw_listing->num + 1000;
struct entry **new_ent;
new_ent = realloc(nftw_listing->ent, new_max * sizeof (struct entry *));
if (!new_ent)
return -ENOMEM;
nftw_listing->max = new_max;
nftw_listing->ent = new_ent;
}
const size_t pathnamelen = strlen(pathname);
struct entry *ent;
/* Allocate memory for this entry.
Remember to account for the name, and the end-of-string terminator, '\0', at end of name. */
ent = malloc(sizeof (struct entry) + pathnamelen + 1);
if (!ent)
return -ENOMEM;
/* Copy other filesystem entry properties to ent here; say 'ent->size = info->st_size;'. */
/* Copy pathname, including the end-of-string terminator, '\0'. */
memcpy(ent->pathname, pathname, pathnamelen + 1);
/* The name pointer is always to within the pathname. */
ent->name = ent->pathname + ftwbuf->base;
/* Append. */
nftw_listing->ent[nftw_listing->num++] = ent;
return 0;
}
/* Scan directory tree starting at path, adding the entries to struct listing.
Note: the listing must already have been properly initialized!
Returns 0 if success, nonzero if error; -1 if errno is set to indicate error.
*/
int scan_tree_sorted(struct listing *list, const char *path)
{
if (!list) {
errno = EINVAL;
return -1;
}
if (!path || !*path) {
errno = ENOENT;
return -1;
}
nftw_listing = list;
int result = nftw(path, nftw_add, 64, FTW_DEPTH);
nftw_listing = NULL;
if (result < 0) {
errno = -result;
return -1;
} else
if (result > 0) {
errno = 0;
return result;
}
if (list->num > 2)
qsort(list->ent, list->num, sizeof list->ent[0], entrysort);
return 0;
}
int main(int argc, char *argv[])
{
struct listing list = STRUCT_LISTING_INITIALIZER;
setlocale(LC_ALL, "");
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", arg0);
fprintf(stderr, " %s .\n", arg0);
fprintf(stderr, " %s TREE [ TREE ... ]\n", arg0);
fprintf(stderr, "\n");
fprintf(stderr, "This program lists all files and directories starting at TREE,\n");
fprintf(stderr, "in sorted order.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
for (int arg = 1; arg < argc; arg++) {
if (scan_tree_sorted(&list, argv[arg])) {
fprintf(stderr, "%s: Error scanning directory tree: %s.\n", argv[arg], strerror(errno));
return EXIT_FAILURE;
}
}
printf("Found %zu entries:\n", list.num);
for (size_t i = 0; i < list.num; i++)
printf("\t%s\t(%s)\n", list.ent[i]->pathname, list.ent[i]->name);
return EXIT_SUCCESS;
}
Compile using gcc -Wall -Wextra -O2 walk.c -o walk, and run using e.g. ./walk ...
The scan_tree_sorted() function calls nftw() for the directory specified, updating the global variable nftw_listing so that the nftw_add() callback function can add each new directory entry to it. If the listing contains more that one entry afterwards, it is sorted using qsort() and a locale-aware comparison function (based on strcoll()).
nftw_add() skips . and .., and adds every other pathname to the listing structure nftw_listing. It automatically grows the array as needed in linear fashion; the new_max = nftw_listing->num + 1000; means we allocate in units of a thousand (pointers).
The scan_tree_sorted() can be called multiple times with the same listing as the target, if one wants to list disjoint subtrees in one listing. Note, however, that it does not check for duplicates, although those could easily be filtered out after the qsort.

Confustion with realpath() buffer [duplicate]

This question already has an answer here:
Unexpected behavior in printing a char array in C
(1 answer)
Closed 8 years ago.
Here's my function, which is looking for regular files in given directory, and then storing full path to them in a list.
static my_func(const char *path, Files **list) //list - storage for file names
{
DIR *d;
struct dirent *dir;
char buf[PATH_MAX + 1];
d = opendir(path);
if (d) {
while ((dir = readdir(d)) != NULL) {
if ((DT_REG == dir->d_type)) {
realpath(dir->d_name, buf);
List_push(list, buf);
printf("%s\n", dir->d_name);
// memset(buf, 0, PATH_MAX + 1);
}
}
}
closedir(d);
return 0;
}
...
...
int main()
{
// list creation
// my_func call
...
List_print(...)
}
Expected output:
FILE_1.txt
FILE_2.txt
FILE_3.txt
FILE_4.txt
FILE_5.txt
/home/user/c/FILE_1.txt
/home/user/c/FILE_2.txt
/home/user/c/FILE_3.txt
/home/user/c/FILE_4.txt
/home/user/c/FILE_5.txt
The current output:
FILE_1.txt
FILE_2.txt
FILE_3.txt
FILE_4.txt
FILE_5.txt
/home/user/c/FILE_1.txt
/home/user/c/FILE_1.txt
/home/user/c/FILE_1.txt
/home/user/c/FILE_1.txt
/home/user/c/FILE_1.txt
Can it be related with my linked list implementation? It works fine, because I tested it with:
List_push(list, dir->d_name)
and got expected results. This is implementation of List_push (Files is just simply struct with char * and pointer to the next element):
void List_push(Files **head, char *x)
{
Files *new;
new = malloc(sizeof(Files));
if (NULL != new) {
new->next = *head;
new->text = x;
*head = new;
} else {
printf("malloc error");
}
}
Also, as you can see, I was trying to clear buf with memset, but without success - the output is:
FILE_1.txt
FILE_2.txt
FILE_3.txt
FILE_4.txt
FILE_5.txt
[console]$
Yes, blank space seems to be filed with something (or these are just '\n' symbols from List_print), so list is not empty.
What is wrong here?
In List_push(list, buf); you store a pointer to buf in the list. You do this for each file, so you end up with several pointers to the same buf in the list. When printing the list items it then will show the (current) contents of buf.
To avoid this you need to create a copy of buf and store that, so that the stored data won't be overwritten when you reuse buf for the next file.

Getting the last created (modified) file

I have a program that created files and fill them with data , it doesn't matter what exactly , it named them based of the actual time (YYYYMMDDHHMMSS). Now I want to always open the last created file , meaning the recent one, is this possible in C ? if yeah, I'll be greatful for any hint ?
UPDATE
I need to make it clear.
say I have a string that I want to use like :
..............
FILE* input = NULL;
char* fileName = NULL;
...............// in some getting the name of the last modified file
and than open it
inp = fopen(fileName,"rb");
The ftw() function may be useful here. It will call a function (which you need to write) for every file and directory in the directory. Your function will decide if its argument is newer than whatever arguments it's seen before, and if so, records it in a global variable.
One caveat is that ftw will look at every file in every subdirectory. That may not be what you want. But if that's OK, using ftw will make your code more concise, because it does the directory scanning and statting for you. Here's an example I wrote that will find the most recently modified file in the current directory:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <ftw.h>
char newest[PATH_MAX];
time_t mtime = 0;
int checkifnewer(const char *path, const struct stat *sb, int typeflag)
{
if (typeflag == FTW_F && sb->st_mtime > mtime) {
mtime = sb->st_mtime;
strncpy(newest, path, PATH_MAX);
}
return 0;
}
main()
{
ftw(".", checkifnewer, 1);
printf("%s\n", newest);
}
You don't have to extract file's timestamp to check which file was lastly created (which is expensive), instead use inotify(), see here. inotify() will tell you whenever a new file is created.
This can be accomplished by using stat function.
Using stat() function you can get the file information. stat structure contains-
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
Check the each file time info with other files, which file have the highest time details, store that into another stat structure and do comparing for all the files. and finally open the file what you have stored in stat structure!
Scan the directory and remember the file with largest timestamp.
If the filename follows YYYYMMDDHHMMSS format, then it is enough to find a file with the "greatest" filename, something like this:
char buffer[MAX_LEN];
void recentByName(const char* path, char* recent){
DIR* dir = opendir(path);
struct dirent* entry;
recent[0] = '\0';
while (NULL != (entry = readdir(dir))) {
if (!isExceptionalDir(entry->d_name)) {
if (strncmp(recent, entry->d_name, MAX_LEN)<0) {
strncpy(recent, entry->d_name, MAX_LEN);
}
}
}
closedir(dir);
}
However 3.stat() might be more reliable at giving the actual modification time, then try this:
void recentByModification(const char* path, char* recent){
struct dirent* entry;
time_t recenttime = 0;
struct stat statbuf;
DIR* dir = opendir(path);
while (NULL != (entry = readdir(dir))) {
if (!isExceptionalDir(entry->d_name)) {
sprintf(buffer, "%s/%s", path, entry->d_name);
stat(buffer, &statbuf);
if (statbuf.st_mtime > recenttime) {
strncpy(recent, entry->d_name, MAX_LEN);
recenttime = statbuf.st_mtime;
}
}
}
closedir(dir);
}
Note that the check "isExceptional()" omits the "." and ".." entries which seem to always be the newest.
Complete program listing:
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#define MAX_LEN 1024
int isExceptionalDir(const char* name){
if (name==NULL || name[0]=='\0') return 1;
else if (name[0]=='.') {
if (name[1]=='\0') return 1;
else if (name[1]=='.' && name[2]=='\0') return 1;
}
return 0;
}
char buffer[MAX_LEN];
void recentByModification(const char* path, char* recent){
struct dirent* entry;
time_t recenttime = 0;
struct stat statbuf;
DIR* dir = opendir(path);
while (NULL != (entry = readdir(dir))) {
if (!isExceptionalDir(entry->d_name)) {
sprintf(buffer, "%s/%s", path, entry->d_name);
stat(buffer, &statbuf);
if (statbuf.st_mtime > recenttime) {
strncpy(recent, entry->d_name, MAX_LEN);
recenttime = statbuf.st_mtime;
}
}
}
closedir(dir);
}
void recentByName(const char* path, char* recent){
DIR* dir = opendir(path);
struct dirent* entry;
recent[0] = '\0';
while (NULL != (entry = readdir(dir))) {
if (!isExceptionalDir(entry->d_name)) {
if (strncmp(recent, entry->d_name, MAX_LEN)<0) {
strncpy(recent, entry->d_name, MAX_LEN);
}
}
}
closedir(dir);
}
char recent[MAX_LEN];
int main(int argc, const char* args[])
{
if (argc < 2) {
printf("Usage: %s path\n", args[0]);
return 1;
}
for (int i=1; i<argc; i++) {
recentByModification(args[i], recent);
printf("%s\n", recent);
}
}

C, total from unix ls function

I have to make ls -l function. My problem is to find the total value from ls -l. Here is how I do it.
if (l_option) {
struct stat s;
stat(dir_name, &s);
printf("total %jd\n", (intmax_t)s.st_size/512);
}
I believe that my solution is right by definition, which is:
"For each directory that is listed, preface the files with a line
`total BLOCKS', where BLOCKS is the total disk allocation for all
files in that directory. The block size currently defaults to 1024
bytes" (info ls) But my function differs from the real ls.
For example:
>ls -l
>total 60
...and in the same directory:
>./ls -l
>total 8
And if I write:
>stat .
>File: `.'
>Size: 4096 Blocks: 8 IO Block: 4096 directory
>...
I fixed it:
n = scandir(path, &namelist, filter, alphasort);
if (l_option) { // flag if -l is given
while (i < n) {
char* temp = (char *) malloc(sizeof(path)+sizeof(namelist[i]->d_name));
strcpy(temp, path); //copy path to temp
stat(strcat(temp, namelist[i]->d_name), &s); // we pass path to + name of file
total += s.st_blocks;
free(temp);
free(namelist[i++]); // optimization rules!
}
free(namelist);
printf("total %d\n", total/2);
}
So basicly, I make new char array containing the dir_name + the name of file, then I get stat structure and use it to find the total.
You should use opendir/readdir/closedir.
#include <dirent.h>
#include <stdio.h>
int main(void)
{
DIR *d;
struct dirent *dir;
d = opendir(".");
if (d)
{
while ((dir = readdir(d)) != NULL)
{
count++;
}
closedir(d);
}
printf("total %jd\n",count);
return(0);
}

Resources