I am writing a utility function for a c program which creates directories, but will also create any intermediate directories which do not exist. I have that code working, but I wanted to turn my attention to improve user input formatting. Namely, I want to regularize the input path before I process it, especially to remove // which will break my current implementation.
I am aware of realpath(3), but my main issue is that it will fail if any of the directories on the path do not already exist. The command line function realpath(1) has the -m option, which seems to do what I want, but I don't want to invoke a shell if I can avoid it (Otherwise I could do mkdir -p and be done with it). Gnu findutils/canonicalize.h has canonicalize_filename_mode, but I don't know how to reference that short of copying the source directly (not out of the question). Thoughts, suggestions?
I am tied to my development environment gcc 4.7.7and rhel 6.6.
Below is my current implementation.
static int do_mkdir(const char *path, mode_t mode) {
struct stat st;
int status = 0;
if ( stat(path, &st) != 0 ) {
errno = 0;
if (mkdir(path, mode) != 0 && errno != EEXIST)
status = -1;
} else if ( !S_ISDIR(st.st_mode) ) {
errno = ENOTDIR;
status = -1;
}
return(status);
}
int mkpath(const char *path, mode_t mode) {
char *pp;
char *sp;
int status;
char copypath[PATH_MAX+1];
copypath = realpath(path, copypath);
status = 0;
pp = copypath;
if(copypath == NULL)
status = -1;
while ( status == 0 && (sp = strchr(pp, '/')) != 0 ) {
if (sp != pp) {
*sp = '\0';
status = do_mkdir(copypath, mode);
*sp = '/';
}
pp = sp + 1;
}
if ( status == 0 )
status = do_mkdir(path, mode);
return (status);
}
It might not fully answer your issue but here is an alternative:
You could process the path string with strtok, setting delim argument to '/'.
By doing so, strtok will return you intermediate directory names (null-terminated: you can get rid off the \0 tweak) and it will gracefully ignore //.
You can then add your "sanitize" checks (look for invalid characters, path name is too long, etc...)
I found a workaround that I believe covers all cases. What I did, was to move the call to realpath into the do_mkdir function so that way it will only have at most one non-existent element, replacing it with a strdup in the mkpath function. I can't think of a situation where performing the canonicalization on a partial path will break anything either.
Related
I'm trying to implement the ls command in C with a few parameters like -a, -l... or -la, but I'm having issues with the parsing, when I use the input I get Segmentation Fault, this is an example of the -a parameter:
int comparator(char *av) {
int i = 0;
if (my_strcmp((av[i]), "-a") == 0)
return 0;
else
return 1;
}
int my_ls_a(char *path) {
int comp = comparator(path);
DIR *pdirec = opendir(".");
struct dirent *direc;
direc = readdir(pdirec);
while (direc != NULL || comp == 0) {
my_printf("%s ", direc->d_name);
direc = readdir(pdirec);
}
if ((path = readdir(pdirec)) == NULL)
my_printf("\n");
if (pdirec == NULL)
return (84);
closedir(pdirec);
return (0);
}
And this is my main:
int main(int ac, char *av[]) {
if (ac == 1)
my_ls_a(av[0]);
return 0;
}
I already have all the #include in a .h by the way.
When I only use the main function it works but not when I add the parameter -a.
It's probably better to use getopt() for parameter parsing instead of writing your own parser.
You have undefined behavior in the function comparator in my_strcmp((av[i]), "-a") because av is defined as a char * so you are passing a character where my_strcmp probably expects a pointer.
You should compile with -Wall -Werror to avoid such silly mistakes.
It is unclear why you pass only a single argument to my_ls_a. You should pass both ac and the argument array av and iterate on the arguments to parse the options.
I was working on my game and decided to use eclipse as my compiler. I had to compile it for both platforms: x86 and x64. The trouble started there. There are many dependency files in the system path.
And every time I had to change them in order to change the platform. So, I've created a line to set up my configurations faster and without affect the path itself.
This is the line to add into the path that I've created:
%DRIVE%\mingw\mingw%PLATFORM%\bin;%DRIVE%\Dropbox\Machine\Windows\C\Place\bin\x%PLATFORM%;%DRIVE%\Dropbox\Machine\Windows\C\PLUGIN\x%PLATFORM%\bin;
As you guys can see there are two variables there: %DRIVE% and %PLATFORM%.
I wish to change them with a file that I try to create in c.
Here is the code
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
char *strremove(char *str, const char *sub) {
char *p, *q, *r;
if ((q = r = strstr(str, sub)) != NULL) {
size_t len = strlen(sub);
while ((r = strstr(p = r + len, sub)) != NULL) {
while (p < r)
*q++ = *p++;
}
while ((*q++ = *p++) != '\0')
continue;
}
return str;
}
#ifndef HAVE_SETENV
int setenv(const char * variable,const char * value) {
if(!variable || !value)return(0);
int len = strlen(variable)+1+strlen(value)+1;
char * EnvString = calloc(len,sizeof(char));
sprintf(EnvString, "%s=%s", variable, value);
if (!_putenv(EnvString)) {
return (1);
}
if(EnvString)free(EnvString);
return (0);
}
#endif
void change_platform(int argc,char ** argv) {
char * variable = "PLATFORM",* value = "86";
if(argc > 1){
value = argv[1];
}
if (setenv(variable, value)) {
printf("\n environmental variable successfully written");
printf("\n value of the environmental variable written is %s",
getenv(variable));
} else {
printf("\n error in writing the environmental variable");
}
}
int main(int argc, char ** argv) {
change_platform(argc,argv);
getch();
return 0;
}
My code shows the right result inside the program, but when I go and check the system environment itself, nothing changes. Am I doing something wrong.
Detail: I thought it was because of mingw which isn't native from Windows, then I've created I file in Visual c++ too, but it did not work either.
Please remember it affects only the environment of the current process
getenv, _wgetenv
int main( void )
{
char *libvar;
// Get the value of the LIB environment variable.
libvar = getenv( "LIB" ); // C4996
// Note: getenv is deprecated; consider using getenv_s instead
if( libvar != NULL )
printf( "Original LIB variable is: %s\n", libvar );
// Attempt to change path. Note that this only affects the environment
// variable of the current process. The command processor's
// environment is not changed.
_putenv( "LIB=c:\\mylib;c:\\yourlib" ); // C4996
// Note: _putenv is deprecated; consider using putenv_s instead
// Get new value.
libvar = getenv( "LIB" ); // C4996
if( libvar != NULL )
printf( "New LIB variable is: %s\n", libvar );
}
If I use just "/home/user/.some/qwe" instead of homedir->str, it works.
struct stat st = {0};
GString* homedir = g_string_new(NULL);
g_string_append(homedir, getenv("HOME"));
g_string_append(homedir, ".some/qwe");
printf("%s", homedir->str);
if (stat(homedir->str, &st) == -1) {
int res= mkdir(homedir->str, 0777);
g_string_free(homedir, TRUE);
}
You would be able to avoid this bug and always build valid path names by using g_build_filename():
char *homedir = g_build_filename(getenv("HOME"), ".some", "qwe", NULL);
printf("%s, homedir);
if (stat(homedir, &st) == -1) {
int res = mkdir(homedir, 0777);
}
g_free(homedir);
Normally, $HOME does not end with /. So, for your case, HOME = /home/user, and string for mkdir would look like: /home/user.some/qwe (notice missing /).
Try changing your code to add that missing slash, for example use:
g_string_append(homedir, "/.some/qwe");
With that said, all you really needed is to know how to debug - just printing value of your string to stdout would make it immediately obvious.
I'm trying to call execv after manually saerching for the program to execute.
In my case,
c is a struct which has args as an array of strings having the arguments passed while receiving input. nargs is the number of arguments.
c->args[0] would contain "ls","cat" etc.
I tried printing the value of the args[0], fullPath etc. in my child process. They all show values like "/bin/ls","/bin/cat" etc. But when I call execv, it returns -1 with an errno of 2, which I understand is the error for "No such file or directory". But I'm sure the file is there because thats what my PathResolver is returning after checking all permissions.
Can anyone point where I might have made a mistake.
//The part happening inside child
char *fullPath = PathResolver(c->args[0],1,&permission);
printf("FullPath: %s -- Permission: %d\n",fullPath,permission);
if(permission==0)
{
fprintf(stderr, "%s: Command not found\n",c->args[0]);
}
else if(permission==-1)
{
fprintf(stderr, "%s: Permission denied\n",c->args[0]);
}
else
{
char* args[c->nargs+1];
int m=0;
for(m=0;m<c->nargs;m++)
{
strcpy(args[m],c->args[m]);
}
args[c->nargs] = NULL;
printf("%d\n",execv(args[0], args));
printf("errno: %d\n",errno);
}
PathResolver function
char* PathResolver(char *command, int ResolverMode, int *Permission)
{
*Permission = 0;
char *returnString;
returnString = malloc((sizeof(char)));
char *strPath = getenv("PATH");
char *del = ":";
char *strToken = strtok(strPath,del);
FILE *f;
while(strToken)
{
char filePath[100];
sprintf(filePath,"%s/%s",strToken,command);
if(access(filePath,F_OK)>=0)
{
if(access(filePath,X_OK)>=0)
{
*Permission = 1;
sprintf(returnString,"%s%s ",returnString,filePath);
if(ResolverMode == 1)
break;
}
else
{
*Permission = -1;
}
}
strToken = strtok(NULL,del);
}
sprintf(returnString,"%s\b",returnString);
return returnString;
}
strcpy(args[m],c->args[m]); is undefined behaviour, because args[m] is not a pointer to valid memory.
The following might be simpler:
char * args[c->nargs + 1];
for (size_t m = 0; m != c->nargs; ++m)
{
args[m] = c->args[m];
}
args[c->nargs] = NULL;
There's no need to copy the strings.
(This may not be your actual problem, but it certainly prevents your program from being correct.)
execv() expects the program name to be prefixed by a full path as 1st parameter.
To have PATH searched instead of providing a path use execvp().
Update:
Also this line
returnString = malloc((sizeof(char)));
does only allocate 1 byte to returnString, which is way to few for how you use returnString.
I've got an library which must read data from sqlite database by calling
extern int read(char** argv, int argc); // from header
it must process:
int read(char** argv, int argc) {
char* lineborder1;
char* lineborder2;
char* spaces1;
char* spaces2;
int maxl2 = 0, maxl1 = 0;
int i, maxi1, maxi2;
if (prelude() == -1) return -1;
// etc...
where prelude is inner procedure for sqlite connection:
int prelude() {
timefile = 0;
f = NULL;
#ifndef WIN32
char* temp = (char*)calloc(200, sizeof(char));
#endif
queries = (char**)malloc(sizeof(char*) * q_cnt);
for (x = 0; x < q_cnt; x++) {
queries[x] = (char*)malloc(sizeof(char) * q_size);
}
#ifdef WIN32
retval = sqlite3_open("todo.db3", &handle);
#else
home = (char*)getenv("HOME");
strcpy(temp, home);
retval = sqlite3_open(strcat(temp, "/.todo.db3"), &handle);
free(temp);
#endif
if (retval) {
printf("Database connection failed\n\r");
return -1;
}
return 0;
}
whole source is here : bitbucket: ctodo.c
I call this read from my application:
else if ((strcmp(argv[1], "read") == 0) || (strcmp(argv[1], "r") == 0)) {
return read(argv, argc);
but getting infinity cycle of this read call:
>>./todo r
Database connection failed
Database connection failed
Database connection failed
.... etc
here is how do I build it:
gcc -I . -c -o todo.a ctodo.c -lsqlite3 -O3
gcc -I . -o todo cctodo.c -lsqlite3 -O3 todo.a
help or version calls wrok fine and read works fine on windows, something specific to my linux build is here but I can't find a bug so far. what could call this read to run in infinity cycle like that?
The read function is defined in libc.so, and used to, you know, read data.
It is exceedingly likely that sqlite3_open() calls it.
By introducing your own function called read(), you've made your program enter infinite loop. If you wait long enough, your program will run out of stack and crash.
Doctor, it hurts when I do that. Well, don't do that :-)