Skip to content

Instantly share code, notes, and snippets.

@alexlarsson
Created May 17, 2017 15:25
Show Gist options
  • Save alexlarsson/3df0c5c680828909802c74b1a6ca71f6 to your computer and use it in GitHub Desktop.
Save alexlarsson/3df0c5c680828909802c74b1a6ca71f6 to your computer and use it in GitHub Desktop.
/*
* Copyright © 2017 Red Hat, Inc
*
* This program 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 2 of the License, or (at your option) any later version.
*
* This 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 this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/limits.h>
#include "libglnx.h"
typedef struct {
int root_fd;
struct stat root_stat;
int loopcount;
} resolve_data_t;
static int
resolve (resolve_data_t *data,
int cwd_fd, char *path, int flags,
int *out_dir_fd,
struct stat *out_resolved_stat)
{
glnx_fd_close int dir_fd = -1;
if (*path == '/' || cwd_fd == AT_FDCWD)
dir_fd = dup (data->root_fd); /* absolute */
else
dir_fd = dup (cwd_fd); /* relative */
if (dir_fd == -1)
return -1;
/* Skip initial slashes */
while (*path == '/')
path++;
while (1)
{
char *end = NULL;
int is_final_element;
glnx_fd_close int element_fd = -1;
const char *element;
struct stat element_stat;
end = path;
while (*end != 0 && *end != '/')
end++;
while (*end == '/')
*end++ = 0;
is_final_element = *end == 0;
element = path;
path = end;
if (*element == 0)
{
/* Special case for path=='/' and path='' */
element = ".";
}
else if (strcmp (element, "..") == 0)
{
struct stat dir_stat;
/* Make sure we never step out of the root */
if (fstat (dir_fd, &dir_stat) == -1)
return -1;
if (dir_stat.st_dev == data->root_stat.st_dev &&
dir_stat.st_ino == data->root_stat.st_ino)
element = ".";
}
element_fd = openat (dir_fd, element, O_NOFOLLOW|O_PATH);
if (element_fd == -1 ||
fstat (element_fd, &element_stat) == -1)
return -1;
while (S_ISLNK (element_stat.st_mode) &&
(!is_final_element || (flags & O_NOFOLLOW) == 0))
{
char symlink_buffer[PATH_MAX + 1];
ssize_t symlink_size;
glnx_fd_close int resolved_fd = -1;
glnx_fd_close int resolved_dir_fd = -1;
if (data->loopcount++ == 40)
{
errno = ELOOP;
return -1;
}
symlink_size = readlinkat (element_fd, "", symlink_buffer, PATH_MAX);
if (symlink_size < 0)
return -1;
symlink_buffer[symlink_size] = 0;
resolved_fd = resolve (data, dir_fd, symlink_buffer, O_NOFOLLOW, &resolved_dir_fd, &element_stat);
if (resolved_fd == -1)
return -1;
close (element_fd);
close (dir_fd);
element_fd = glnx_steal_fd (&resolved_fd);
dir_fd = glnx_steal_fd (&resolved_dir_fd);
}
if (is_final_element)
{
if (out_dir_fd)
*out_dir_fd = glnx_steal_fd (&dir_fd);
if (out_resolved_stat)
*out_resolved_stat = element_stat;
return glnx_steal_fd (&element_fd);
}
else if (!S_ISDIR (element_stat.st_mode))
{
errno = ENOTDIR;
return -1;
}
close (dir_fd);
dir_fd = glnx_steal_fd (&element_fd);
/* Next element */
}
}
int
resolve_path_relative_at (int root_fd, const char *path,
struct stat *out_stat)
{
char *path_copy;
resolve_data_t data = { root_fd };
if (fstat (root_fd, &data.root_stat) == -1)
return -1;
/* Make sure we can modify the path */
path_copy = strdupa (path);
if (path_copy == NULL)
{
errno = ENOMEM;
return -1;
}
return resolve (&data, root_fd, path_copy, 0, NULL, out_stat);
}
int
resolve_path_relative (const char *root, const char *path,
struct stat *out_stat)
{
int root_fd = openat (AT_FDCWD, root, O_PATH);
if (root_fd == -1)
return -1;
return resolve_path_relative_at (root_fd, path, out_stat);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment