I have for homework to write a C program, which is acting like the Linux "ls -al" command. I know that there are a lot of example programs over the internet, which are doing the thing that I need, but I have a specific problem, to which I can't find a solution. I also want to mention, that I am new to C programming. Here is my code :
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <pwd.h>
int list_dir(const char *dirname) {
struct dirent* current_directory;
struct stat my_stat;
struct tm lt;
struct passwd *pwd; // For User-ID
DIR* directory = opendir(dirname);
if(directory == NULL) {
printf("list_dir : %s : %s \n", dirname, strerror(errno));
return 0;
}
printf("Directory : %s\n", dirname);
printf("\n");
while( (current_directory = readdir(directory) ) ) {
stat(current_directory->d_name, &my_stat);
if ( (stat(current_directory->d_name, &my_stat) ) == 0 ) {
pwd = getpwuid(my_stat.st_uid); // Get User-ID
}
// Last Modified
time_t t = my_stat.st_mtime;
localtime_r(&t, <);
char timebuf[80];
strftime(timebuf, sizeof(timebuf), "%c", <);
if (pwd != 0) {
printf("%s \t %ld \t %s \t %s", pwd->pw_name, (long)my_stat.st_size, timebuf, current_directory->d_name);
printf("\n");
} else {
printf("%d \t %ld \t %s \t %s", my_stat.st_uid, (long)my_stat.st_size, timebuf, current_directory->d_name);
printf("\n");
}
}
closedir(directory);
return 0;
}
int main(int argc, char* argv[]) {
if ( argc == 1 ) {
return list_dir ( "." );
} else {
int ret = 0;
for (int i = 1; i < argc; i += 1 ) {
if ( list_dir ( argv[i] ) != 0 ) {
ret = 1;
}
}
return ret;
}
}
The program has to display the same things (without the permissions) as "ls -al". So far so good, if I compile it with "gcc -std=gnu99 -o list_dir list_dir.c" and execute the program with "./list_dir" I am getting the same result as "ls -al" and it looks like this :
username 1599 Fri May 1 20:43:57 2015 list_dir.c
However if I run the program with something like : "./list_dir /home/username/Downloads/" I am getting this :
32727 0 Sun May 8 07:09:04 4461391 selection_sort.c
As you can see, the program can't get the right information about the Username, the size of the file and the year. Also this information is appearing thanks to the else case of the if(pwd != 0) statement. If I don't have the else case, the program is printing out only the files, for which it can get the correct information. Also if I remove this if statement and also the if statement :
if ( (stat(current_directory->d_name, &my_stat) ) == 0 )
I am getting a segmentation fault.
So my questions are :
1.What am I doing wrong. I know that I am doing something wrong, because as a hint for the homework I have an example run of the program and also a hint that I can use "stat, lstat, readlink, getpwnam, getpwuid, strftime".
2.Is there any way to get the username with stat() and with the User-ID only, or it's only possible with getpwuid?
Here,
if ( (stat(current_directory->d_name, &my_stat) ) == 0 ) {
pwd = getpwuid(my_stat.st_uid); // Get User-ID
}
What happens if stat() fails? pwd would have uninitialized value or incorrect value set from previous iteration.
The reason why stat would fail, current_directory->d_name contains only the file name whereas stat expects a fullpath. So you need to prepend the directory name to the file and pass it to stat(). You are only passing the filename right now.
Something like:
char buf[1024];
errno = 0;
snprintf(buf, sizeof buf, "%s/%s", dirname, current_directory->d_name);
if ( (stat(buf, &my_stat) ) == 0 ) {
...
}
else {
printf("%s: %s\n", buf, strerror(errno));
// exit here or continue to the next entry in the directory
//depending how you wish to handle failure
}
and handle the error in case stat() fails.
You also have one other stat() inside the loop which doesn't do anything.
Related
I want to calculate the size of the directory (path) recursively. In my current code I have a function that identifies if it's a directory or file, if it's a directory it calls the function with the subdirectory (file) and if it's a file it adds to the totalSize variable. However, my current code doesn't return anything meaning that there is an error somewhere.
here is my code -
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
void getsize(const char *path,int* totalSize);
int main()
{
int total = 0;
char path [] = "C:\\Users\\abbas\\Desktop\\Leetcode airplane";
getsize(path,&total);
printf("%d",total);
return total;
}
void getsize(const char *path,int* totalSize)
{
struct dirent *pDirent;
struct stat buf;
struct stat info;
DIR *pDir;
int exists;
char str[100];
pDir = opendir (path);
while ((pDirent = readdir(pDir)) != NULL)
{
stat(pDirent->d_name,&info);
if(S_ISDIR(info.st_mode))
{
strcpy(str,path);
strcat(str,"/");
strcat(str,pDirent->d_name);
getsize(str,totalSize);
}
else
{
strcpy(str,path);
strcat(str,"/");
strcat(str,pDirent->d_name);
exists = stat(str,&buf);
if (exists < 0)
{
continue;
}
else
{
(*totalSize) += buf.st_size;
}
}
}
closedir(pDir);
}
Include string.h.
The arbitrary fixed sizestr[100] is problematic. If you are on Linux include linux/limits.h and use str[PATH_MAX] or even better pathconf(path, _PC_NAME_MAX). In either case you should either ensure the buffer is big enough (using snprintf() for instance), or dynamically allocate the buffer.
You need to exclude . and .. otherwise you end up with an infinite loop (path/../path/..).
stat(pDirent->d_name,&info) fails as you need to stat() path/pDirect->d_name not just pDirect->d_name.
(not fixed) Maybe snprintf(path2, sizeof path2, "%s%s%s", path, PATH_SEP, pDirenv->d_name) instead of strcpy() and strcat()?
Check return values of functions otherwise you are wasting time.
No point of doing two stat() calls on the same path so just use (*totalSize) += buf.st_size;.
(not fixed) On Windows, consider using _stat64() with the address of a struct __stat64 (#AndrewHenle).
I assume you only want the size of files.
(not fixed) It would be more natural if getsize() returned the size instead of using int *totalSize out parameter.
(not fixed) Consider using nftw() (or the older ftw()) to walk the tree.
Note that program now accept path via command line for testing purposes.
#include <dirent.h>
#include <errno.h>
#include <linux/limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
const char PATH_SEP =
#ifdef _WIN32
"\\";
#else
"/";
#endif
void getsize(const char *path,int *totalSize) {
struct dirent *pDirent;
DIR *pDir = opendir (path);
while ((pDirent = readdir(pDir)) != NULL) {
if(
!strcmp(pDirent->d_name, ".") ||
!strcmp(pDirent->d_name, "..")
)
continue;
char path2[PATH_MAX];
strcpy(path2, path);
strcat(path2, PATH_SEP);
strcat(path2, pDirent->d_name);
struct stat info;
if(stat(path2, &info) == -1) {
perror("stat");
return;
}
if(S_ISDIR(info.st_mode))
getsize(path2, totalSize);
else if(S_ISREG(info.st_mode))
(*totalSize) += info.st_size;
}
closedir(pDir);
}
int main(argc, char *argv[]) {
if(argc != 2) {
printf("usage: your_program path\n");
return 1;
}
int total = 0;
getsize(argv[1], &total);
printf("%d\n",total);
}
and example test:
$ mkdir -p 1/2
$ dd if=/dev/zero of=1/file count=123
123+0 records in
123+0 records out
62976 bytes (63 kB, 62 KiB) copied, 0.000336838 s, 187 MB/s
$ dd if=/dev/zero of=1/2/file count=234
234+0 records in
234+0 records out
119808 bytes (120 kB, 117 KiB) copied, 0.0015842 s, 75.6 MB/s
$ echo $((62976 + 119808))
182784
$ ./your_program 1
182784
I think the major error of your code lies in the recursive logic.
To quote pp.183 of The C Programming Language:
Each directory always contains entries for itself, called ".",
and its parent, ".."; these must be skipped, or the program will
loop forever.
Therefore, maybe you can try adding the following if test
at the beginning of the while loop:
while ((pDirent = readdir(pDir)) != NULL)
{
if (strcmp(pDirent->d_name, ".") == 0
|| strcmp(pDirent->d_name, "..") == 0)
continue; /* skip self and parent */
/* ... */
}
Still, there might be other errors, but I think this one is the most significant.
Practice safe coding.
Below risks buffer overflow.
// Risky
strcpy(str,path);
strcat(str,"/");
strcat(str,pDirent->d_name);
Had code done,
int len = snprintf(str, sizeof str, "%s/%s", path, pDirent->d_name);
if (len < 0 || (unsigned) len >= sizeof str) {
fprintf(stderr, "Path too long %s/%s\n", path, pDirent->d_name);
exit (-1);
}
Then the code would have readily errored out do to recursion on "." and ".." and led to OP's self-discovery of a key problem.
This make for faster code production and more resilient code. Saves OP time.
I tried to write a program in C that checks if a certain file is executable or not, if it is a shell script or a binary
<apue.h> is a header from the book Advanced Programming in the UNIX Environment
I don't think the approach is exactly right (if it's a shell script or binary). I think there is a more efficient solution. Which one do you think?
In addition, what other problems does the code have?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <apue.h>
void checkIfFileExists(const char *fileName);
int main (int argc,char *argv[])
{
char *fileName = argv[1];
if (argc < 2 )
{
err_quit("File to check not specified\n");
return 0;
}
if (argc > 2 )
{
err_quit("Too many arguments\n");
return 0;
}
checkIfFileExists(fileName);
return 0;
}
void checkIfFileExists(const char *fileName)
{
if(!access( fileName, F_OK ))
{
if(!access( fileName, X_OK ))
{
printf("The file %s is an executable\n",fileName);
//check if the file is binary or shell script
}
else
{
printf("The file %s is not an executable\n",fileName);
}
}
else
{
err_quit("The file %s was not found\n",fileName);
}
}
You can use the stat methods,
#include <sys/stat.h>
...
struct stat myf;
stat(<file>,&mf);
if( mf.st_mode & S_IXUSR )
printf("execute");
The output is a bitmap from which you can extract the file permission info
look at https://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
for the permission define available.
I am trying to combine two char array into a third char array, lets look at the following example:
In this code i have already got values in av[1] and av[2].
Just for an example of the values lets take av[1]=ab and av[2]=fg
main (char *av[])
{
av[2] = av[1] "/" av[2]
printf ("%s" , av[2]);
}
The out put that i expect is to be is :
ab/fg
when i run the code an error occurs saying: expected ';' before string constant.
I don't think that is the problem.
I have found the answer and here is all the soled code, thanks for the help, Sorry if it is not well organised i am still learning.
The following code actually does what cp does in a linux terminal, it is just a duplicate function of cp. it might not do all that cp can do but it does most of the things.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> /*hearder to use Stat system
call*/
#define BUFFERSIZE 4096
#define COPYMODE 0644
void oops(char *, char *);
main(int ac, char *av[])/*argument vector*/
{
int in_fd, out_fd, n_chars;
char buf[BUFFERSIZE];
if ( ac != 3 ){ /* argument account"ac"*/
fprintf( stderr, "usage: %s source destination\n", *av);
exit(1);
}
printf("%s", av[2]); /*Test deleat after code works*/
struct stat src, dst; // struct is a
variable that combies all types into one
stat(av[1], &src); //stat system call
stat(av[2], &dst); //stat system call
if( dst.st_mode & S_IFDIR ){ // checks
if the second argument int the array is a file or a directory
printf ("\n It is a directory \n");
printf("%s", av[2]);
strcat(av[2],"/"); /* it concatenates two string or character*/
strcat(av[2],av[1]); /* It takes two argument, i.e, two strings or character arrays, and stores the resultant concatenated string in the first string specified in the argument.*/
printf("\n %s",av[2]); /* testing if values are the same as
expected*/
printf("\n %s",av[1]);
}
if ((src.st_dev == dst.st_dev) && (src.st_ino == dst.st_ino)) { /* compering the file attribute of an inode number and the id of device*/
printf("\n Destination file and source file are same \n");
}
else {
if ( (in_fd=open(av[1], O_RDONLY)) == -1 )
oops("Cannot open ", av[1]);
if ( (out_fd=creat( av[2], src.st_mode)) == -1 ) /* "st_mode" indicates the permissions on the file, tells the modes on a file.*/
oops( "Cannot creat", av[2]);
while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 )
if ( write( out_fd, buf, n_chars ) != n_chars )
oops("Write error to ", av[2]);
if ( n_chars == -1 )
oops("Read error from ", av[1]);
if ( close(in_fd) == -1 || close(out_fd) == -1 )
oops("Error closing files","");
}
}
void oops(char *s1, char *s2)
{
fprintf(stderr,"Error: %s ", s1);
perror(s2);
exit(1);
}
Try the strcat function from <string.h>
Very basic example:
char a[15] = "Hello";
char b[] = "World";
strcat(a, " ");
strcat(a, b);
printf("%s", a);
Output:
Hello World
Just make sure that the destination character array has enough space to hold the entire concatenated string.
If all you want to do is print out the result then just do this:
printf("%s/%s", av[1], av[2]);
I want to write my own code for move(mv) Unix command. I am completely new to C language and apparently lost on how to fix my code. I want to perform actions like renaming a file if both the inputs are file names. If the the dest_folder is a directory I would like to move the file into the directory.
But I am unable to fix code for the particular problem as I am not much familiar with directories and C in particular. The program takes 2 inputs source and destination after which it performs necessary functions. I am apparently able to rename my files but I am unable to move the file to a particular folder for some reason I don't know?
Need help with moving file to a particular directory.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define SBUF 256
#define DBUF 256
int main(int ac, char *argv[])
{
DIR* dir_ptr; // the directory
struct dirent* direntp;
if( ac == 1 )
{
printf("Usage: %s MOVE\n", argv[0] );
exit(0);
}
if(ac>1 && ac<3)
{
printf("Error! few arguments provided " );
exit(0);
}
char src_folder[SBUF];
char dest_folder[DBUF];
strcpy(src_folder, argv[1]);
strcpy(dest_folder, argv[2]);
dir_ptr = opendir("."); //open directory
if ( dir_ptr == NULL )
{
perror( "." );
exit( 1 );
}
while( (direntp = readdir( dir_ptr )) != NULL )
{
if ( strcmp(direntp->d_name, dest_folder) !=0) //search file or directory
{
printf("found the file %s", dest_folder);
break;
}else
printf("not found");
break;
}
rename(src_folder, dest_folder);
closedir( dir_ptr );
return 0;
}
rename(3) does not work the way you want it to work (I don't know why, ask the committee). You cannot do a rename(some_file, some_directory), just as the man-page says.
Just use stat(2) (or lstat(2) if necessary) and check what you have been given. Here is a short, runnable sketch.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
// check if it is the same inode on the same device
#define SAME_INODE(a, b) ((a).st_ino == (b).st_ino && (a).st_dev == (b).st_dev)
// ALL CHECKS OMMITTED!
int main(int argc, char **argv)
{
struct stat statbuf_src, statbuf_dest;
char *src, *dest, *new_src, *new_dest;
char *current_directory;
if (argc != 3) {
fprintf(stderr, "usage: %s src dest\n", argv[0]);
exit(EXIT_FAILURE);
}
// work on copy
src = malloc(strlen(argv[1]) + 1);
dest = malloc(strlen(argv[2]) + 1);
strcpy(src, argv[1]);
strcpy(dest, argv[2]);
stat(src, &statbuf_src);
stat(dest, &statbuf_dest);
// there are many more, of course
printf("\"%s\" is a ", src);
if (S_ISREG(statbuf_src.st_mode)) {
puts("a regular file");
}
if (S_ISDIR(statbuf_src.st_mode)) {
puts("a directory");
}
printf("\"%s\" is a ", dest);
if (S_ISREG(statbuf_dest.st_mode)) {
puts("a regular file");
}
if (S_ISDIR(statbuf_dest.st_mode)) {
puts("a directory");
}
if (SAME_INODE(statbuf_dest, statbuf_src)) {
printf("%s and %s are the identical\n", src, dest);
}
// if that is not set you have to do it by hand:
// climb up the tree, concatenating names until the inodes are the same
current_directory = getenv("PWD");
printf("current directory is \"%s\"\n", current_directory);
// I'm pretty sure it can be done in a much more elegant way
new_src = malloc(strlen(src) + 1 + strlen(current_directory) + 1);
strcpy(new_src,current_directory);
strcat(new_src,"/");
strcat(new_src,src);
printf("new_src = %s\n",new_src);
new_dest = malloc(strlen(dest) + 1 + strlen(current_directory) + 1 + strlen(src) + 1);
strcpy(new_dest,current_directory);
strcat(new_dest,"/");
strcat(new_dest,dest);
strcat(new_dest,"/");
strcat(new_dest,src);
printf("new_dest = %s\n",new_dest);
if(rename(new_src,new_dest) != 0){
fprintf(stderr,"rename failed with error %s\n",strerror(errno));
}
free(new_src);
free(new_dest);
free(src);
free(dest);
exit(EXIT_SUCCESS);
}
Edit: added code for the desciption below
At the end you have a the path where you are, the information if the arguments given are directories or regular files and the path. If the source is a regular file and the destination a directory, you concatenate the path with the name of the regular file, the path with the name of the directory and the name of the regular file (your source)
Out of
Path = /home/foo
src = bar
dest = coffee
build
new_src = /home/foo/bar
new_dest = /home/foo/coffee/bar
Such that the call to rename() is
rename(new_src, new_dest);
That way you rename a regular file to a regular file which rename() accepts.
Please be aware that rename() does not work across every filesystem, but most.
Like you know, mv is implemented by rename. rename is a atomic system call that can rename a file to a file , an emtpy directory to an empty directory or a directory to a directory(the dest must be nonentity). So there are following situation to deal with:
mv file1 file2 - use rename function
mv dir1 dir2(nonentity or empty) - use rename function
mv dir1 dir2(not empty) - rename dir1 to dir2/dir1
mv file dir(exist) - rename file to dir/file
mv dir file - illegal operation
can you understand?
I have drafted a code snippet that is to emulate the operation of ls -all in a custom shell named My$HELL
The main shell process invokes this code(by calling its executable through execlp).
Following is the code of the executable myls which is to do the work:-
myls.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
void search_dir(const char * arg);
void main(int argc, char *argv[])
{
int i;
if (argc==1)
search_dir(".");
for(i=1;i<argc;i++)
search_dir(argv[i]);
}
void search_dir(const char *arg)//Function to read directories and file attributes//
{
DIR *dirp;
struct dirent *dp;//Dirent structure used to read and store directory attributes//
char file_name[256];
char time[50]={"\0"};
struct tm *timeinfo;
struct stat prop_file;//stat function for accessing file attributes//
char type;
if((dirp=opendir(arg))==NULL)
{
perror("opendir");
return;
}
printf("\n\nDirectory \tTime last Modified\tSize\t\t Name\n");
while((dp=readdir(dirp))!=NULL) // Navigates the directory structure
{
if ( stat(dp->d_name,&prop_file)!=0) //Reading file attributes//
{
printf("\n%s:Error in reading the file attributes", dp->d_name );
continue;
}
if ( dp->d_type==8 )
{
type = '-';
}
else
{
type = 'd';
}
timeinfo=localtime(&(prop_file.st_mtime));
strftime(time,20,"%b %d %H:%M", timeinfo);
printf("\n %c\t\t %s\t\t%d\t\t %s",type,time,(int)prop_file.st_size,dp->d_name); //Printing ile attributes//
}
printf("\n");
}
Irrespective of the contents in the directory, the process displays certain fields after which the calling process terminates with a segmentation fault.
A GDB run is also of a little help (for being vague) and search on the error yields little result. Following is the debugged output:-
[~pbox/working/trial]<My$HELL>myls
Executing myls
Directory Time last Modified Size Name
d Aug 14 19:22 4096 ..
d Aug 14 18:42 4096 .
[~pbox/working/trial]<My$HELL>
Program received signal SIGSEGV, Segmentation fault.
strlen () at ../sysdeps/x86_64/strlen.S:106
106 ../sysdeps/x86_64/strlen.S: No such file or directory.
(gdb) Quit
From what I could understand such error are results of illegal variable/pointer assignments. Any help in pointing out the bug is highly appreciated.
Am also appending code segment of the main process from where myls is being called
main.c
.
.
else if(strcmp(command[0],"myls")==0) //command of type char ** stores the user input command check if the first field is 'myls'//
{
printf("Executing myls\n");
strcat(path,"/myls"); //path stores the directory path
result=execvp(path,command); //result of type int
exit(0);
}
.
.
Cheers and thanks in anticipation!!
the following code :
1) cleanly compiles
2) handles errors in an appropriate manner
3) does the job correctly
4) does not follow symbolic links
5) does not display the proper file type for every file
6) when accessing directories that are (in any way) protected
from casual reading, will output an error message
7) properly builds the path+filename before calling stat()
8) properly declares main() function and proper return
9) does not handle any options that are passed in.
10)does not seg fault
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <string.h>
void search_dir(const char * arg);
int main(int argc, char *argv[])
{
int i;
char dirBuf[128] = {'\0'};
char * newline = NULL;
if (argc==1)
search_dir(".");
else
{
for(i=1;i<argc;i++)
{
newline = strcpy( dirBuf, argv[i] );
if( strstr( dirBuf, "\n") )
{
*newline = '\0';
}
search_dir(dirBuf);
}
}
return 0;
}
void search_dir(const char *arg)//Function to read directories and file attributes//
{
DIR *dirp;
struct dirent *dp;//Dirent structure used to read and store directory attributes//
char fileName[256];
char fileTime[50]={"\0"};
struct tm *timeinfo;
struct stat prop_file;//stat function for accessing file attributes//
char type;
printf( "%s\n", arg);
if( NULL == (dirp=opendir(arg)) )
{
perror("opendir failed");
return;
}
// implied else, opendir successful
printf("\n\nDirectory \tTime last Modified\tSize\t\t Name\n");
while( NULL != (dp=readdir(dirp)) ) // gets next entry in current directory,
{
strcpy(fileName, arg);
strcat(fileName, "/");
strcat(fileName, dp->d_name);
printf( "\nfileName: %s", fileName);
if ( stat(fileName,&prop_file) ) //Reading file attributes//
{
perror( "stat failed" );
printf("\n%s:Error in reading the file attributes", dp->d_name );
continue;
}
#ifdef _DIRENT_HAVE_D_OFF
// following if/else needs expansion
if ( dp->d_type==8 )
{
type = '-';
}
else
{
type = 'd';
}
#else
type = '?';
#endif
timeinfo=localtime(&(prop_file.st_mtime));
strftime(fileTime, 49, "%b %d %H:%M", timeinfo);
printf("\n %c\t\t %s\t\t%d\t\t %s",
type,
fileTime,
(int)prop_file.st_size,
dp->d_name); //Printing file attributes//
}
printf("\n");
closedir( dirp );
}