sortix-mirror/libc/stdlib/canonicalize_file_name_at.c
Jonas 'Sortie' Termansen 2b72262b4f Relicense Sortix to the ISC license.
I hereby relicense all my work on Sortix under the ISC license as below.

All Sortix contributions by other people are already under this license,
are not substantial enough to be copyrightable, or have been removed.

All imported code from other projects is compatible with this license.

All GPL licensed code from other projects had previously been removed.

Copyright 2011-2016 Jonas 'Sortie' Termansen and contributors.

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
2016-03-05 22:21:50 +01:00

205 lines
5.8 KiB
C

/*
* Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* stdlib/canonicalize_file_name_at.c
* Return the canonicalized filename.
*/
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int dup_handles_cwd(int fd)
{
if ( fd == AT_FDCWD )
return open(".", O_RDONLY | O_DIRECTORY);
return dup(fd);
}
static char* FindDirectoryEntryAt(int dirfd, ino_t inode, dev_t dev)
{
int dupdirfd = dup_handles_cwd(dirfd);
if ( dupdirfd < 0 )
return NULL;
DIR* dir = fdopendir(dupdirfd);
if ( !dir ) { close(dupdirfd); return NULL; }
struct dirent* entry;
while ( (entry = readdir(dir)) )
{
if ( !strcmp(entry->d_name, "..") )
continue;
struct stat st;
if ( fstatat(dupdirfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) )
continue; // Not ideal, but missing permissions, broken symlinks..
if ( st.st_ino == inode && st.st_dev == dev )
{
char* result = strdup(entry->d_name);
closedir(dir);
return result;
}
}
closedir(dir);
errno = ENOENT;
return NULL;
}
static const char* SkipSlashes(const char* path)
{
while ( *path == '/' )
path++;
return *path ? path : NULL;
}
char* canonicalize_file_name_at(int dirfd, const char* path)
{
if ( path && path[0] == '.' && !path[1] )
path = NULL;
// The below code is able to determine the absolute path to a directory by
// using the .. entry and searching the parent directory. This doesn't work
// for files, as they can be linked in any number of directories and have no
// pointer back to the directory they were opened from. We'll therefore
// follow the path until the last element, and if the path points to a file
// we'll simply append that to the final result.
if ( path )
{
// Open the directory specified by the last slash in the path.
char* last_slash = strrchr(path, '/');
if ( last_slash )
{
size_t subpath_len = (size_t) (last_slash - path + 1);
char* subpath = (char*) malloc((subpath_len + 1) * sizeof(char));
if ( !subpath )
return NULL;
memcpy(subpath, path, subpath_len * sizeof(char));
subpath[subpath_len] = '\0';
int fd = openat(dirfd, subpath, O_RDONLY | O_DIRECTORY);
free(subpath);
if ( fd < 0 )
return NULL;
path = SkipSlashes(last_slash + 1);
char* ret = canonicalize_file_name_at(fd, path);
close(fd);
return ret;
}
// We have reached the final element in the path. It could be:
// 1) A directory.
// 2) A file.
// 3) A symbolic link to a directory.
// 4) A symbolic link to a file.
// The ideal case is if it's a directory (case 1). We follow symbolic
// links to directories as that's also okay (case 3).
int fd = openat(dirfd, path, O_RDONLY | O_DIRECTORY);
if ( 0 <= fd )
{
char* ret = canonicalize_file_name_at(fd, NULL);
close(fd);
return ret;
}
// Stop if an error happened other that the target wasn't a directory.
if ( errno != ENOTDIR )
return NULL;
// TODO: Symbolic link support here.
// Okay, so now we are dealing with either case 2 or case 4. Since
// Sortix doesn't have current have symbolic links, we'll be lazy and
// just assume we are dealing with a file. Otherwise, we'd have to open
// with O_NOFOLLOW and if that fails, use readlink to figure out where
// the symbolic link is going, open the directory that contains it and
// continue this function from there - or something.
}
// Our task is now to determine the absolute path of the directory opened as
// dirfd and append the path variable to it, if any. We'll find the inode
// information of the directory and search its parent directory for an entry
// that resolves to this directory. That'll give us one part of the path.
// We'll then apply that techinique recursively until we hit a directory
// whose parent is itself (the root directory).
int fd;
int parent;
struct stat fdst;
struct stat parentst;
size_t retlen = 0;
size_t newretlen;
char* ret = NULL;
char* newret;
char* elem;
if ( path )
{
retlen = 1 + strlen(path);
if ( !(ret = (char*) malloc(sizeof(char) * (retlen + 1))) )
return NULL;
stpcpy(stpcpy(ret, "/"), path);
}
if ( (fd = dup_handles_cwd(dirfd)) < 0 )
goto cleanup_done;
if ( fstat(fd, &fdst) )
goto cleanup_fd;
if ( !S_ISDIR(fdst.st_mode) )
{
errno = ENOTDIR;
goto cleanup_fd;
}
next_parent:
parent = openat(fd, "..", O_RDONLY | O_DIRECTORY);
if ( parent < 0 )
goto cleanup_fd;
if ( fstat(parent, &parentst) )
goto cleanup_parent;
if ( fdst.st_ino == parentst.st_ino &&
fdst.st_dev == parentst.st_dev )
{
close(fd);
close(parent);
return ret ? ret : strdup("/");
}
elem = FindDirectoryEntryAt(parent, fdst.st_ino, fdst.st_dev);
if ( !elem )
goto cleanup_parent;
newretlen = 1 + strlen(elem) + retlen;
newret = (char*) malloc(sizeof(char) * (newretlen + 1));
if ( !newret )
goto cleanup_elem;
stpcpy(stpcpy(stpcpy(newret, "/"), elem), ret ? ret : "");
free(elem);
free(ret);
ret = newret;
retlen = newretlen;
close(fd);
fd = parent;
fdst = parentst;
goto next_parent;
cleanup_elem:
free(elem);
cleanup_parent:
close(parent);
cleanup_fd:
close(fd);
cleanup_done:
free(ret);
return NULL;
}