Resolving filepath to executable in C - c

I am currently tasked with a homework assignment to write a primitive shell in C, and am having difficulty implementing the shell feature which constructs a path to a given requested program. e.g. Transforming user input of wc to /usr/bin/wc.
Getenv() is working fine to get the value of $PATH. Using code supplied by my instructors, I've also parsed this value into individual 'tokens,' where a token is defined: typedef char *tok_t
My question is how can I fix this implementation of the following function, which seeks to return the absolute path to a given filename if found, and NULL otherwise.
The main issue here is concatenating a tok_t and a char* to produce the full pathname.
char *resolve_path(char *filename) {
printf("trying to resolve path...\n");
char *path_var = getenv("PATH");
tok_t *path_list = get_toks(path_var);
//fprint_tok(stdout, path_list);
char *path;
for (int i = 0; path_list[i]; i++) {
path = (char*) malloc(PATH_MAX);
strcat(path, *path_list[i]);
strcat(path, filename);
printf("Trying... %s\n", path);
if (file_exists(path)) {
return path;
}
free(path);
}
return NULL;
}
Should I bother with malloc() and strcat(), or is there some better way of implementing this? Currently getting segfaults and warnings about the type compatibility in use of strcat().

You do need to use malloc() since you are returning the resulting path from the function (a pointer to an automatic array created in this function will not be valid after the function returns). You do need to use strcat() or similar in order to produce a single contiguous char * to pass to file_exists().
There are a few issues with your code, however:
Do not explicitly cast void * to other types in C - at best, it is unnecessary (I'm talking about casting the return value of your allocation, in this case).
Check to see if malloc() fails.
You don't need to call malloc() and free() inside your loop - just once (each) outside the loop is sufficient.
If tok_t is a char * then path_list is a char **, so no need to dereference path_list[i] when you pass it to strncpy()/strncat() as that would merely be a char, when they expect strings. This is the likely cause of your statement "Currently getting segfaults and warnings about the type compatibility in use of strcat()."
You need to set the first character of path to NULL before your first call to strcat(), or better, use strncpy(), in which case you will want to set the last character of path to NULL after you're done.
Use strncat() with PATH_MAX - strlen(path) because otherwise you could overflow path.
Here's an example:
char *resolve_path(const char *filename) {
printf("trying to resolve path...\n");
char *path_var = getenv("PATH");
tok_t *path_list = get_toks(path_var);
char *path = malloc(PATH_MAX+1); // See, no cast, and the +1 is for the NULL
if (!path) {
return NULL; // Check for failure
}
// strncpy/strncat won't null-terminate path if we run out of space
path[PATH_MAX] = NULL;
for (int i = 0; path_list[i]; i++) {
// this could be done more efficiently with memcpy/hand-coding/etc
strncpy(path, path_list[i], PATH_MAX); // don't dereference it
strncat(path, filename, PATH_MAX - strlen(path));
printf("Trying... %s\n", path);
if (file_exists(path)) {
return path;
}
}
free(path);
return NULL;
}

I'm expounding on #Iskar_Jarak's answer because using that code as-is still results in a segmentation fault.
Because tok_t is just a typedef for char*, it sort of muddles your code. get_toks() is (without typedef's) technically just returning a char**.
That means this line:
strncpy(path, *path_list[i], PATH_MAX);
should actually be
strncpy(path, path_list[i], PATH_MAX);
Because you shouldn't dereference path_list[i]. If you do, you pass a char tostrncpy when it takes a char*. That's why you're getting that warning and a seg fault.
So, for what it's worth, here're my corrections to your code:
char *resolve_path(const char *filename) {
printf("trying to resolve path...\n");
char *path_var = getenv("PATH");
tok_t *path_list = get_toks(path_var);
char *path = malloc(PATH_MAX+1);
if (path == NULL) {
return NULL; // malloc failed
}
path[PATH_MAX] = '\0'; // null terminate path
for (int i = 0; path_list[i]; i++) {
size_t len = strlen(path_list[i]); // get the length of this path
strncpy(path, path_list[i], PATH_MAX); // copy path_list to path
path[len] = '/'; // add seperator
strncat(path, filename, PATH_MAX - len - 1); // -1 because of separator
printf("Trying... %s\n", path);
if (file_exists(path)) {
return path;
}
}
free(path);
return NULL;
}

Related

Strange behaviour when implementing strcat in C

I'm trying to implement the strcat function myself. Here's my code:
char *my_strcat(char *dst, const char* src) {
char *tmp = dst;
while(*tmp) {
tmp ++;
}
while(*tmp++ = *src++);
return dst;
}
However, when I try to test this function, I get only the src string. (i.e. I'm expecting to get dst+src, but the returned value is the same as src)
As we can tell from the code, in the first while loop, I'm trying to move tmp pointer to the end of dst.
However,
I tried to add a printf in the first while loop, nothing is printed out, which indicates that it didn't even entered the loop.
Then I tried to used if (*tmp == '\0') print something, and found that *tmp == '\0' is true.
But I'm pretty sure that tmp is a non-empty string, so *tmp should point to the first character of the string (I think). So I'm feeling confused about it. Can anyone tell why this happens and how can I fix it?
Edit:
The calling code is
char *subpath = "";
subpath = my_strcat(subpath, path);
path is a const char* read from the command.
Do I need to assign the value back to subpath? Or just calling my_strcat is enough?
There are two problems here:
char *subpath = "";
subpath = my_strcat(subpath, path);
subpath points to a string of length 0, there is not enough space to append anything.
subpath points to a string literal and writing into string literals is undefined behaviour, on modern desktop platforms your program usually crashes.
You want something like this:
char subpath[100] = "";
char *foo;
foo = my_strcat(subpath, path);
or just
char subpath[100] = "";
my_strcat(subpath, path);
or
char subpath[100] = "Hello ";
my_strcat(subpath, "world!");
printf("%s\n", subpath); // prints Helllo World!

Getting bus error in function attempting to set character to '\0' in C string

I need to write a function in C that, given a path such as /usr/bin/wc, returns the parent directory of the file (this one would return /usr/bin). This is my attempt at doing this:
char *get_parent_dir(char *path) {
char *head = path;
int i = strlen(path) - 1;
while (i > 0) {
if (path[i] == '/') {
path[i] = '\0';
break;
}
i--;
}
return head;
}
However, upon running this with a test path, I get the following error: Bus error: 10.
Can anyone explain why this is, and what I could change to avoid getting this error?
Edit: my usage of the function is something like
char *full_path = "/usr/bin/wc";
get_parent_dir(full_path);
You are effectively passing a string literal as argument to get_parent_dir,
which reseluts in that the function tries to modify a string literal (that normally can't be modified). In the standard this means undefined behaviour (which means that the observed behavior is in line with what the standard prescribes). The exact symptom of this is depending on your operating system (and the compiler), here I get segmentation fault instead.
Already the modification of the parameter is questionable even if it were passed a mutable string. Normally one doesn't expect a function to modify strings that are passed to them. Instead I'd suggest that you allocate new space for the parent directory (but that too requires some care since the caller then is expected to free the result when done with it):
char *get_parent_dir(char *path) {
char *head = strdup(path);
int i = strlen(path) - 1;
if( head == NULL )
return NULL;
path = head;
while (i > 0) {
if (path[i] == '/') {
path[i] = '\0';
break;
}
i--;
}
return head;
}
regarding this line:
char *full_path = "/usr/bin/wc";
is setting char pointer full_path to point to a string literal.
Trying to change a string literal results in a seg fault event.
Try this instead:
char full_path[] = "/usr/bin/wc";
Which places the string in the local array full_path, where it can be modified.

Return a string on C

I'm getting a core dump that I have no clue how to solve. I have searched other questions and googled my problem but I just can't figure out how to solve this...
Here is the code:
const char checkExtension(const char *filename)
{
const char *point = filename;
const char *newName = malloc(sizeof(filename-5));
if((point = strrchr(filename,'.palz')) != NULL )
{
if(strstr(point,".palz") == 0)
{
strncpy(newName, filename, strlen(filename)-5);
printf("%s\n",newName ); // the name shows correctly
return newName; // Segmentation fault (core dumped)
}
}
return point;
}
The function was called char checkExtensions(const char *filename). I added the const due the solutions that I have found online but so far I haven't been able to make it work...
Thank you in advance for the help!
You have many problems with your code. Here are some of them:
Your function returns char which is a single character. You need to return a pointer to an array of characters, a C string.
You don't allocate the right amount of memory. You use sizeof() on a pointer which yields the size of a pointer.
You make it impossible for the caller to know whether or not to deallocate memory. Sometimes you heap allocate, sometimes not. Your approach will leak.
You pass '.palz', which is a character literal, to strrchr which expects a single char. What you mean to pass is '.'.
A better approach is to let the caller allocate the memory. Here is a complete program that shows how:
#include <string.h>
#include <stdio.h>
void GetNewFileName(const char *fileName, char *newFileName)
{
const char *dot = strrchr(fileName, '.');
if (dot)
{
if (strcmp(dot, ".palz") == 0)
{
size_t len = dot - fileName;
memcpy(newFileName, fileName, len);
newFileName[len] = 0;
return;
}
}
size_t len = strlen(fileName);
memcpy(newFileName, fileName, len);
newFileName[len] = 0;
return;
}
int main(void)
{
char fileName[256];
char newFileName[256];
strcpy(fileName, "foo.bar");
GetNewFileName(fileName, newFileName);
printf("%s %s\n", fileName, newFileName);
strcpy(fileName, "foo.bar.palz");
GetNewFileName(fileName, newFileName);
printf("%s %s\n", fileName, newFileName);
strcpy(fileName, "foo.bar.palz.txt");
GetNewFileName(fileName, newFileName);
printf("%s %s\n", fileName, newFileName);
return 0;
}
Output
foo.bar foo.bar
foo.bar.palz foo.bar
foo.bar.palz.txt foo.bar.palz.txt
Note that strcmp compares sensitive to letter case. On Windows file names are insensitive to case. I will leave that issue for you to deal with.
By letting the caller allocate memory you allow them to chose where the memory is allocated. They can use a local stack allocated buffer if they like. And it's easy for the caller to allocate the memory because the new file name is never longer than the original file name.
This is most probably your problem:
const char *newName = malloc(sizeof(filename-5));
First, filename is of type const char *, which means that (filename - 5) is also of this type. Thus, sizeof(filename - 5) will always return the size of the pointer datatype of your architecture (4 for x32, 8 for x64).
So, depending on your architecture, you are calling either malloc(4) or malloc(8).
The rest of the code doesn't even compile and it has serious string manipulation issues, so it's hard to tell what you were aiming at. I suppose the strncpy() was copying too much data into newName buffer, which caused buffer overflow.
If your goal was to extract the filename from a path, then you should probably just use char *basename(char *path) for that.
Several pretty major problems with your code. Making it up as I type, so it may not fix everything first time right away. Bear with me.
You need to return a char *, not a char.
const char checkExtension(const char *filename)
{
const char *point = filename;
You malloc memory but the instruction flow does not guarantee it will be freed or returned.
sizeof(filename) should be strlen(filename), minus 5 (sans extension) but +1 (with terminating 0).
const char *newName = malloc(sizeof(filename-5));
strrchr searches for a single character. Some compilers allow "multibyte character constants", but they expect something like 2 -- not five. Since you know the length and start of the string, use strcmp. (First ensure there are at least 5 characters. If not, no use in testing anyway.)
if((point = strrchr(filename,'.palz')) != NULL ) {
Uh, strstr searches for a string inside a string and returns 0 if not found (actually NULL). This contradicts your earlier test. Remove it.
if(strstr(point,".palz") == 0)
{
strncpy copies n characters, but famously (and documented) does not add the terminating 0 if it did not get copied. You will have to this yourself.
.. This is actually where the malloc line should appear, right before using and returning it.
strncpy(newName, filename, strlen(filename)-5);
printf("%s\n",newName ); // the name shows correctly
return newName; // Segmentation fault (core dumped)
}
}
You return the original string here. How do you know you need to free it, then? If you overwrote a previous char * its memory will be lost. Better to return a duplicate of the original string (so it can always be freed), or, as I'd prefer, return NULL to indicate "no further action needed" to the calling routine.
return point;
}
Hope I did not forget anything.
There are several problems with your code:
Wrong return type:
const char checkExtension(const char *filename){
You need to return a pointer (const char *), not a single character.
Not enough memory:
const char checkExtension(const char *filename){
const char *newName = malloc(sizeof(filename-5));
You are allocating the size of a pointer (char *), which is typically 4 or 8. You need to call strlen() to find out the size of the string:
Multibyte character:
if((point = strrchr(filename,'.palz')) != NULL ) {
'.palz' is a multibyte character literal. While this is allowed in C, its value is implementation-defined and might not do what you expect. String literals use double quotes (".palz").
No terminating zero:
strncpy(newName, filename, strlen(filename)-5);
Note that strncpy() doesn't necessarily null-terminate the target string. It write at most strlen(filename)-5 characters. If the source string contains more characters (as in your case), it will not write a terminating zero.
I'm not sure what exactly you're trying to do. Perhaps something like this:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
const char *checkExtension(const char *filename)
{
int len = strlen (filename)-5;
char *newName = NULL; /* return NULL on allocation failure. */
if (len > 0 && !strcmp (filename+len, ".palz")) {
newName = malloc (len+1);
if (newName) {
memcpy (newName, filename, len);
newName[len] = 0;
}
}
return newName;
}
int main (int ac, char **av)
{
if (ac > 1) {
const char *p = checkExtension (av[1]);
puts (p ? p : "NULL");
} else {
puts ("?");
}
return 0;
}
Multiple errors here. You have not said what you are trying to achieve, that has to be implied from the code. You have declared point and newName as const, yet reassigned with a value. You have tested strstr() == 0 when it should be strstr() == NULL. You have called strrchr(filename,'.palz') but sent a string instead of a char. Then you have returned the local variable point which goes out of scope before you get a chance to use it, because it was not declared as static. So it's irrelevant whether you returned a char or a char pointer.
char *checkExtension(const char *filename) {
// if filename has extension .palz return a pointer to
// the filename stripped of extension or return NULL
char *point;
static char newName[512];
strncpy(newName, filename, 512);
if ((point = strstr(newName, ".palz")) != NULL ) {
if (strlen (point) == 5) {
*point = 0; // string terminator
// printf("%s\n",newName ); // use only for debugging
return newName;
}
}
return NULL;
}
Alternatively provide a string the function can modify -
char *checkExtension(const char *filename, char *newName) { ... }
Alternatively provide a filename the function can modify -
char *checkExtension(char *filename) {
char *point;
if ((point = strstr(filename, ".palz")) != NULL ) {
if (strlen (point) == 5) {
*point = 0; // string terminator
return filename;
}
}
return NULL;
}

C: Loading a file name into a structure via argv[] but I don't want the filetype (e.g., .txt)?

I'm trying to create a data-structure:
typedef struct node_ {
char* fName;
char* lName;
char* origin;
char* destination;
int seatNumber;
struct node_* next;
} Seat;
by scanning in lines from .txt documents. The origin is the name of the document, e.g., chicago.txt.
I've just been sending argv[i] (which, again, was the name of the file, e.g., chicago.txt) to my function for the origin. Which has worked, but...
Now I need to check to see if the origin is the destination, but the destination is not formatted with .txt. (e.g., chicago.txt != chicago). I considered using strncmp for the first 3 letters or so, but that doesn't seem like a great solution. Is there a simple way to mod the argv[] to not contain .txt before sending it to the function in question? Or is there a better way to solve this that I haven't considered?
Don't try altering the arguments passed. Copy them to a local variable first. This example removes the .txt extension from the argument provided.
#include <stdio.h>
#include <string.h>
char fname [100];
int main (int argc, char *argv[]) {
char *xptr;
if (argc < 2) return 1;
strncpy (fname, argv[1], 99);
strlwr (fname);
xptr = strstr (fname, ".txt");
strncpy (fname, argv[1], 99); // copy arg again to restore orig case
if (xptr != NULL)
*xptr = 0; // truncate string
printf ("%s\n", fname);
return 0;
}
You don't need to (and shouldn't) modify anything, it is easy to just check the characters you care about, as shown below:
Given str1 is the string with a . and str2 is the one we want to compare:
int result = 0; // will be 1 if same
char *found;
found = strchr(str1,'.');
if (found)
{
if (strncmp(str1,str2,found-str1) == 0)
result = 1;
}
Note I did not test so you might need to add 1 to the expression found-str1
How this works: In C you can do pointer arithmetic strchr finds where the . is and we then figure out how long a string to compare with strncmp.

Simple C string manipulation

I trying to do some very basic string processing in C (e.g. given a filename, chop off the file extension, manipulate filename and then add back on the extension)- I'm rather rusty on C and am getting segmentation faults.
char* fname;
char* fname_base;
char* outdir;
char* new_fname;
.....
fname = argv[1];
outdir = argv[2];
fname_len = strlen(fname);
strncpy(fname_base, fname, (fname_len-4)); // weird characters at the end of the truncation?
strcpy(new_fname, outdir); // getting a segmentation on this I think
strcat(new_fname, "/");
strcat(new_fname, fname_base);
strcat(new_fname, "_test");
strcat(new_fname, ".jpg");
printf("string=%s",new_fname);
Any suggestions or pointers welcome.
Many thanks and apologies for such a basic question
You need to allocate memory for new_fname and fname_base. Here's is how you would do it for new_fname:
new_fname = (char*)malloc((strlen(outdir)+1)*sizeof(char));
In strlen(outdir)+1, the +1 part is for allocating memory for the NULL CHARACTER '\0' terminator.
In addition to what other's are indicating, I would be careful with
strncpy(fname_base, fname, (fname_len-4));
You are assuming you want to chop off the last 4 characters (.???). If there is no file extension or it is not 3 characters, this will not do what you want. The following should give you an idea of what might be needed (I assume that the last '.' indicates the file extension). Note that my 'C' is very rusty (warning!)
char *s;
s = (char *) strrchr (fname, '.');
if (s == 0)
{
strcpy (fname_base, fname);
}
else
{
strncpy (fname_base, fname, strlen(fname)-strlen(s));
fname_base[strlen(fname)-strlen(s)] = 0;
}
You have to malloc fname_base and new_fname, I believe.
ie:
fname_base = (char *)(malloc(sizeof(char)*(fname_len+1)));
fname_base[fname_len] = 0; //to stick in the null termination
and similarly for new_fname and outdir
You're using uninitialized pointers as targets for strcpy-like functions: fname_base and new_fname: you need to allocate memory areas to work on, or declare them as char array e.g.
char fname_base[FILENAME_MAX];
char new_fname[FILENAME_MAX];
you could combine the malloc that has been suggested, with the string manipulations in one statement
if ( asprintf(&new_fname,"%s/%s_text.jpg",outdir,fname_base) >= 0 )
// success, else failed
then at some point, free(new_fname) to release the memory.
(note this is a GNU extension which is also available in *BSD)
Cleaner code:
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
const char *extra = "_test.jpg";
int main(int argc, char** argv)
{
char *fname = strdup(argv[1]); /* duplicate, we need to truncate the dot */
char *outdir = argv[1];
char *dotpos;
/* ... */
int new_size = strlen(fname)+strlen(extra);
char *new_fname = malloc(new_size);
dotpos = strchr(fname, '.');
if(dotpos)
*dotpos = '\0'; /* truncate at the dot */
new_fname = malloc(new_size);
snprintf(new_fname, new_size, "%s%s", fname, extra);
printf("%s\n", new_fname);
return 0;
}
In the following code I do not call malloc.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* Change this to '\\' if you are doing this on MS-windows or something like it. */
#define DIR_SYM '/'
#define EXT_SYM '.'
#define NEW_EXT "jpg"
int main(int argc, char * argv[] ) {
char * fname;
char * outdir;
if (argc < 3) {
fprintf(stderr, "I want more command line arguments\n");
return 1;
}
fname = argv[1];
outdir = argv[2];
char * fname_base_begin = strrchr(fname, DIR_SYM); /* last occurrence of DIR_SYM */
if (!fname_base_begin) {
fname_base_begin = fname; // No directory symbol means that there's nothing
// to chop off of the front.
}
char * fname_base_end = strrchr(fname_base_begin, EXT_SYM);
/* NOTE: No need to search for EXT_SYM in part of the fname that we have cut off
* the front and then have to deal with finding the last EXT_SYM before the last
* DIR_SYM */
if (!fname_base_end) {
fprintf(stderr, "I don't know what you want to do when there is no extension\n");
return 1;
}
*fname_base_end = '\0'; /* Makes this an end of string instead of EXT_SYM */
/* NOTE: In this code I actually changed the string passed in with the previous
* line. This is often not what you want to do, but in this case it should be ok.
*/
// This line should get you the results I think you were trying for in your example
printf("string=%s%c%s_test%c%s\n", outdir, DIR_SYM, fname_base_begin, EXT_SYM, NEW_EXT);
// This line should just append _test before the extension, but leave the extension
// as it was before.
printf("string=%s%c%s_test%c%s\n", outdir, DIR_SYM, fname_base_begin, EXT_SYM, fname_base_end+1);
return 0;
}
I was able to get away with not allocating memory to build the string in because I let printf actually worry about building it, and took advantage of knowing that the original fname string would not be needed in the future.
I could have allocated the space for the string by calculating how long it would need to be based on the parts and then used sprintf to form the string for me.
Also, if you don't want to alter the contents of the fname string you could also have used:
printf("string=%s%c%*s_test%c%s\n", outdir, DIR_SYM, (unsigned)fname_base_begin -(unsigned)fname_base_end, fname_base_begin, EXT_SYM, fname_base_end+1);
To make printf only use part of the string.
The basic of any C string manipulation is that you must write into (and read from unless... ...) memory you "own". Declaring something is a pointer (type *x) reserves space for the pointer, not for the pointee that of course can't be known by magic, and so you have to malloc (or similar) or to provide a local buffer with things like char buf[size].
And you should be always aware of buffer overflow.
As suggested, the usage of sprintf (with a correctly allocated destination buffer) or alike could be a good idea. Anyway if you want to keep your current strcat approach, I remember you that to concatenate strings, strcat have always to "walk" thourgh the current string from its beginning, so that, if you don't need (ops!) buffer overflow checks of any kind, appending chars "by hand" is a bit faster: basically when you finished appending a string, you know where the new end is, and in the next strcat, you can start from there.
But strcat doesn't allow to know the address of the last char appended, and using strlen would nullify the effort. So a possible solution could be
size_t l = strlen(new_fname);
new_fname[l++] = '/';
for(i = 0; fname_base[i] != 0; i++, l++) new_fname[l] = fname_base[i];
for(i = 0; testjpgstring[i] != 0; i++, l++) new_fname[l] = testjpgstring[i];
new_fname[l] = 0; // terminate the string...
and you can continue using l... (testjpgstring = "_test.jpg")
However if your program is full of string manipulations, I suggest using a library for strings (for lazyness I often use glib)

Resources