blob: 69bbb6e4631020ff2ce4e8834bfceddd6bc33162 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "file-private.h"
#include "guacamole/error.h"
#include "guacamole/file.h"
#include "guacamole/mem.h"
#include "guacamole/string.h"
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/**
* Creates the directory with the given path. Where possible (non-Windows
* platforms), this directory is given "rwxr-x---" (0750) permissions. If the
* directory cannot be created, errno is set appropriately.
*
* @param path
* The full path of the directory to create.
*
* @return
* Zero if the directory was created successfully, non-zero otherwise.
*/
static int guac_mkdir(const char* path) {
#ifdef __MINGW32__
return _mkdir(path);
#else
return mkdir(path, S_IRWXU | S_IRGRP | S_IXGRP);
#endif
}
/**
* Attempts to acquire a lock on the file associated with the given file
* descriptor. The type of lock acquired is dictated by the read_lock flag. If
* the lock cannot be acquired, errno is set appropriately.
*
* This function currently has no effect under Windows and simply returns
* success.
*
* @param fd
* The file descriptor of the file to lock.
*
* @param read_lock
* Whether the lock acquired should be a read lock (non-zero) or a write
* lock (zero).
*
* @return
* Zero if the lock was successfully acquired, non-zero on error.
*/
static int guac_flock(int fd, int read_lock) {
#ifdef __MINGW32__
return 0;
#else
/* Translate requested file open flags (read-only vs. read/write) into
* the relevant kind of lock */
struct flock file_lock = {
.l_type = read_lock ? F_RDLCK : F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0,
.l_pid = getpid()
};
/* Abort if file cannot be locked */
return fcntl(fd, F_SETLK, &file_lock) == -1;
#endif
}
int guac_is_filename(const char* filename) {
/* Verify no references to current or parent directory */
if (strcmp(filename, "..") == 0 || strcmp(filename, ".") == 0)
return 0;
/* Verify no path separators are present in filename */
for (const char* current = filename; *current != '\0'; current++) {
if (*current == '/' || *current == '\\')
return 0;
}
return 1;
}
int guac_openat(const char* path, const char* filename,
const guac_open_how* how) {
int dir_fd = -1;
int fd = -1;
/* Verify filename does not contain any path separators, etc. (only the
* path should be used as a path) */
if (!guac_is_filename(filename))
goto failed;
/* Ensure path exists, creating if necessary and requested, fail if
* impossible */
if (how->flags & GUAC_O_CREATE_PATH) {
if (guac_mkdir(path) && errno != EEXIST) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Containing directory could not be created for file";
goto failed;
}
}
/* Access directory (resulting file descriptor will be used as the path for
* the requested file) */
dir_fd = open(path, O_RDONLY);
if (dir_fd == -1) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "Containing directory could not be opened";
goto failed;
}
/* O_CREAT and O_EXCL should be implicit when a unique suffix is requested
* (the unique suffix option only makes sense if creating exclusive files) */
int oflags = how->oflags;
if (how->flags & GUAC_O_UNIQUE_SUFFIX)
oflags |= O_CREAT | O_EXCL;
/* Always return a filename for the opened file if a filename buffer is
* provided */
size_t filename_length = 0;
if (how->filename != NULL) {
/* We at least need enough storage for the unaltered filename */
filename_length = guac_strlcpy(how->filename, filename, how->filename_size);
if (filename_length >= how->filename_size) {
guac_error = GUAC_STATUS_RESULT_TOO_LARGE;
guac_error_message = "Insufficient space in provided buffer for filename (even without suffix)";
goto failed;
}
}
/* Attempt to open requested file beneath specified path */
fd = openat(dir_fd, filename, oflags, how->mode);
if (fd == -1) {
/* Fail now if there's nothing further we can try to resolve the
* failure */
if (!(how->flags & GUAC_O_UNIQUE_SUFFIX)) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "File could not be opened";
goto failed;
}
/* Below here, GUAC_O_UNIQUE_SUFFIX is known to be set, and we will be
* generating alternative filenames */
/* We can only proceed if we have available storage for alternative
* filenames */
if (how->filename == NULL) {
guac_error = GUAC_STATUS_INVALID_ARGUMENT;
guac_error_message = "No filename buffer provided for adding unique suffix";
goto failed;
}
/* We also need space for the smallest possible suffix (2 characters -
* a single period followed by a single digit) */
char* suffix = ((char*) how->filename) + filename_length;
size_t suffix_size = guac_mem_ckd_sub_or_die(how->filename_size, filename_length);
if (suffix_size <= 2) {
guac_error = GUAC_STATUS_RESULT_TOO_LARGE;
guac_error_message = "Insufficient space in provided buffer for filename and any suffix";
goto failed;
}
/* Prepare filename for additional suffix, overwriting current null
* terminator with the leading "." if that suffix */
*(suffix++) = '.';
suffix_size--;
/* Try ".1", ".2", ".3", etc. until one succeeds or we give up due to
* the sheer quantity of tries */
for (int i = 1; fd == -1 && i <= GUAC_FILE_UNIQUE_SUFFIX_MAX; i++) {
/* Generate and append numeric suffix (reusing common leading "."
* and overwriting any numeric suffix from previous iterations) */
if (snprintf(suffix, suffix_size, "%i", i) >= suffix_size) {
guac_error = GUAC_STATUS_RESULT_TOO_LARGE;
guac_error_message = "Insufficient space in provided buffer for filename and necessary suffix";
goto failed;
}
/* Retry with newly suffixed filename */
fd = openat(dir_fd, how->filename, oflags, how->mode);
}
/* Abort if we've run out of filenames */
if (fd == -1) {
guac_error = GUAC_STATUS_NOT_AVAILABLE;
guac_error_message = "Exhausted all possible unique suffixes";
goto failed;
}
} /* end if open failed */
/* Explicit file locks are required only on POSIX platforms */
if (how->flags & GUAC_O_LOCKED) {
if (guac_flock(fd, (oflags & O_ACCMODE) == O_RDONLY)) {
guac_error = GUAC_STATUS_SEE_ERRNO;
guac_error_message = "File could not be locked";
goto failed;
}
}
close(dir_fd);
return fd;
failed:
if (dir_fd != -1)
close(dir_fd);
if (fd != -1)
close(fd);
return -1;
}