I've been trying to get this code to work for hours! All I need to do is open a file to see if it is real and readable. I'm new to C so I'm sure there is something stupid I'm missing. Here is the code (shorthand, but copied):
#include <stdio.h>
main() {
char fpath[200];
char file = "/test/file.this";
sprintf(fpath,"~cs4352/projects/proj0%s",file);
FILE *fp = fopen(fpath,"r");
if(fp==NULL) {
printf("There is no file on the server");
exit(1);
}
fclose(fp);
//do more stuff
}
I have also verified that the path is correctly specifying a real file that I have read permissions to. Any other ideas?
Edit 1: I do know that the fpath ends up as "~cs4352/projects/proj0/test/file.this"
Edit 2: I have also tried the using the absolute file path. In both cases, I can verify that the paths are properly built via ls.
Edit 3: There errno is 2... I'm currently trying to track what that means in google.
Edit 4: Ok, errno of 2 is "There is no such file or directory". I am getting this when the reference path in fopen is "/home/courses1/cs4352/projects/proj0/index.html" which I verified does exist and I have read rights to it. As for the C code listed below, there may be a few semantic/newbie errors in it, but gcc does not give me any compile time warnings, and the code works exactly as it should except that it says that it keeps spitting errno of 2. In other words, I know that all the strings/char array are working properly, but the only thing that could be an issue is the fopen() call.
Solution: Ok, the access() procedure is what helped me the most (and what i am still using as it is less code, not to mention the more elegant way of doing it). The problem actually came from something that I didn't explain to you all (because I didn't see it until I used access()). To derrive the file, I was splitting strings using strtok() and was only splitting on " \n", but because this is a UNIX system, I needed to add "\r" to it as well. Once I fixed that, everything fell into place, and I'm sure that the fopen() function would work as well, but I have not tested it.
Thank you all for your helpful suggestions, and especially to Paul Beckingham for finding this wonderful solution.
Cheers!
The "~" is expanded by the shell, and is not expanded by fopen.
To test the existence and readability of a file, consider using the POSIX.1 "access" function:
#include <unistd.h>
if (access ("/path/to/file", F_OK | R_OK) == 0)
{
// file exists and is readable
}
First, file needs to be declared as char* or const char*, not simply char as you've written. But this might just be a typo, the compiler should at least give a warning there.
Secondly, use an absolute path (or a path relative to the current directory), not shell syntax with ~. The substitution of ~cs4352 by the respective home directory is usually done by the shell, but you are directly opening the file. So you are trying to open a file in a ~cs4352 subdirectory of your current working directory, which I guess is not what you want.
Other people have probably produced the equivalent (every modern shell, for example), but here's some code that will expand a filename with ~ or ~user notation.
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif
#include <assert.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char *relfname(const char *name, char *buffer, size_t bufsiz)
{
assert(name != 0 && buffer != 0 && bufsiz != 0);
if (name[0] != '~')
strncpy(buffer, name, bufsiz);
else
{
const char *copy;
struct passwd *usr = 0;
if (name[1] == '/' || name[1] == '\0')
{
usr = getpwuid(getuid());
copy = &name[1];
}
else
{
char username[PATH_MAX];
copy = strchr(name, '/');
if (copy == 0)
copy = name + strlen(name);
strncpy(username, &name[1], copy - &name[1]);
username[copy - &name[1]] = '\0';
usr = getpwnam(username);
}
if (usr == 0)
return(0);
snprintf(buffer, bufsiz, "%s%s", usr->pw_dir, copy);
}
buffer[bufsiz-1] = '\0';
return buffer;
}
#ifdef TEST
static struct { const char *name; int result; } files[] =
{
{ "/etc/passwd", 1 },
{ "~/.profile", 1 },
{ "~root/.profile", 1 },
{ "~nonexistent/.profile", 0 },
};
#define DIM(x) (sizeof(x)/sizeof(*(x)))
int main(void)
{
int i;
int fail = 0;
for (i = 0; i < DIM(files); i++)
{
char buffer[PATH_MAX];
char *name = relfname(files[i].name, buffer, sizeof(buffer));
if (name == 0 && files[i].result != 0)
{
fail++;
printf("!! FAIL !! %s\n", files[i].name);
}
else if (name != 0 && files[i].result == 0)
{
fail++;
printf("!! FAIL !! %s --> %s (unexpectedly)\n", files[i].name, name);
}
else if (name == 0)
printf("** PASS ** %s (no match)\n", files[i].name);
else
printf("** PASS ** %s -> %s\n", files[i].name, name);
}
return((fail == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
#endif
You could try examining errno for more information on why you're not getting a valid FILE*.
BTW-- in unix the global value errno is set by some library and system calls when they need to return more information than just "it didn't work". It is only guaranteed to be good immediately after the relevant call.
char file = "/test/file.this";
You probably want
char *file = "/test/file.this";
Are you sure you do not mean
~/cs4352/projects/proj0%s"
for your home directory?
To sum up:
Use char *file=/test/file.this";
Don't expect fopen() to do shell substitution on ~ because it won't. Use the full path or use a relative path and make sure the current directory is approrpriate.
error 2 means the file wasn't found. It wasn't found because of item #2 on this list.
For extra credit, using sprintf() like this to write into a buffer that's allocated on the stack is a dangerous habit. Look up and use snprintf(), at the very least.
As someone else here mentioned, using access() would be a better way to do what you're attempting here.
Related
I have a file such as the following:
1-3-5 2 1
2 3-4-1 2
4-1 2-41-2 3-4
I want to return the number of columns of this file. I am reading the file with mmap in C. I have been trying to do with strtok(), but failing, so far. This is just a testfile, my original file is in GB scale.
pmap = mmap(0,mystat.st_size,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
char *start = pmap;
char *token;
token = strtok(start, "\t");
while (token != NULL){
printf("%s \n",token);
token = strtok(NULL, "\t");
col_len++;
}
I have been trying something on these lines, but, obviously there is a logical error. I am getting the following output:
number of cols = 1
Although, the # of cols should be 3.
It'd be great if you guys can help with any idea on how to parse this kind of a file using mmap.
I am using mmap because of faster execution for a single pass over the file.
It is hard to provide a definitive answer without a definitive question; as written, the question does not contain complete code, does not show the precise input, and does not show the debugging output.
But it is possible to provide some suggestions based on the non-applicability of strtok to this problem.
(strtok modifies its first argument, so it is really not a good idea to use it with an mmaped resource. However, that is not directly relevant to the problem you are having.)
You should ensure that the columns in the file are really separated by tabs. It seems to me most likely that the file contains spaces, not tabs, which is why the program reports that the entire file contains one column. If this were the only problem, you could call strtok with the second argument " \t" rather than "\t". But remember that strtok combines successive delimiters into a single separator so if the file is tab-separated and there are empty fields, strtok will not report the empty fields.
Related to the phrase "entire file" above, you do not tell strtok to recognized a newline character as terminating a token. So the strtok loop will try to analyze the entire file, counting the last field of each line as part of the same token as the first field of the next line. That is surely not what you want.
However, strtok overwrites the column delimiter that it finds, so if you did fix the strtok calls to include \n as a delimiter character, you would no longer be able to tell where the lines ended. That is probably important to your code, and it is a key reason why strtok is not an appropriate tool in this case. The Gnu strtok manpage (man strtok, emphasis added) provides a warning about this very issue (in the BUGS section at the end):
Be cautious when using these functions. If you do use them, note that:
These functions modify their first argument.
These functions cannot be used on constant strings.
The identity of the delimiting byte is lost.
There is no guarantee that a file ends with a NUL character. In fact, the file is very unlikely to contain a NUL character, and it is undefined behaviour to reference bytes in the mmap'ed region which are not in the file, but in practice most OSs will mmap an integral number of pages, zero-filling the last page. So 4095 times out of 4096, you will not notice this problem, and the 4096th time when the file is precisely an integral number of pages, your program will crash and burn, along with whatever sensitive equipment it is controlling. This is another reason strtok should never be used on mmaped files.
My comment was actually not correct, as you use MAP_PRIVATE, you don't risk destroying your file. But still, if you modify the memory area, the touched pages are copied, and you probably don't want this overhead, otherwise you could just copy the file to RAM from the beginning. So I'd still say: don't use strtok() here.
A solution with an own loop based on the functions in <ctype.h> is quite simple, though. As I wanted to try it myself, see here a working program to demonstrate it (the relevant part is the countCols() function):
#define _POSIX_C_SOURCE 200112L
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int countCols(const char *line, size_t maxlen)
{
int cols = 0;
int incol = 0;
const char *c = line;
while (maxlen && (!isspace(*c) || isblank(*c)))
{
if (isblank(*c))
{
incol = 0;
}
else
{
if (!incol)
{
incol = 1;
++cols;
}
}
++c;
--maxlen;
}
return cols;
}
int main(int argc, char **argv)
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s [file]\n", argv[0]);
return EXIT_FAILURE;
}
struct stat st;
if (stat(argv[1], &st) < 0)
{
fprintf(stderr, "Could not stat `%s': %s\n", argv[1],
strerror(errno));
return EXIT_FAILURE;
}
int dataFd = open(argv[1], O_RDONLY);
if (dataFd < 0)
{
fprintf(stderr, "Could not open `%s': %s\n", argv[1],
strerror(errno));
return EXIT_FAILURE;
}
char *data = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, dataFd, 0);
if (data == MAP_FAILED)
{
close(dataFd);
fprintf(stderr, "Could not mmap `%s': %s\n", argv[1],
strerror(errno));
return EXIT_FAILURE;
}
int cols = countCols(data, st.st_size);
printf("found %d columns.\n", cols);
munmap(data, st.st_size);
return EXIT_SUCCESS;
}
I have a text file with the names of about 800 files I want to transfer from one folder to another. Basically, the text file looks like this :
file1.aaa (End of line)
file2.aaa
..
etc
I made this code, using the function 'rename' as everyone suggests on the internet :
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main ( void )
{
FILE *file = fopen ( "C:\\Users\\blabla\\ListOfFiles.txt", "r" );
char path1[100] = "C:\\blabla\\folder1\\";
char path2[100] = "C:\\blabla\\folder2\\";
char *s1;
char *s2;
char line [20]; /* the file names won't be any longer than that */
while(fgets(line, sizeof line,file) != NULL)
{
char *filePath1 = (char *) malloc((strlen(path1) + strlen(line) + 1) * sizeof(char));
char *filePath2 = (char *) malloc((strlen(path2) + strlen(line) + 1) * sizeof(char));
filePath1 = strcpy(filePath1, path1);
filePath2 = strcpy(filePath2, path2);
strcat(filePath1,line);
strcat(filePath2,line);
if (rename(filePath1, filePath2) != 0)
{
perror("wrong renaming");
getchar();
}
free(filePath1);
free(filePath2);
}
fclose (file);
return 0;
}
Now, when I print the filepaths I get the expected results, but the program stops running when it's supposed to run the 'rename' function, because of an invalid argument problem.
I looked at http://www.cplusplus.com/ and noticed that it says the arguments of rename() should be const char*, could this be where the problem come from ? But if so, I don't see how I could turn my arguments into 'const', since I need to update them as I read my initial text file.
The code that builds the file paths is horribly over-complicated but should work. To simplify it, remove the malloc() and just use two statically-sized arrays. Also, for the future, please don't cast the return value of malloc() in C.
You're misunderstanding the const thing, it means that rename() won't change the characters pointed at by its two arguments. It's a way to say "these two pointers point at data which is input-only to this function, there will be no attempt to modify that data from inside the function". You should always const argument pointers when possible, it helps make the code much clearer.
If you're getting "invalid argument", that probably means the files aren't being found. Print out the filenames to help you verify.
I suggest you to take a look at:
How can I copy a file on Unix using C?
And replace "/bin/cp" for "/bin/mv" in that code.
Hope it helps!
I'm writing a UNIX minishell on ubuntu, and am trying to add built-in commands at this point. When it's not a built-in command I fork and then the child executes it, however for built-in commands I'll just execute it in the current process.
So, I need a way to see if the files exist(if they do it's not a built-in command), however execvp uses the environment PATH variable to automatically look for them, so I have no idea how I would manually check beforehand.
So, do you guys know how I could test an argument to see if it's a built-in command simply by supplying the name?
Thanks guys.
I have tested the answer by Tom
It contained a number of problems. I have fixed them here and provided a test program.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
int is_file(const char* path) {
struct stat buf;
stat(path, &buf);
return S_ISREG(buf.st_mode);
}
/*
* returns non-zero if the file is a file in the system path, and executable
*/
int is_executable_in_path(char *name)
{
char *path = getenv("PATH");
char *item = NULL;
int found = 0;
if (!path)
return 0;
path = strdup(path);
char real_path[4096]; // or PATH_MAX or something smarter
for (item = strtok(path, ":"); (!found) && item; item = strtok(NULL, ":"))
{
sprintf(real_path, "%s/%s", item, name);
// printf("Testing %s\n", real_path);
if ( is_file(real_path) && !(
access(real_path, F_OK)
|| access(real_path, X_OK))) // check if the file exists and is executable
{
found = 1;
}
}
free(path);
return found;
}
int main()
{
if (is_executable_in_path("."))
puts(". is executable");
if (is_executable_in_path("echo"))
puts("echo is executable");
}
Notes
the test for access return value was reversed
the second strtok call had the wrong delimiter
strtok changed the path argument. My sample uses a copy
there was nothing to guarantee a proper path separator char in the concatenated real_path
there was no check whether the matched file was actually a file (directories can be 'executable' too). This leads to strange things like . being recognized as an external binary
What you can do is you can change the path to the particular directory and then use #include<dirent.h> header file and its readdir and scandir functions to walk through the directory or stat structure to see if the file exists in the directory or not.
You can iterate yourself through the PATH directories, and for each entry in PATH (You will have to split PATH with :, probably using strtok) concatenate at the end of each path the name of the command called. When you have create this path, check if the file exists and if it is executable using access.
int is_built_in(char *path, char *name)
{
char *item = strtok(path, ":");
do {
char real_path[4096] = strcat(item, name); // you would normally alloc exactly the size needed but lets stick to it for the sake of the example
if (!access(real_path, F_OK) && !access(real_path, X_OK)) // check if the file exists and is executable
return 0;
} while ((item = strtok(NULL, ":")) != NULL);
return 1;
}
Why do you want to test before calling execvp? That's the wrong approach. Just call execvp and it will tell you if the program does not exist.
I've run into the need to be able refer to a directory by path given its file descriptor in Linux. The path doesn't have to be canonical, it just has to be functional so that I can pass it to other functions. So, taking the same parameters as passed to a function like fstatat(), I need to be able to call a function like getxattr() which doesn't have a f-XYZ-at() variant.
So far I've come up with these solutions; though none are particularly elegant.
The simplest solution is to avoid the problem by calling openat() and then using a function like fgetxattr(). This works, but not in every situation. So another method is needed to fill the gaps.
The next solution involves looking up the information in proc:
if (!access("/proc/self/fd",X_OK)) {
sprintf(path,"/proc/self/fd/%i/",fd);
}
This, of course, totally breaks on systems without proc, including some chroot environments.
The last option, a more portable but potentially-race-condition-prone solution, looks like this:
DIR* save = opendir(".");
fchdir(fd);
getcwd(path,PATH_MAX);
fchdir(dirfd(save));
closedir(save);
The obvious problem here is that in a multithreaded app, changing the working directory around could have side effects.
However, the fact that it works is compelling: if I can get the path of a directory by calling fchdir() followed by getcwd(), why shouldn't I be able to just get the information directly: fgetcwd() or something. Clearly the kernel is tracking the necessary information.
So how do I get to it?
Answer
The way Linux implements getcwd in the kernel is this: it starts at the directory entry in question and prepends the name of the parent of that directory to the path string, and repeats that process until it reaches the root. This same mechanism can be theoretically implemented in user-space.
Thanks to Jonathan Leffler for pointing this algorithm out. Here is a link to the kernel implementation of this function: https://github.com/torvalds/linux/blob/v3.4/fs/dcache.c#L2577
The kernel thinks of directories differently from the way you do - it thinks in terms of inode numbers. It keeps a record of the inode number (and device number) for the directory, and that is all it needs as the current directory. The fact that you sometimes specify a name to it means it goes and tracks down the inode number corresponding to that name, but it preserves only the inode number because that's all it needs.
So, you will have to code a suitable function. You can open a directory directly with open() precisely to get a file descriptor that can be used by fchdir(); you can't do anything else with it on many modern systems. You can also fail to open the current directory; you should be testing that result. The circumstances where this happens are rare, but not non-existent. (A SUID program might chdir() to a directory that the SUID privileges permit, but then drop the SUID privileges leaving the process unable to read the directory; the getcwd() call will fail in such circumstances too - so you must error check that, too!) Also, if a directory is removed while your (possibly long-running) process has it open, then a subsequent getcwd() will fail.
Always check results from system calls; there are usually circumstances where they can fail, even though it is dreadfully inconvenient of them to do so. There are exceptions - getpid() is the canonical example - but they are few and far between. (OK: not all that far between - getppid() is another example, and it is pretty darn close to getpid() in the manual; and getuid() and relatives are also not far off in the manual.)
Multi-threaded applications are a problem; using chdir() is not a good idea in those. You might have to fork() and have the child evaluate the directory name, and then somehow communicate that back to the parent.
bignose asks:
This is interesting, but seems to go against the querent's reported experience: that getcwd knows how to get the path from the fd. That indicates that the system knows how to go from fd to path in at least some situations; can you edit your answer to address this?
For this, it helps to understand how - or at least one mechanism by which - the getcwd() function can be written. Ignoring the issue of 'no permission', the basic mechanism by which it works is:
Use stat on the root directory '/' (so you know when to stop going upwards).
Use stat on the current directory '.' (so you know where you are); this gives you a current inode.
Until you reach the root directory:
Scan the parent directory '..' until you find the entry with the same inode as the current inode; this gives you the next component name of the directory path.
And then change the current inode to the inode of '.' in the parent directory.
When you reach root, you can build the path.
Here is an implementation of that algorithm. It is old code (originally 1986; the last non-cosmetic changes were in 1998) and doesn't make use of fchdir() as it should. It also works horribly if you have NFS automounted file systems to traverse - which is why I don't use it any more. However, this is roughly equivalent to the basic scheme used by getcwd(). (Ooh; I see a 18 character string ("../123456789.abcd") - well, back when it was written, the machines I worked on only had the very old 14-character only filenames - not the modern flex names. Like I said, it is old code! I haven't seen one of those file systems in what, 15 years or so - maybe longer. There is also some code to mess with longer names. Be cautious using this.)
/*
#(#)File: $RCSfile: getpwd.c,v $
#(#)Version: $Revision: 2.5 $
#(#)Last changed: $Date: 2008/02/11 08:44:50 $
#(#)Purpose: Evaluate present working directory
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 1987-91,1997-98,2005,2008
#(#)Product: :PRODUCT:
*/
/*TABSTOP=4*/
#define _POSIX_SOURCE 1
#include "getpwd.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_POSIX_SOURCE) || defined(USG_DIRENT)
#include "dirent.h"
#elif defined(BSD_DIRENT)
#include <sys/dir.h>
#define dirent direct
#else
What type of directory handling do you have?
#endif
#define DIRSIZ 256
typedef struct stat Stat;
static Stat root;
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_getpwd_c[] = "#(#)$Id: getpwd.c,v 2.5 2008/02/11 08:44:50 jleffler Exp $";
#endif /* lint */
/* -- Routine: inode_number */
static ino_t inode_number(char *path, char *name)
{
ino_t inode;
Stat st;
char buff[DIRSIZ + 6];
strcpy(buff, path);
strcat(buff, "/");
strcat(buff, name);
if (stat(buff, &st))
inode = 0;
else
inode = st.st_ino;
return(inode);
}
/*
-- Routine: finddir
Purpose: Find name of present working directory
Given:
In: Inode of current directory
In: Device for current directory
Out: pathname of current directory
In: Length of buffer for pathname
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/88 JL Rewritten to use opendir/readdir/closedir
25/09/90 JL Modified to pay attention to length
10/11/98 JL Convert to prototypes
*/
static int finddir(ino_t inode, dev_t device, char *path, size_t plen)
{
register char *src;
register char *dst;
char *end;
DIR *dp;
struct dirent *d_entry;
Stat dotdot;
Stat file;
ino_t d_inode;
int status;
static char name[] = "../123456789.abcd";
char d_name[DIRSIZ + 1];
if (stat("..", &dotdot) || (dp = opendir("..")) == 0)
return(-1);
/* Skip over "." and ".." */
if ((d_entry = readdir(dp)) == 0 ||
(d_entry = readdir(dp)) == 0)
{
/* Should never happen */
closedir(dp);
return(-1);
}
status = 1;
while (status)
{
if ((d_entry = readdir(dp)) == 0)
{
/* Got to end of directory without finding what we wanted */
/* Probably a corrupt file system */
closedir(dp);
return(-1);
}
else if ((d_inode = inode_number("..", d_entry->d_name)) != 0 &&
(dotdot.st_dev != device))
{
/* Mounted file system */
dst = &name[3];
src = d_entry->d_name;
while ((*dst++ = *src++) != '\0')
;
if (stat(name, &file))
{
/* Can't stat this file */
continue;
}
status = (file.st_ino != inode || file.st_dev != device);
}
else
{
/* Ordinary directory hierarchy */
status = (d_inode != inode);
}
}
strncpy(d_name, d_entry->d_name, DIRSIZ);
closedir(dp);
/**
** NB: we have closed the directory we are reading before we move out of it.
** This means that we should only be using one extra file descriptor.
** It also means that the space d_entry points to is now invalid.
*/
src = d_name;
dst = path;
end = path + plen;
if (dotdot.st_ino == root.st_ino && dotdot.st_dev == root.st_dev)
{
/* Found root */
status = 0;
if (dst < end)
*dst++ = '/';
while (dst < end && (*dst++ = *src++) != '\0')
;
}
else if (chdir(".."))
status = -1;
else
{
/* RECURSE */
status = finddir(dotdot.st_ino, dotdot.st_dev, path, plen);
(void)chdir(d_name); /* We've been here before */
if (status == 0)
{
while (*dst)
dst++;
if (dst < end)
*dst++ = '/';
while (dst < end && (*dst++ = *src++) != '\0')
;
}
}
if (dst >= end)
status = -1;
return(status);
}
/*
-- Routine: getpwd
Purpose: Evaluate name of current directory
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/88 JL Short circuit if pwd = /
25/09/90 JL Revise interface; check length
10/11/98 JL Convert to prototypes
Known Bugs
----------
1. Uses chdir() and could possibly get lost in some other directory
2. Can be very slow on NFS with automounts enabled.
*/
char *getpwd(char *pwd, size_t plen)
{
int status;
Stat here;
if (pwd == 0)
pwd = malloc(plen);
if (pwd == 0)
return (pwd);
if (stat("/", &root) || stat(".", &here))
status = -1;
else if (root.st_ino == here.st_ino && root.st_dev == here.st_dev)
{
strcpy(pwd, "/");
status = 0;
}
else
status = finddir(here.st_ino, here.st_dev, pwd, plen);
if (status != 0)
pwd = 0;
return (pwd);
}
#ifdef TEST
#include <stdio.h>
/*
-- Routine: main
Purpose: Test getpwd()
Maintenance Log
---------------
10/11/86 JL Original version stabilised
25/09/90 JL Modified interface; use GETCWD to check result
*/
int main(void)
{
char pwd[512];
int pwd_len;
if (getpwd(pwd, sizeof(pwd)) == 0)
printf("GETPWD failed to evaluate pwd\n");
else
printf("GETPWD: %s\n", pwd);
if (getcwd(pwd, sizeof(pwd)) == 0)
printf("GETCWD failed to evaluate pwd\n");
else
printf("GETCWD: %s\n", pwd);
pwd_len = strlen(pwd);
if (getpwd(pwd, pwd_len - 1) == 0)
printf("GETPWD failed to evaluate pwd (buffer is 1 char short)\n");
else
printf("GETPWD: %s (but should have failed!!!)\n", pwd);
return(0);
}
#endif /* TEST */
Jonathan's answer is very fine in showing how it works. But it doesn't show a workaround for the situation you describe.
I would as well use something like you describe:
DIR* save = opendir(".");
fchdir(fd);
getcwd(path,PATH_MAX);
fchdir(dirfd(save));
closedir(save);
but, in order to avoid race conditions in with threads, fork another process in order to do that.
That might sound expensive, but if you don't do that too often, it should be ok.
The idea is something like this (no runnable code, just a raw idea):
int fd[2];
pipe(fd);
pid_t pid;
if ((pid = fork()) == 0) {
// child; here we do the chdir etc. stuff
close(fd[0]); // read end
char path[PATH_MAX+1];
DIR* save = opendir(".");
fchdir(fd);
getcwd(path,PATH_MAX);
fchdir(dirfd(save));
closedir(save);
write(fd[1], path, strlen(path));
close(fd[1]);
_exit(EXIT_SUCCESS);
} else {
// parent; pid is our child
close(fd[1]); // write end
int cursor=0;
while ((r=read(fd[0], &path+cursor, PATH_MAX)) > 0) {
cursor += r;
}
path[cursor]='\0'; // make it 0-terminated
close(fd[0]);
wait(NULL);
}
I am not sure if this will resolve all issues, and I as well do not do any error checking, so that's what you should add.
I am currently trying to count the number of bytes consumed by files in a certain directory. It recursively goes through all the folders on the current directory and counts the bytes of the files.
When I recursively call the function rec_bytes, I print off "Go in"... but when it returns the value... It segfaults.
I labeled the problematic line in the code below.
I think the problem has to do with open/closing directories.
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
int rec_Bytes(char path[])
{
int bytesSum = 0;
printf("PathX %s\n", path);
DIR *mydir = opendir(path); // Look in current directory
struct dirent *entry = NULL;
while((entry = readdir(mydir))) /* If we get EOF, the expression is 0 and
* the loop stops. */
{
if (!isDir(entry->d_name)) // Check to see if the entry is a directory of a file
{
char tempPath[] = "";
strcat(tempPath, path);
strcat(tempPath,"/");
strcat(tempPath,entry->d_name);
int tempSum = fileSize(tempPath); // Get file size
bytesSum += tempSum; // Add to sum
printf("%s\t%d\n", entry->d_name, tempSum);
}
else // The current entry is a directory
{
if ((strcmp((entry->d_name),"..") != 0) && (strcmp((entry->d_name),".")) != 0)
{
printf("Directory%s\n", entry->d_name);
char tempPath[] = "";
strcat(tempPath, path);
strcat(tempPath,"/");
strcat(tempPath,entry->d_name);
printf("Go in\n");
int tempSum = rec_Bytes(tempPath); <<<<<< Get segmentation fault here.
printf("Come Out%d\n", tempSum);
bytesSum += tempSum;
printf("%s\t%d\n", entry->d_name, tempSum);
}
}
}
closedir(mydir);
printf("XXXX\t%s\t%d\n", path, bytesSum);
return bytesSum;
}
// Thanks to : http://cboard.cprogramming.com/cplusplus-programming/117431-how-tell-if-file-directory.html
int isDir(const char* target)
{
struct stat statbuf;
stat(target, &statbuf);
return S_ISDIR(statbuf.st_mode);
}
Your problem is with lines like this...
char tempPath[] = "";
This will allocate a buffer with one byte, that byte being a null character. There is no room in that buffer for any longer string.
Basically, C does not have dynamically resizable strings. It has null terminated strings that live within fixed-size arrays of characters. This creates an issue when you won't know the length of the string until you've finished building it, of course.
Try something like...
char tempPath[5000] = "";
as a quick fix. Also, look up strncat - it's less likely to segfault. There's a printf variant as well, but I use too much C++ these days.
EDIT
Actually, the segfault is probably due to those strcats and printfs corrupting the stack. The segfault is probably when a function tries to return. The basic issue is the too-small string buffers thing, though.
Whoops!
The real quick fix is...
char tempPath[5000];
tempPath [0] = 0;
Otherwise, it won't always get initialised to an empty string when you expect it to.