My program check if two files (with a different path / name) match or not. The hard and symbolic links between the files are followed to determine it.
How can I modify the program to see what happens if the files I want to compare are device files? Or directories? Both seem valid uses. Also checking the device
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
void printUsage()
{
printf("Use: ./leg <name_file1> <name_file2>.\n");
}
int createStat(const char *file, struct stat *fileStat)
{
if (stat(file, fileStat) < 0)
{
fprintf(stderr,"Error! File %s does not exist.\n", file);
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
if (argc < 3)
{
printf("Insufficient number of parameters.\n");
printUsage();
return -1;
}
struct stat fileStat1;
struct stat fileStat2;
if (createStat(argv[1], &fileStat1) == 0)
{
return -1;
}
if (createStat(argv[2], &fileStat2) == 0)
{
return -1;
}
if (S_ISREG(fileStat1.st_mode) && S_ISREG(fileStat2.st_mode)) {
if ((fileStat1.st_dev == fileStat2.st_dev) && (fileStat1.st_ino == fileStat2.st_ino)) {
printf("Files '%s' and '%s' coincide.\n", argv[1], argv[2]);
} else
printf("Files '%s' and '%s' do not match.\n", argv[1], argv[2]);
} else
fprintf(stderr,"'%s' and '%s' there are no files.\n", argv[1], argv[2]);
return 0;
}
For regular files and directories, comparing the st_dev and st_ino fields is sufficient.
For paths representing character or block devices, comparing the st_dev and st_ino fields will tell you if they are the same file, ie: different paths to the same directory entry, including symbolic link indirections. Comparing the st_rdev fields will tell if they represent the same device, which is also useful but a different thing.
Also note that fprintf(stderr,"Error! File %s does not exist.\n", file); may produce a misleading message: access failure may be caused by other reasons. It is simple and efficient to produce the correct message this way:
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int createStat(const char *file, struct stat *fileStat) {
if (stat(file, fileStat) < 0) {
fprintf(stderr, "Cannot stat file %s: %s\n", file, strerror(errno));
return 0;
}
return 1;
}
Related
I am using Raspbian and this is a part of an exercise for my advanced C programming in Linux environments. I have to open a directory and list its content, checking if entries are subdirectories or files.
Here is what I did:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
//declarations
int main(int argc, char ** argv){
if(argc != 2){
fprintf(stderr, "Wrong number of arguments\n");
exit(EXIT_FAILURE);
}
DIR *dir;
dir = opendir(argv[1]);
if(!dir){
fprintf(stderr, "Error: unable to open directory\n");
exit(EXIT_FAILURE);
}
struct dirent * entry;
struct stat filestat;
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
stat(entry->d_name, &filestat);
//printf("S_ISREG(%s) value is:%d\n", entry->d_name, S_ISREG(filestat.st_mode));
//printf("S_ISDIR(%s) value is:%d\n", entry->d_name, S_ISDIR(filestat.st_mode));
if(S_ISDIR(filestat.st_mode) == 0){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
closedir(dir);
printf("END\n");
return EXIT_SUCCESS;
}
Now, it works if I am working with relative paths. If I am working on absolute paths, S_ISREG and S_ISDIR macros always return 0. What am I doing wrong?
It seems by doing stat(entry -> d_name, &filestat) you're giving as directory path just the name of the folder you're looking at, which should work if it belongs to the directory from which the file is being executed but might not work with absolute paths
Also, you are not checking if stat is producing any error, which might also be an issue here - or at least provide some insight to the real problem
you might want to try concatenating your path to entry->d_name and use that when you call stat instead, e.g.
char fullPath[MAX_LEN];
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
sprintf(fullPath, "%s/%s", argv[1], entry->dname);
if(stat(fullPath, &filestat) < 0){
//handle error
}
if(S_ISDIR(filestat.st_mode) == 0){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
where MAX_LEN is some predefined constant
Use
if (fstatat(dirfd(dir), entry->d_name, &filestat, AT_SYMLINK_NOFOLLOW) == -1) {
fprintf(stderr, "Cannot stat %s/%s: %s.\n",
argv[1], entry->d_name, strerror(errno));
/* Optionally, exit(EXIT_FAILURE); */
} else {
/* Directory entry information in filestat */
}
As explained in the man 3 readdir manual page, if entry->d_type == DT_UNKNOWN, you need to do the above fstatat() to obtain the type.
This is because some filesystems do not provide the information in the directory entries, and return d_type == DT_UNKNOWN for all directory entries. All applications are required to handle DT_UNKNOWN correctly.
Note that you need
#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
at the beginning of your file, to tell the C library to expose the functions (fstatat() and strerror()).
However, opendir()/readdir()/closedir() is the wrong way to check directory contents, because the contents may change during your traversal. You would be better off using [scandir()](https://man7.org/linux/man-pages/man3/scandir.3.html9, glob() (if searching for files matching a pattern), or nftw() (if traversing entire trees) which are all POSIX.1 standard functions provided by POSIXy C libraries (basically all except Windows).
An exercise claiming to show how to list files in a directory and using opendir()/readdir()/closedir() is at best misleading, because it leaves all the complexity – handling changes to the directory contents, like renamed files – to you the writer, without telling you such work is necessary! Yes, two decades ago filesystems were typically so simple that opendir()/readdir()/closedir() worked without issues, but that is not the case anymore. The other functions listed above are supposed to handle such cases gracefully, so you the programmer shouldn't need to do it yourself.
Here is one way to properly implement the directory scan in Linux:
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <ftw.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
static int report_entry(const char *path,
const struct stat *info,
int typeflag,
struct FTW *ftwbuf)
{
/* If you need it, entry name is (path + ftwbuf->base). */
switch (typeflag) {
case FTW_D: /* Directory */
printf("%s is a directory\n", path);
/* If this is the path given to nftw(), recurse into it: */
if (ftwbuf->level == 0)
return FTW_CONTINUE;
/* Do not recurse into any other directories. */
return FTW_SKIP_SUBTREE;
case FTW_DNR: /* Directory, but no access to contents */
printf("%s is a directory but there is no read access\n", path);
return FTW_CONTINUE;
case FTW_DP: /* Directory that was already mentioned */
return FTW_CONTINUE;
case FTW_F: /* Regular file */
printf("%s is a file\n", path);
return FTW_CONTINUE;
case FTW_SL: /* Symlink, and FTW_PHYS was set for nftw() */
printf("%s is a symbolic link\n", path);
return FTW_CONTINUE;
case FTW_SLN: /* Symlink to a nonexistent file */
/* This will NOT be reported if FTW_PHYS is set in the nftw() call. */
printf("%s is a symbolic link to a nonexistent file\n", path);
return FTW_CONTINUE;
case FTW_NS: /* stat() failed */
printf("%s is unknown, and cannot be stat()'d\n", path);
return FTW_CONTINUE;
default: /* Should never occur */
printf("%s is of unknown an unexpected type (%d)\n", path, typeflag);
return FTW_STOP;
}
}
/* Number of file descriptors nftw() is allowed to use.
It mostly matters to applications that use many file descriptors,
like service daemons (servers). If nftw() runs out, it slows down,
but does not fail. In Linux, processes usually have at least
a thousand file descriptors available, so 64 is very conservative. */
#ifndef NFTW_FDS
#define NFTW_FDS 64
#endif
int report_directory(const char *path)
{
int result;
if (!path || !*path) {
/* No path specified; invalid parameter */
errno = EINVAL;
return -1;
}
result = nftw(path, report_entry, NFTW_FDS, FTW_ACTIONRETVAL | FTW_PHYS);
if (result == -1) {
/* nftw() error, errno set. */
return -1;
} else
if (result == FTW_STOP) {
/* report_entry() returned FTW_STOP. We assume error was already printed. */
errno = 0;
return -1;
} else
if (result != 0) {
/* Unexpected error */
errno = EIO;
return -1;
}
/* Done successfully. */
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
const char *thisname = (argc >= 1 && argv && argv[0]) ? argv[0] : "(this)";
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", thisname);
fprintf(stderr, " %s DIRECTORY-OR-FILE\n", thisname);
fprintf(stderr, "\n");
return (argc == 2) ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (report_directory(argv[1]) != 0) {
if (errno) {
fprintf(stderr, "Error: %s: %s.\n", argv[1], strerror(errno));
}
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Try this:
while((entry = readdir(dir))){
if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
if(entry->d_type == DT_DIR){
printf("Dir: %s\n", entry->d_name);
} else {
printf("File: %s\n", entry->d_name);
}
}
}
It might not work on your platform though, read the readdir documentation for more information.
I created a C program which would create a directory and file.
I have tried to debug the error, but it didn't work
#include <dirent.h>
#include <errno.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
create_dir(char* outputdir,char* str_outpath,char* value){
DIR* dir = opendir(outputdir);
FILE *f;
if (dir) {
/* Directory exists. */
closedir(dir);
} else if (ENOENT == errno) {
/* Directory does not exist. */
mkdir(outputdir, 0700);
closedir(dir);
printf("Successfully created the directory %s ", outputdir);
} else {
printf("Creation of the directory %s failed",outputdir);
/* opendir() failed for some other reason. */
}
f = fopen(str_outpath, "a");
fprintf(f,"%s",value);
fclose(f);
}
I want it to create a file and a directory successfully
As others have mentioned. You do not have a main function.
Also your create_dir function is missing a type. I'll assume it's void since you are not returning anything. This should compile.
#include <dirent.h>
#include <errno.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
void create_dir(char* outputdir,char* str_outpath,char* value){
DIR* dir = opendir(outputdir);
FILE *f;
if (dir) {
/* Directory exists. */
closedir(dir);
} else if (ENOENT == errno) {
/* Directory does not exist. */
mkdir(outputdir, 0700);
closedir(dir);
printf("Successfully created the directory %s ", outputdir);
} else {
printf("Creation of the directory %s failed",outputdir);
/* opendir() failed for some other reason. */
}
f = fopen(str_outpath, "a");
fprintf(f,"%s",value);
fclose(f);
}
int main(){
char directory[] = "/users/me/documents/testdir";
char filepath[] = "testfile";
char data[] = "hello world";
create_dir(directory,filepath,data);
return 0;
}
I did not execute the code to check whether it works. I merely copied and pasted yours and called the function.
In C under most cases you need to have a main function. So in order to run your code you'll need to have something like this (assuming that you want to pass in the parameters from the command-line) underneath that function:
int main(int argc, char *argv[]) {
if (argc < 4) {
printf("Proper Usage is ./program otputdir str_outpath value\n");
return -1;
}
char *outputdir = argv[1];
char *str_outpath = argv[2];
char *value = argv[3];
create_dir(outputdir, str_outpath, value);
return 0;
}
EDIT: fixed an issue with not checking argc
I have a linked list with an fd and a string I used to open this file in each entry. I want to open and add files to this list only if this file is not already opened, because I open and parse this files and do not want to do it twice. My idea was to compare the filename with every single name in this list, but my program do it multiple times and one file in Linux can have multiple names (soft/hard links). I think it should not be so complicated, because its easy for the OS to check, whether I already used a inode or not, r?
I already tried to open the same file with and without flock, but I always get a new fd.
When you successfully open a file use fstat on the file. Check to see if the st_ino and st_dev of the struct stat filed in by fstat have already been recorded in your linked list. If so then close the file descriptor and move on to the next file. Otherwise add the file descriptor, the file name and st_ino and st_dev values to the list.
You can instead use stat to check before opening the file, but using fstat after will be slightly faster if the usual case is that file hasn't already been opened.
In situations like this, it's often useful to consider your data structures. Change to a data structure which does not allow duplicates, such as a hash table.
Maintain a set of which data you've seen before. I've used a hash table for this set. As per #RossRidge's answer, use the inode and device as the key. This allows duplicates to be discovered in O(1) time.
Here is an example implementation.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
static int get_fd(GHashTable *fds, const char *filename, int mode) {
int fd;
struct stat stat;
int keysize = 33;
char key[keysize]; /* Two 64 bit numbers as hex and a separator */
/* Resolve any symlinks */
char *real_filename = realpath(filename, NULL);
if( real_filename == NULL ) {
printf("%s could not be resolved.\n", filename);
return -1;
}
/* Open and stat */
fd = open( real_filename, mode );
if( fd < 0 ) {
printf("Could not open %s: %s.\n", real_filename, strerror(errno));
return -1;
}
if( fstat(fd, &stat) != 0 ) {
printf("Could not stat %s: %s.\n", real_filename, strerror(errno));
return -1;
}
/* Make a key for tracking which data we've processed.
This uses both the inode and the device it's on.
It could be done more efficiently as a bit field.
*/
snprintf(key, keysize, "%lx|%lx", (long int)stat.st_ino, (long int)stat.st_dev);
/* See if we've already processed that */
if( g_hash_table_contains(fds, key) ) {
return 0;
}
else {
/* Note that we've processed it */
g_hash_table_add(fds, key);
return fd;
}
}
int main(int argc, char** argv) {
int mode = O_RDONLY;
int fd;
GHashTable *fds = g_hash_table_new(&g_str_hash, &g_str_equal);
for(int i = 1; i < argc; i++) {
char *filename = argv[i];
fd = get_fd(fds, filename, mode);
if( fd == 0 ) {
printf("%s has already been processed.\n", filename);
}
else if( fd < 0 ) {
printf("%s could not be processed.\n", filename);
}
else {
printf("%s: %d\n", filename, fd);
}
}
}
And here's a sample result.
$ touch one two three
$ ln one one_link
$ ln -s two two_sym
$ ./test one* two* three*
one: 3
one_link has already been processed.
two: 5
two_sym has already been processed.
three: 7
As long as you don't close the successfully and intentionally opened files, you can use nonblocking flock to prevent another lock on the same file:
#include <unistd.h>
#include <sys/file.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <assert.h>
int openAndLock(const char* fn){
int fd = -1;
if(((fd = open(fn, O_RDONLY)) >= 0) && (flock(fd, LOCK_EX|LOCK_NB) == 0)){
fprintf(stderr, "Successfully opened and locked %s\n", fn);
return fd;
}else{
fprintf(stderr, "Failed to open or lock %s\n", fn);
close(fd);
return -1;
}
}
int main(int argc, char** argv){
for(int i=1; i<argc; i++){
openAndLock(argv[i]);
}
return 0;
}
Example:
$ touch foo
$ ln foo bar
$ ./a.out foo foo
Successfully opened and locked foo
Failed to open or lock foo
$ ./a.out foo bar
Successfully opened and locked foo
Failed to open or lock bar
I am writing a simple C program that receives a directory as an argument and displays the files in this directory and also his subdirectories. I wrote a "recursive" function for doing that. But for an unknown reason, my program fails at the stat function. Here is my program :
#define _POSIX_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
void display_directory(char* path){
DIR* directory = opendir(path);
if( directory == NULL){
printf("opendir failure for %s\n", path);
exit(1);
}
struct dirent* dirent;
struct stat stat_info;
while((dirent = readdir(directory)) != NULL){
printf("[%s]\n", dirent->d_name);
if(stat(dirent -> d_name, &stat_info) == -1){
printf("readdir error for %s\n", dirent->d_name);
exit(1);
}
if(S_ISREG(stat_info.st_mode)){
printf("File: %s \n", dirent -> d_name);
}
if(S_ISDIR(stat_info.st_mode)){
if(strncmp(dirent->d_name, "..",2)){
printf("Directory : %s\n", dirent->d_name);
display_directory(dirent->d_name);
}
}
}
closedir(directory);
}
int main(int argc, char* argv[]){
char* path;
if(argc > 1){
path = argv[1];
} else {
path = ".";
}
display_directory(path);
return EXIT_SUCCESS;
}
For instance, if in my directory A, I have a1, a2, a3 and .., it reads first the .. directory, and when it reads the directory a1, the stat function fails.
Can someone tells me what is not correct with my code.
[EDIT] I included <errno.h> as many of you suggest and after running the program, I have the error Too many open files.
#define _POSIX_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
void display_directory(char* path){
DIR* directory = opendir(path);
if( directory == NULL){
printf("opendir failure for %s --> %s\n", path, strerror(errno));
exit(1);
}
struct dirent* dirent;
struct stat stat_info;
while((dirent = readdir(directory)) != NULL){
printf("[%s]\n", dirent->d_name);
if(stat(dirent->d_name, &stat_info)){
printf("readdir error for %s ---> %s\n", dirent->d_name, strerror(errno));
continue;
}
if(S_ISREG(stat_info.st_mode)){
printf("Fichier : %s \n", dirent->d_name);
}
if(S_ISDIR(stat_info.st_mode)){
if(strncmp(dirent->d_name, "..",2)){
printf("Directory : %s\n", dirent->d_name);
display_directory(dirent->d_name);
}
}
}
closedir(directory);
}
int main(int argc, char* argv[]){
char* path;
if (argc > 2) {
fprintf(stderr, "Usage: %s [directory]\n", argv[0]);
exit(1);
}
path = argv[1];
display_directory(path);
return EXIT_SUCCESS;
}
The output of the program :
[..]
[mykill.c]
readdir error for mykill.c ---> No such file or directory
[.]
Directory : .
[..]
[.]
Directory : .
[..]
[.]
Directory : .
[..]
...
...
Directory : .
opendir failure for . --> Too many open files
mykill.c is a file in the directory that was passed as an argument.
I have a pretty good idea what's wrong, but I want to tell you how to debug this for yourself, first. Change this code ...
if(stat(dirent -> d_name, &stat_info) == -1){
printf("readdir error for %s\n", dirent->d_name);
exit(1);
}
... to read instead ...
if (stat(dirent->d_name, &stat_info)) {
printf("%s: %s\n", dirent->d_name, strerror(errno));
continue;
}
You will need to add to the include list
#include <errno.h>
Run the program again. If you don't see from the output what the problem is, then edit the COMPLETE, UNEDITED output into your question and we'll go from there.
if(S_ISDIR(stat_info.st_mode)){
if( !strcmp(dirent->d_name, ".")) continue;
if( !strcmp(dirent->d_name, "..")) continue;
printf("Directory : %s\n", dirent->d_name);
display_directory(dirent->d_name);
}
You are making stat only with filename (without full path), add full path to the filename or change working directory before calling stat.
Use nftw().
How can i create a temporary folder in /tmp directory.
Try the mkdtemp function.
char *tmpdir;
strcpy (template, "/tmp/myprog.XXXXXX");
tmpdir = mkdtemp (template);
if (!tmpdir) {
// Error out here
}
printf ("Temporary directory created : %s", tmpdir);
Since I can't change/improve other's answers yet, I'm writing one myself.
I'd use stat and mkdir. For example:
#include <errno.h> // for errno
#include <stdio.h> // for printf
#include <stdlib.h> // for EXIT_*
#include <string.h> // for strerror
#include <sys/stat.h> // for stat and mkdir
int main() {
const char *mydir = "/tmp/mydir";
struct stat st;
if (stat(mydir, &st) == 0) {
printf("%s already exists\n", mydir);
return EXIT_SUCCESS;
}
if (mkdir(mydir, S_IRWXU|S_IRWXG) != 0) {
printf("Error creating directory: %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("%s successfully created\n", mydir);
return EXIT_SUCCESS;
}