Reputation: 1007
How can I determine the absolute path of a file or directory from a given relative path in C/C++ on GNU/Linux?
I know about realpath()
, but it does not work on non-existing files.
Let's say the user enters ../non-existant-directory/file.txt
, and the programs working directory is /home/user/
.
What I need is a function that returns /home/non-existant-directory/file.txt
.
I need this function to check if a given path is in a certain subdirectory or not.
Upvotes: 20
Views: 14084
Reputation: 31
I know that this is a very old topic, but in case someone comes here looking for an answer to if realpath
will work when one or more path components in its operand is missing:
The answer, at least as of coreutils v. 8+
, is "yes" on most Linux variants, if you supply the "-m
" option to it. Here's the relevant help text:
$ realpath --help
Usage: realpath [OPTION]... FILE...
Print the resolved absolute file name;
all but the last component must exist
-e, --canonicalize-existing all components of the path must exist
-m, --canonicalize-missing no components of the path need exist
-L, --logical resolve '..' components before symlinks
-P, --physical resolve symlinks as encountered (default)
-q, --quiet suppress most error messages
--relative-to=FILE print the resolved path relative to FILE
--relative-base=FILE print absolute paths unless paths below FILE
-s, --strip, --no-symlinks don't expand symlinks
-z, --zero separate output with NUL rather than newline
--help display this help and exit
--version output version information and exit
Here's an example on a machine with coreutils v. 8.22
installed:
$ pwd
/home/alice/foo/bar
$ ls /home/alice/foo/bar/baz/quux/splat
ls: cannot access /home/alice/foo/bar/baz/quux/splat: No such file or directory
$ realpath -e ../../foo/bar/baz/quux/splat
realpath: ‘../../foo/bar/baz/quux/splat’: No such file or directory
$ realpath -m ../../foo/bar/baz/quux/splat
/home/alice/foo/bar/baz/quux/splat
Upvotes: 3
Reputation: 194
you can try to do something like this:
let pbuf = std::path::PathBuf::from_str("somewhere/something.json").unwrap();
let parent = pbuf.parent().unwrap().canonicalize().unwrap_or(std::env::current_dir().unwrap());
let file = pbuf.file_name().unwrap().to_str().unwrap();
let final_path = parent.join(file).to_str().unwrap().to_owned();
if parent path does not exist (or fails for whatever reason), it will attach current directory in it's place.
Upvotes: 0
Reputation: 1
As noted by @R.. GitHub, you can build this functionality on realpath()
. Here is an example function, which uses realpath()
to determine the canonical form of the portion of the path which exists, and appends the non-existent portion of the path to it.
Since realpath()
operates on C-style strings, I decided to use them here too. But the function can easily be re-written to use std::string
(just don't forget to free canonical_file_path
after copying it to an std::string
!).
Please note that duplicate "/" entries are not removed from the portion of the path which does not exist; it is simply appended to canonical form of the portion which does exist.
////////////////////////////////////////////////////////////////////////////////
// Return the input path in a canonical form. This is achieved by expanding all
// symbolic links, resolving references to "." and "..", and removing duplicate
// "/" characters.
//
// If the file exists, its path is canonicalized and returned. If the file,
// or parts of the containing directory, do not exist, path components are
// removed from the end until an existing path is found. The remainder of the
// path is then appended to the canonical form of the existing path,
// and returned. Consequently, the returned path may not exist. The portion
// of the path which exists, however, is represented in canonical form.
//
// If successful, this function returns a C-string, which needs to be freed by
// the caller using free().
//
// ARGUMENTS:
// file_path
// File path, whose canonical form to return.
//
// RETURNS:
// On success, returns the canonical path to the file, which needs to be freed
// by the caller.
//
// On failure, returns NULL.
////////////////////////////////////////////////////////////////////////////////
char *make_file_name_canonical(char const *file_path)
{
char *canonical_file_path = NULL;
unsigned int file_path_len = strlen(file_path);
if (file_path_len > 0)
{
canonical_file_path = realpath(file_path, NULL);
if (canonical_file_path == NULL && errno == ENOENT)
{
// The file was not found. Back up to a segment which exists,
// and append the remainder of the path to it.
char *file_path_copy = NULL;
if (file_path[0] == '/' ||
(strncmp(file_path, "./", 2) == 0) ||
(strncmp(file_path, "../", 3) == 0))
{
// Absolute path, or path starts with "./" or "../"
file_path_copy = strdup(file_path);
}
else
{
// Relative path
file_path_copy = (char*)malloc(strlen(file_path) + 3);
strcpy(file_path_copy, "./");
strcat(file_path_copy, file_path);
}
// Remove path components from the end, until an existing path is found
for (int char_idx = strlen(file_path_copy) - 1;
char_idx >= 0 && canonical_file_path == NULL;
--char_idx)
{
if (file_path_copy[char_idx] == '/')
{
// Remove the slash character
file_path_copy[char_idx] = '\0';
canonical_file_path = realpath(file_path_copy, NULL);
if (canonical_file_path != NULL)
{
// An existing path was found. Append the remainder of the path
// to a canonical form of the existing path.
char *combined_file_path = (char*)malloc(strlen(canonical_file_path) + strlen(file_path_copy + char_idx + 1) + 2);
strcpy(combined_file_path, canonical_file_path);
strcat(combined_file_path, "/");
strcat(combined_file_path, file_path_copy + char_idx + 1);
free(canonical_file_path);
canonical_file_path = combined_file_path;
}
else
{
// The path segment does not exist. Replace the slash character
// and keep trying by removing the previous path component.
file_path_copy[char_idx] = '/';
}
}
}
free(file_path_copy);
}
}
return canonical_file_path;
}
Upvotes: 0
Reputation: 215221
Try realpath
. If it fails, start removing path components from the end one at a time and retrying realpath
until it succeeds. Then append the components you removed back onto the result of the successful realpath
call.
If you're sure the containing directory exists and you just want to make a file there, you only have to remove at most one component.
Another approach would be to just create the file first, then call realpath
.
Upvotes: 13