I am working on modifying the didactic OS xv6 (written in c) to support symbolic links (AKA shortcuts).
A symbolic link is a file of type T_SYM that contains a path to it's destination.
For doing that, i wrote a recursive function that gets a path and a buffer and fills the buffer with the "real" path (i.e. if the path contains a link, it should be replaced by the real path, and a link can occur at any level in the path).
Basically, if i have a path a/b/c/d, and a link from f to a/b, the following operations should be equivalent:
cd a/b/c/d
cd f/c/d
Now, the code is written, but the problem that i try to solve is the problem of starting the path with "/" (meaning that the path is absolute and not relative).
Right now, if i run it with a path named /dir1 it treats it like dir1 (relative instead of absolute).
This is the main function, it calls the recursive function.
pathname is the given path, buf will contain the real path.
int readlink(char *pathname, char *buf, size_t bufsize){
char name[DIRSIZ];
char realpathname[100];
memset(realpathname,0,100);
realpathname[0] = '/';
if(get_real_path(pathname, name, realpathname, 0, 0)){
memmove(buf, realpathname, strlen(realpathname));
return strlen(realpathname);
}
return -1;
}
This is the recursive part.
the function returns an inode structure (which represents a file or directory in the system). it builds the real path inside realpath.
ilock an iunlock are being used to use the inode safely.
struct inode* get_real_path(char *path, char *name, char* realpath, int position){
struct inode *ip, *next;
char buf[100];
char newpath[100];
if(*path == '/')
ip = iget(ROOTDEV, ROOTINO);// ip gets the root directory
else
ip = idup(proc->cwd); // ip gets the current working directory
while((path = skipelem(path, name)) != 0){name will get the next directory in the path, path will get the rest of the directories
ilock(ip);
if(ip->type != T_DIR){//if ip is a directory
realpath[position-1] = '\0';
iunlockput(ip);
return 0;
}
if((next = dirlookup(ip, name, 0)) == 0){//next will get the inode of the next directory
realpath[position-1] = '\0';
iunlockput(ip);
return 0;
}
iunlock(ip);
ilock(next);
if (next->type == T_SYM){ //if next is a symbolic link
readi(next, buf, 0, next->size); //buf contains the path inside the symbolic link (which is a path)
buf[next->size] = 0;
iunlockput(next);
next = get_real_path(buf, name, newpath, 0);//call it recursively (might still be a symbolic link)
if(next == 0){
realpath[position-1] = '\0';
iput(ip);
return 0;
}
name = newpath;
position = 0;
}
else
iunlock(next);
memmove(realpath + position, name, strlen(name));
position += strlen(name);
realpath[position++]='/';
realpath[position] = '\0';
iput(ip);
ip = next;
}
realpath[position-1] = '\0';
return ip;
}
I have tried many ways to do it right but with no success. If anyone sees the problem, i'd be happy to hear the solution.
Thanks,
Eyal
I think it's clear that after running get_real_path(pathname, name, realpathname, 0, 0) the realpathname cannot possibly start with a slash.
Provided the function executes successfully, the memmove(realpath + position, name, strlen(name)) ensures that realpath starts with name, as the position variable always contains zero at the first invocation of memmove.
I'd suggest something like
if(*path == '/') {
ip = iget(ROOTDEV, ROOTINO); // ip gets the root
realpath[position++] = '/';
} else
ip = idup(proc->cwd); // ip gets the current working directory
P.S. I'm not sure why you put a slash into the realpathname before executing the get_real_path, since at this point you don't really know whether the path provided is an absolute one.
Ok, found the problem...
The problem was deeper than what i thought...
Somehow the realpath was changed sometimes with no visible reason... but the reason was the line:
name = newpath;
the solution was to change that line to
strcpy(name,newpath);
the previous line made a binding between the name and the realpath... which can be ok if we were not dealing with softlinks. When dereferencing a subpath, this binding ruined everything.
Thanks for the attempts
Related
I'm looking for an efficient way to convert absolute filepath to a path relative to a specific directory.
Let's say we have to following structure:
D:\main\test1\blah.txt
D:\test2\foo.txt
With "D:\main" being the reference directory, then result would be:
blah.txt => "\test1\blah.txt"
foo.txt => "..\test2\foo.txt"
Any clue ?
Notes for the record:
It seems that:
there is no unified API function (cross-platform) for performing this
this question has been asked various times for other languages (though most answers take advantage of function PathRelativePathTo):
How to get relative path from absolute path
Getting a file path relative to a particular directory
How do I get a relative path from one path to another in C#
You are giving windows paths in your example. So, if it is acceptable for you to use the WinAPI functions, you can use PathRelativePathTo.
Here is the shortest solution I could figure out.
Algorithm is actually quite simple:
Given 1) a reference path (path to which result path will be relative to); and 2) an absolute path (full path of a file) :
while path parts are equals : skip them
when we come across a difference
add a ".." for each remaining part of reference path
add remaining parts from absolute path
The only limitation under windows is in case of distinct volume (drive letters differ), in which situation we have no choice but to return the original absolute path.
Cross-platform C source :
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
const char* path_separator = "\\";
#else
const char* path_separator = "/";
#endif
#define FILENAME_MAX 1024
char* get_relative_path(char* reference_path, char* absolute_path) {
static char relative_path[FILENAME_MAX];
// init result string
relative_path[0] = '\0';
// check first char (under windows, if differs, we return absolute path)
if(absolute_path[0] != reference_path[0]) {
return absolute_path;
}
// make copies to prevent altering original strings
char* path_a = strdup(absolute_path);
char* path_r = strdup(reference_path);
int inc;
int size_a = strlen(path_a)+1;
int size_r = strlen(path_r)+1;
for(inc = 0; inc < size_a && inc < size_r; inc += strlen(path_a+inc)+1) {
char* token_a = strchr(path_a+inc, path_separator[0]);
char* token_r = strchr(path_r+inc, path_separator[0]);
if(token_a) token_a[0] = '\0';
if(token_r) token_r[0] = '\0';
if(strcmp(path_a+inc, path_r+inc) != 0) break;
}
for(int inc_r = inc; inc_r < size_r; inc_r += strlen(path_r+inc_r)+1) {
strcat(relative_path, "..");
strcat(relative_path, path_separator);
if( !strchr(reference_path+inc_r, path_separator[0]) ) break;
}
if(inc < size_a) strcat(relative_path, absolute_path+inc);
return relative_path;
}
First thing you need to identify the file path separator as stated here.
const char kPathSeparator =
#ifdef _WIN32
'\\';
#else
'/';
#endif
Then, you need to write a function to compute a canonical absolute file path. You will have to use #ifdef _WIN32 again because there are specific windows treatment required (add current disk at the begining of path if none is present).
After that, remove all . in the path, and remove all .. with their previous directory.
Once this function is written, you need to use it twice to get your origin and target canonical absolute paths, and then as explains #Weather Vane you need to identify the common part in the two paths and to add the number of .. concatenated to the end of the target canonical path.
I have function:
gint isfileexists(gchar *filename)
{
struct stat buffer;
gint i = stat(filename, &buffer);
if (i == 0) {
return 1;
}
return 0;
}
and if I call them:
isfileexists("/etc/myfile")
it search "myfile" in "/home/user/etc/myfile". How to do this well?
It should only look for /home/USER/etc/myfile if:
you leave off the leading / when calling isfileexists; and
that directory /home/USER is your current working directory.
In other words, if the argument is a relative path name.
Since you have the leading /, it will be an absolute path name and should access /etc/myfile.
If I've misunderstood and you actually want the one in your home directory, you can use getenv("HOME") to get your home directory and then append /etc/myfile with strcat. That will also work regardless of your current working directory.
I'm trying to compile libUnihan code with MinGW, but have run into a function which requires porting. The purpose of the function is to get a canonical path representation. It uses pwd.h (which is POSIX, and MinGW isn't) so it can account for the use of '~' to mean the home directory by retrieving a passwd struct, which contains pw_dir. I did find a little information here, and a port of realpath here, but I am still entirely at a loss as to how to deal with this. With MinGW, I still have a home directory represented by ~ and located at /home/nate, but since it isn't POSIX, I don't have pwd.h to help me find where this home directory is.
Q: How can I port the function below to work properly with MinGW?
/**
* Return the canonicalized absolute pathname.
*
* It works exactly the same with realpath(3), except this function can handle the path with ~,
* where realpath cannot.
*
* #param path The path to be resolved.
* #param resolved_path Buffer for holding the resolved_path.
* #return resolved path, NULL is the resolution is not sucessful.
*/
gchar*
truepath(const gchar *path, gchar *resolved_path){
gchar workingPath[PATH_MAX];
gchar fullPath[PATH_MAX];
gchar *result=NULL;
g_strlcpy(workingPath,path,PATH_MAX);
// printf("*** path=%s \n",path);
if ( workingPath[0] != '~' ){
result = realpath(workingPath, resolved_path);
}else{
gchar *firstSlash, *suffix, *homeDirStr;
struct passwd *pw;
// initialize variables
firstSlash = suffix = homeDirStr = NULL;
firstSlash = strchr(workingPath, DIRECTORY_SEPARATOR);
if (firstSlash == NULL)
suffix = "";
else
{
*firstSlash = 0; // so userName is null terminated
suffix = firstSlash + 1;
}
if (workingPath[1] == '\0')
pw = getpwuid( getuid() );
else
pw = getpwnam( &workingPath[1] );
if (pw != NULL)
homeDirStr = pw->pw_dir;
if (homeDirStr != NULL){
gint ret=g_sprintf(fullPath, "%s%c%s", homeDirStr, DIRECTORY_SEPARATOR, suffix);
if (ret>0){
result = realpath(fullPath, resolved_path);
}
}
}
return result;
}
The purpose is to implement ~[username]/ remapping logic. This sort of code makes sense in Linux/UNIX environments, but the most common use is just to refer to the user's own home directory.
For expediency, I'd just add support for the common case - ~/ - i.e. the current user, and not bother supporting the more general case - have it fail with an obvious error in that case.
The function to get the current user's home directory is SHGetFolderPath.
#include <windows.h>
char homeDirStr[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, homeDirStr))) {
// Do something with the path
} else {
// Do something else
}
In the case of a failed lookup of the user, the code you pasted does not try to replace that string, but simply returns NULL, so you could emulate that.
I'm trying the open a relative path in Ubuntu , but after opening the first folder - called 14 - the code fails to open the folder inside - called 15:
int pathsCtr; // number of folders in RelativeArray
char ** RelativeArray; // the folders in the relative path, currently:
RelativeArray[0] = "14";
RelativeArray[1] = "15";
// some code before
if (pathsCtr > 0 && flag == TRUE) // then we have a relative path
{
int j = 0;
while (j < pathsCtr) // run until the last path and open every one
{
printf("\n%s\n" , RelativeArray[j]);
dirp = opendir(RelativeArray[j]); // open all directories until the last one
if (dirp == NULL)
return -1;
j++; // proceed to the next directory
}
flag = FALSE; // turn off the flag , we'll never go near this again
}
When j == 0 this line : dirp = opendir(RelativeArray[j]); works and dirp is not NULL.
But when j == 1 that line dirp = opendir(RelativeArray[j]); fails and dirp is NULL .
What am I doing wrong?
EDIT:
Assume that I'm doing malloc for RelativeArray before the above code .
opendir() opens a directory for reading its contents, but it does not change the working directory of the process.
To access a subdirectory, you will have to specify it by its fully path relative to the current working directory (or else its absolute path).
You can probably do this by concatenating your strings with an appropriate separator character.
Since you don't seem to do anything with the directory stream pointer returned by opendir() other than check that it's non-null, there's a good chance this is not the function you want to be using. You may want to look at chdir() instead (man 2 chdir) but do think about any undesired consequences.
I found many examples of CreatingDirectory recursively, but not the one I was looking for.
here is the spec
Given input
\\server\share\aa\bb\cc
c:\aa\bb\cc
USING helper API
CreateDirectory (char * path)
returns true, if successful
else
FALSE
Condition: There should not be any parsing to distinguish if the path is Local or Server share.
Write a routine in C, or C++
I think it's quite easier... here a version that works in every Windows version:
unsigned int pos = 0;
do
{
pos = path.find_first_of("\\/", pos + 1);
CreateDirectory(path.substr(0, pos).c_str(), NULL);
} while (pos != std::string::npos);
Unicode:
pos = path.find_first_of(L"\\/", pos + 1);
Regards,
This might be exactly what you want.
It doesn't try to do any parsing to distinguish if the path is Local or Server share.
bool TryCreateDirectory(char *path){
char *p;
bool b;
if(
!(b=CreateDirectory(path))
&&
!(b=NULL==(p=strrchr(path, '\\')))
){
size_t i;
(p=strncpy((char *)malloc(1+i), path, i=p-path))[i]='\0';
b=TryCreateDirectory(p);
free(p);
b=b?CreateDirectory(path):false;
}
return b;
}
The algorithm is quite simple, just pass the string of higher level directory recursively while creation of current level of directory fails until one success or there is no more higher level. When the inner call returns with succeed, create the current. This method do not parse to determ the local or server it self, it's according to the CreateDirectory.
In WINAPI, CreateDirectory will never allows you to create "c:" or "\" when the path reaches that level, the method soon falls in to calling it self with path="" and this fails, too. It's the reason why Microsoft defines file sharing naming rule like this, for compatibility of DOS path rule and simplify the coding effort.
Totally hackish and insecure and nothing you'd ever actually want to do in production code, but...
Warning: here be code that was typed in a browser:
int createDirectory(const char * path) {
char * buffer = malloc((strlen(path) + 10) * sizeof(char));
sprintf(buffer, "mkdir -p %s", path);
int result = system(buffer);
free(buffer);
return result;
}
How about using MakeSureDirectoryPathExists() ?
Just walk through each directory level in the path starting from the root, attempting to create the next level.
If any of the CreateDirectory calls fail then you can exit early, you're successful if you get to the end of the path without a failure.
This is assuming that calling CreateDirectory on a path that already exists has no ill effects.
The requirement of not parsing the pathname for server names is interesting, as it seems to concede that parsing for / is required.
Perhaps the idea is to avoid building in hackish expressions for potentially complex syntax for hosts and mount points, which can have on some systems elaborate credentials encoded.
If it's homework, I may be giving away the algorithm you are supposed to think up, but it occurs to me that one way to meet those requirements is to start trying by attempting to mkdir the full pathname. If it fails, trim off the last directory and try again, if that fails, trim off another and try again... Eventually you should reach a root directory without needing to understand the server syntax, and then you will need to start adding pathname components back and making the subdirs one by one.
std::pair<bool, unsigned long> CreateDirectory(std::basic_string<_TCHAR> path)
{
_ASSERT(!path.empty());
typedef std::basic_string<_TCHAR> tstring;
tstring::size_type pos = 0;
while ((pos = path.find_first_of(_T("\\/"), pos + 1)) != tstring::npos)
{
::CreateDirectory(path.substr(0, pos + 1).c_str(), nullptr);
}
if ((pos = path.find_first_of(_T("\\/"), path.length() - 1)) == tstring::npos)
{
path.append(_T("\\"));
}
::CreateDirectory(path.c_str(), nullptr);
return std::make_pair(
::GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES,
::GetLastError()
);
}
void createFolders(const std::string &s, char delim) {
std::stringstream ss(s);
std::string item;
char combinedName[50]={'\0'};
while (std::getline(ss, item, delim)) {
sprintf(combinedName,"%s%s%c",combinedName,item.c_str(),delim);
cout<<combinedName<<endl;
struct stat st = {0};
if (stat(combinedName,&st)==-1)
{
#if REDHAT
mkdir(combinedName,0777);
#else
CreateDirectory(combinedName,NULL);
#endif
}
}
}