From 0ce3d61cb926454b05154830209df7eb57d6a0aa Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Tue, 19 Mar 2013 17:23:41 +0100 Subject: [PATCH] Add canonicalize_file_name{,_at}(3). --- libc/Makefile | 2 + libc/canonicalize_file_name.cpp | 31 +++++ libc/canonicalize_file_name_at.cpp | 206 +++++++++++++++++++++++++++++ libc/getcwd.cpp | 100 +------------- libc/include/stdlib.h | 2 + 5 files changed, 248 insertions(+), 93 deletions(-) create mode 100644 libc/canonicalize_file_name.cpp create mode 100644 libc/canonicalize_file_name_at.cpp diff --git a/libc/Makefile b/libc/Makefile index e02c8ba1..b0431658 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -125,6 +125,8 @@ wctype.o \ HOSTEDOBJS=\ access.o \ calltrace.o \ +canonicalize_file_name_at.o \ +canonicalize_file_name.o \ chdir.o \ chmod.o \ chown.o \ diff --git a/libc/canonicalize_file_name.cpp b/libc/canonicalize_file_name.cpp new file mode 100644 index 00000000..0a87a5b2 --- /dev/null +++ b/libc/canonicalize_file_name.cpp @@ -0,0 +1,31 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + canonicalize_file_name.cpp + Return the canonicalized filename. + +*******************************************************************************/ + +#include +#include + +extern "C" char* canonicalize_file_name(const char* path) +{ + return canonicalize_file_name_at(AT_FDCWD, path); +} diff --git a/libc/canonicalize_file_name_at.cpp b/libc/canonicalize_file_name_at.cpp new file mode 100644 index 00000000..0bb3ffb4 --- /dev/null +++ b/libc/canonicalize_file_name_at.cpp @@ -0,0 +1,206 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + canonicalize_file_name_at.cpp + Return the canonicalized filename. + +*******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +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; +} + +extern "C" 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)); + 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 ( 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 ) + { + if ( !(ret = (char*) malloc(sizeof(char) * (1 + strlen(path) + 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; +} diff --git a/libc/getcwd.cpp b/libc/getcwd.cpp index 0670bd90..203bb4c6 100644 --- a/libc/getcwd.cpp +++ b/libc/getcwd.cpp @@ -22,105 +22,14 @@ *******************************************************************************/ -#include -#include -#include #include -#include #include #include #include -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, 0) ) - 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; -} - extern "C" char* get_current_dir_name(void) { - int fd; - int parent; - struct stat fdst; - struct stat parentst; - size_t retlen = 0; - size_t newretlen; - char* ret = NULL; - char* newret; - char* elem; - - fd = open(".", O_RDONLY | O_DIRECTORY); - if ( fd < 0 ) - goto cleanup_done; - if ( fstat(fd, &fdst) ) - 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; + return canonicalize_file_name("."); } extern "C" char* getcwd(char* buf, size_t size) @@ -130,7 +39,12 @@ extern "C" char* getcwd(char* buf, size_t size) return cwd; if ( !cwd ) return NULL; - if ( size < strlen(cwd)+1 ) { free(cwd); errno = ERANGE; return NULL; } + if ( size < strlen(cwd) + 1 ) + { + free(cwd); + errno = ERANGE; + return NULL; + } strcpy(buf, cwd); free(cwd); return buf; diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h index a50a8fc0..6893686f 100644 --- a/libc/include/stdlib.h +++ b/libc/include/stdlib.h @@ -69,6 +69,8 @@ long atol(const char*); long long atoll(const char*); void* bsearch(const void*, const void*, size_t, size_t, int (*)(const void*, const void*)); void* calloc(size_t, size_t); +char* canonicalize_file_name(const char* path); +char* canonicalize_file_name_at(int dirfd, const char* path); div_t div(int, int); void exit(int) __attribute__ ((__noreturn__)); void _Exit(int status) __attribute__ ((__noreturn__));