2017-11-18 17:09:20 +01:00
/* SPDX-License-Identifier: LGPL-2.1+ */
2015-10-26 21:16:26 +01:00
2015-11-30 21:43:37 +01:00
# include <errno.h>
# include <stddef.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
2017-09-04 15:35:07 +02:00
# include <linux/magic.h>
2015-11-30 21:43:37 +01:00
# include <time.h>
# include <unistd.h>
2015-10-27 03:01:06 +01:00
# include "alloc-util.h"
2015-10-26 21:16:26 +01:00
# include "dirent-util.h"
# include "fd-util.h"
# include "fileio.h"
# include "fs-util.h"
2015-11-30 21:43:37 +01:00
# include "log.h"
# include "macro.h"
# include "missing.h"
2015-12-01 23:22:03 +01:00
# include "mkdir.h"
# include "parse-util.h"
# include "path-util.h"
2018-01-11 00:39:12 +01:00
# include "process-util.h"
2016-06-30 16:59:06 +02:00
# include "stat-util.h"
2016-04-25 00:18:27 +02:00
# include "stdio-util.h"
2015-10-26 21:16:26 +01:00
# include "string-util.h"
# include "strv.h"
2015-12-01 23:22:03 +01:00
# include "time-util.h"
2015-10-27 00:42:07 +01:00
# include "user-util.h"
2015-10-26 21:16:26 +01:00
# include "util.h"
int unlink_noerrno ( const char * path ) {
PROTECT_ERRNO ;
int r ;
r = unlink ( path ) ;
if ( r < 0 )
return - errno ;
return 0 ;
}
int rmdir_parents ( const char * path , const char * stop ) {
size_t l ;
int r = 0 ;
assert ( path ) ;
assert ( stop ) ;
l = strlen ( path ) ;
/* Skip trailing slashes */
while ( l > 0 & & path [ l - 1 ] = = ' / ' )
l - - ;
while ( l > 0 ) {
char * t ;
/* Skip last component */
while ( l > 0 & & path [ l - 1 ] ! = ' / ' )
l - - ;
/* Skip trailing slashes */
while ( l > 0 & & path [ l - 1 ] = = ' / ' )
l - - ;
if ( l < = 0 )
break ;
t = strndup ( path , l ) ;
if ( ! t )
return - ENOMEM ;
if ( path_startswith ( stop , t ) ) {
free ( t ) ;
return 0 ;
}
r = rmdir ( t ) ;
free ( t ) ;
if ( r < 0 )
if ( errno ! = ENOENT )
return - errno ;
}
return 0 ;
}
int rename_noreplace ( int olddirfd , const char * oldpath , int newdirfd , const char * newpath ) {
2018-10-02 13:34:18 +02:00
int r ;
2015-10-26 21:16:26 +01:00
2018-10-02 13:34:18 +02:00
/* Try the ideal approach first */
if ( renameat2 ( olddirfd , oldpath , newdirfd , newpath , RENAME_NOREPLACE ) > = 0 )
2015-10-26 21:16:26 +01:00
return 0 ;
2018-10-02 13:34:18 +02:00
/* renameat2() exists since Linux 3.15, btrfs and FAT added support for it later. If it is not implemented,
* fall back to a different method . */
if ( ! IN_SET ( errno , EINVAL , ENOSYS , ENOTTY ) )
2015-10-26 21:16:26 +01:00
return - errno ;
2018-10-02 13:34:18 +02:00
/* Let's try to use linkat()+unlinkat() as fallback. This doesn't work on directories and on some file systems
* that do not support hard links ( such as FAT , most prominently ) , but for files it ' s pretty close to what we
* want — though not atomic ( i . e . for a short period both the new and the old filename will exist ) . */
if ( linkat ( olddirfd , oldpath , newdirfd , newpath , 0 ) > = 0 ) {
if ( unlinkat ( olddirfd , oldpath , 0 ) < 0 ) {
r = - errno ; /* Backup errno before the following unlinkat() alters it */
( void ) unlinkat ( newdirfd , newpath , 0 ) ;
return r ;
}
return 0 ;
2015-10-26 21:16:26 +01:00
}
2018-10-02 13:34:18 +02:00
if ( ! IN_SET ( errno , EINVAL , ENOSYS , ENOTTY , EPERM ) ) /* FAT returns EPERM on link()… */
2015-10-26 21:16:26 +01:00
return - errno ;
2018-10-02 13:34:18 +02:00
/* OK, neither RENAME_NOREPLACE nor linkat()+unlinkat() worked. Let's then fallback to the racy TOCTOU
* vulnerable accessat ( F_OK ) check followed by classic , replacing renameat ( ) , we have nothing better . */
if ( faccessat ( newdirfd , newpath , F_OK , AT_SYMLINK_NOFOLLOW ) > = 0 )
return - EEXIST ;
if ( errno ! = ENOENT )
return - errno ;
if ( renameat ( olddirfd , oldpath , newdirfd , newpath ) < 0 )
2015-10-26 21:16:26 +01:00
return - errno ;
return 0 ;
}
int readlinkat_malloc ( int fd , const char * p , char * * ret ) {
size_t l = 100 ;
int r ;
assert ( p ) ;
assert ( ret ) ;
for ( ; ; ) {
char * c ;
ssize_t n ;
c = new ( char , l ) ;
if ( ! c )
return - ENOMEM ;
n = readlinkat ( fd , p , c , l - 1 ) ;
if ( n < 0 ) {
r = - errno ;
free ( c ) ;
return r ;
}
if ( ( size_t ) n < l - 1 ) {
c [ n ] = 0 ;
* ret = c ;
return 0 ;
}
free ( c ) ;
l * = 2 ;
}
}
int readlink_malloc ( const char * p , char * * ret ) {
return readlinkat_malloc ( AT_FDCWD , p , ret ) ;
}
int readlink_value ( const char * p , char * * ret ) {
_cleanup_free_ char * link = NULL ;
char * value ;
int r ;
r = readlink_malloc ( p , & link ) ;
if ( r < 0 )
return r ;
value = basename ( link ) ;
if ( ! value )
return - ENOENT ;
value = strdup ( value ) ;
if ( ! value )
return - ENOMEM ;
* ret = value ;
return 0 ;
}
int readlink_and_make_absolute ( const char * p , char * * r ) {
_cleanup_free_ char * target = NULL ;
char * k ;
int j ;
assert ( p ) ;
assert ( r ) ;
j = readlink_malloc ( p , & target ) ;
if ( j < 0 )
return j ;
k = file_in_same_dir ( p , target ) ;
if ( ! k )
return - ENOMEM ;
* r = k ;
return 0 ;
}
int chmod_and_chown ( const char * path , mode_t mode , uid_t uid , gid_t gid ) {
assert ( path ) ;
/* Under the assumption that we are running privileged we
* first change the access mode and only then hand out
* ownership to avoid a window where access is too open . */
if ( mode ! = MODE_INVALID )
if ( chmod ( path , mode ) < 0 )
return - errno ;
if ( uid ! = UID_INVALID | | gid ! = GID_INVALID )
if ( chown ( path , uid , gid ) < 0 )
return - errno ;
2018-06-14 04:26:29 +02:00
return 0 ;
}
int fchmod_and_chown ( int fd , mode_t mode , uid_t uid , gid_t gid ) {
/* Under the assumption that we are running privileged we
* first change the access mode and only then hand out
* ownership to avoid a window where access is too open . */
if ( mode ! = MODE_INVALID )
if ( fchmod ( fd , mode ) < 0 )
return - errno ;
if ( uid ! = UID_INVALID | | gid ! = GID_INVALID )
if ( fchown ( fd , uid , gid ) < 0 )
return - errno ;
2015-10-26 21:16:26 +01:00
return 0 ;
}
int fchmod_umask ( int fd , mode_t m ) {
mode_t u ;
int r ;
u = umask ( 0777 ) ;
r = fchmod ( fd , m & ( ~ u ) ) < 0 ? - errno : 0 ;
umask ( u ) ;
return r ;
}
2018-04-11 16:58:49 +02:00
int fchmod_opath ( int fd , mode_t m ) {
2018-05-11 11:09:37 +02:00
char procfs_path [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) ] ;
2018-04-11 16:58:49 +02:00
/* This function operates also on fd that might have been opened with
* O_PATH . Indeed fchmodat ( ) doesn ' t have the AT_EMPTY_PATH flag like
* fchownat ( ) does . */
xsprintf ( procfs_path , " /proc/self/fd/%i " , fd ) ;
if ( chmod ( procfs_path , m ) < 0 )
return - errno ;
return 0 ;
}
2015-10-26 21:16:26 +01:00
int fd_warn_permissions ( const char * path , int fd ) {
struct stat st ;
if ( fstat ( fd , & st ) < 0 )
return - errno ;
if ( st . st_mode & 0111 )
log_warning ( " Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway. " , path ) ;
if ( st . st_mode & 0002 )
log_warning ( " Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway. " , path ) ;
2017-07-20 16:19:18 +02:00
if ( getpid_cached ( ) = = 1 & & ( st . st_mode & 0044 ) ! = 0044 )
2015-10-26 21:16:26 +01:00
log_warning ( " Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway. " , path ) ;
return 0 ;
}
int touch_file ( const char * path , bool parents , usec_t stamp , uid_t uid , gid_t gid , mode_t mode ) {
2017-12-27 16:20:28 +01:00
char fdpath [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) ] ;
_cleanup_close_ int fd = - 1 ;
int r , ret = 0 ;
2015-10-26 21:16:26 +01:00
assert ( path ) ;
2017-12-27 16:20:28 +01:00
/* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
* itself which is updated , not its target
*
* Returns the first error we encounter , but tries to apply as much as possible . */
2015-10-26 21:16:26 +01:00
2017-12-27 16:20:28 +01:00
if ( parents )
( void ) mkdir_parents ( path , 0755 ) ;
/* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
* case the path refers to an existing device or socket node , as we can open it successfully in all cases , and
* won ' t trigger any driver magic or so . */
fd = open ( path , O_PATH | O_CLOEXEC | O_NOFOLLOW ) ;
if ( fd < 0 ) {
if ( errno ! = ENOENT )
2015-10-26 21:16:26 +01:00
return - errno ;
2017-12-27 16:20:28 +01:00
/* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
* here , and nothing else */
fd = open ( path , O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC , IN_SET ( mode , 0 , MODE_INVALID ) ? 0644 : mode ) ;
if ( fd < 0 )
2015-10-26 21:16:26 +01:00
return - errno ;
}
2017-12-27 16:20:28 +01:00
/* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
* ownership and time of the file node in all cases , even if the fd refers to an O_PATH object — which is
* something fchown ( ) , fchmod ( ) , futimensat ( ) don ' t allow . */
xsprintf ( fdpath , " /proc/self/fd/%i " , fd ) ;
if ( mode ! = MODE_INVALID )
if ( chmod ( fdpath , mode ) < 0 )
ret = - errno ;
if ( uid_is_valid ( uid ) | | gid_is_valid ( gid ) )
if ( chown ( fdpath , uid , gid ) < 0 & & ret > = 0 )
ret = - errno ;
2015-10-26 21:16:26 +01:00
if ( stamp ! = USEC_INFINITY ) {
struct timespec ts [ 2 ] ;
timespec_store ( & ts [ 0 ] , stamp ) ;
ts [ 1 ] = ts [ 0 ] ;
2017-12-27 16:20:28 +01:00
r = utimensat ( AT_FDCWD , fdpath , ts , 0 ) ;
2015-10-26 21:16:26 +01:00
} else
2017-12-27 16:20:28 +01:00
r = utimensat ( AT_FDCWD , fdpath , NULL , 0 ) ;
if ( r < 0 & & ret > = 0 )
2015-10-26 21:16:26 +01:00
return - errno ;
2017-12-27 16:20:28 +01:00
return ret ;
2015-10-26 21:16:26 +01:00
}
int touch ( const char * path ) {
2015-11-11 22:54:56 +01:00
return touch_file ( path , false , USEC_INFINITY , UID_INVALID , GID_INVALID , MODE_INVALID ) ;
2015-10-26 21:16:26 +01:00
}
2018-09-23 09:17:03 +02:00
int symlink_idempotent ( const char * from , const char * to , bool make_relative ) {
_cleanup_free_ char * relpath = NULL ;
2015-10-26 21:16:26 +01:00
int r ;
assert ( from ) ;
assert ( to ) ;
2018-09-23 09:17:03 +02:00
if ( make_relative ) {
_cleanup_free_ char * parent = NULL ;
parent = dirname_malloc ( to ) ;
if ( ! parent )
return - ENOMEM ;
r = path_make_relative ( parent , from , & relpath ) ;
if ( r < 0 )
return r ;
from = relpath ;
}
2015-10-26 21:16:26 +01:00
if ( symlink ( from , to ) < 0 ) {
2017-09-26 18:26:20 +02:00
_cleanup_free_ char * p = NULL ;
2015-10-26 21:16:26 +01:00
if ( errno ! = EEXIST )
return - errno ;
r = readlink_malloc ( to , & p ) ;
2017-09-26 18:26:20 +02:00
if ( r = = - EINVAL ) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
return - EEXIST ;
if ( r < 0 ) /* Any other error? In that case propagate it as is */
2015-10-26 21:16:26 +01:00
return r ;
2017-09-26 18:26:20 +02:00
if ( ! streq ( p , from ) ) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
return - EEXIST ;
2015-10-26 21:16:26 +01:00
}
return 0 ;
}
int symlink_atomic ( const char * from , const char * to ) {
_cleanup_free_ char * t = NULL ;
int r ;
assert ( from ) ;
assert ( to ) ;
r = tempfn_random ( to , NULL , & t ) ;
if ( r < 0 )
return r ;
if ( symlink ( from , t ) < 0 )
return - errno ;
if ( rename ( t , to ) < 0 ) {
unlink_noerrno ( t ) ;
return - errno ;
}
return 0 ;
}
int mknod_atomic ( const char * path , mode_t mode , dev_t dev ) {
_cleanup_free_ char * t = NULL ;
int r ;
assert ( path ) ;
r = tempfn_random ( path , NULL , & t ) ;
if ( r < 0 )
return r ;
if ( mknod ( t , mode , dev ) < 0 )
return - errno ;
if ( rename ( t , path ) < 0 ) {
unlink_noerrno ( t ) ;
return - errno ;
}
return 0 ;
}
int mkfifo_atomic ( const char * path , mode_t mode ) {
_cleanup_free_ char * t = NULL ;
int r ;
assert ( path ) ;
r = tempfn_random ( path , NULL , & t ) ;
if ( r < 0 )
return r ;
if ( mkfifo ( t , mode ) < 0 )
return - errno ;
if ( rename ( t , path ) < 0 ) {
2018-04-27 18:20:38 +02:00
unlink_noerrno ( t ) ;
return - errno ;
}
return 0 ;
}
int mkfifoat_atomic ( int dirfd , const char * path , mode_t mode ) {
_cleanup_free_ char * t = NULL ;
int r ;
assert ( path ) ;
if ( path_is_absolute ( path ) )
return mkfifo_atomic ( path , mode ) ;
/* We're only interested in the (random) filename. */
r = tempfn_random_child ( " " , NULL , & t ) ;
if ( r < 0 )
return r ;
if ( mkfifoat ( dirfd , t , mode ) < 0 )
return - errno ;
if ( renameat ( dirfd , t , dirfd , path ) < 0 ) {
2015-10-26 21:16:26 +01:00
unlink_noerrno ( t ) ;
return - errno ;
}
return 0 ;
}
int get_files_in_directory ( const char * path , char * * * list ) {
_cleanup_closedir_ DIR * d = NULL ;
2016-12-09 10:04:30 +01:00
struct dirent * de ;
2015-10-26 21:16:26 +01:00
size_t bufsize = 0 , n = 0 ;
_cleanup_strv_free_ char * * l = NULL ;
assert ( path ) ;
/* Returns all files in a directory in *list, and the number
* of files as return value . If list is NULL returns only the
* number . */
d = opendir ( path ) ;
if ( ! d )
return - errno ;
2016-12-09 10:04:30 +01:00
FOREACH_DIRENT_ALL ( de , d , return - errno ) {
2015-10-26 21:16:26 +01:00
dirent_ensure_type ( d , de ) ;
if ( ! dirent_is_file ( de ) )
continue ;
if ( list ) {
/* one extra slot is needed for the terminating NULL */
if ( ! GREEDY_REALLOC ( l , bufsize , n + 2 ) )
return - ENOMEM ;
l [ n ] = strdup ( de - > d_name ) ;
if ( ! l [ n ] )
return - ENOMEM ;
l [ + + n ] = NULL ;
} else
n + + ;
}
2018-03-22 16:53:26 +01:00
if ( list )
* list = TAKE_PTR ( l ) ;
2015-10-26 21:16:26 +01:00
return n ;
}
2016-04-25 00:18:27 +02:00
2016-07-26 17:23:28 +02:00
static int getenv_tmp_dir ( const char * * ret_path ) {
const char * n ;
int r , ret = 0 ;
2016-06-30 16:59:06 +02:00
2016-07-26 17:23:28 +02:00
assert ( ret_path ) ;
2016-06-30 16:59:06 +02:00
2016-07-26 17:23:28 +02:00
/* We use the same order of environment variables python uses in tempfile.gettempdir():
* https : //docs.python.org/3/library/tempfile.html#tempfile.gettempdir */
FOREACH_STRING ( n , " TMPDIR " , " TEMP " , " TMP " ) {
const char * e ;
e = secure_getenv ( n ) ;
if ( ! e )
continue ;
if ( ! path_is_absolute ( e ) ) {
r = - ENOTDIR ;
goto next ;
}
2017-10-27 16:28:15 +02:00
if ( ! path_is_normalized ( e ) ) {
2016-07-26 17:23:28 +02:00
r = - EPERM ;
goto next ;
}
r = is_dir ( e , true ) ;
if ( r < 0 )
goto next ;
if ( r = = 0 ) {
r = - ENOTDIR ;
goto next ;
}
* ret_path = e ;
return 1 ;
next :
/* Remember first error, to make this more debuggable */
if ( ret > = 0 )
ret = r ;
2016-06-30 16:59:06 +02:00
}
2016-07-26 17:23:28 +02:00
if ( ret < 0 )
return ret ;
2016-06-30 16:59:06 +02:00
2016-07-26 17:23:28 +02:00
* ret_path = NULL ;
return ret ;
}
2016-06-30 16:59:06 +02:00
2016-07-26 17:23:28 +02:00
static int tmp_dir_internal ( const char * def , const char * * ret ) {
const char * e ;
int r , k ;
assert ( def ) ;
assert ( ret ) ;
r = getenv_tmp_dir ( & e ) ;
if ( r > 0 ) {
* ret = e ;
return 0 ;
}
k = is_dir ( def , true ) ;
if ( k = = 0 )
k = - ENOTDIR ;
if ( k < 0 )
return r < 0 ? r : k ;
* ret = def ;
2016-06-30 16:59:06 +02:00
return 0 ;
}
2016-07-26 17:23:28 +02:00
int var_tmp_dir ( const char * * ret ) {
/* Returns the location for "larger" temporary files, that is backed by physical storage if available, and thus
* even might survive a boot : / var / tmp . If $ TMPDIR ( or related environment variables ) are set , its value is
* returned preferably however . Note that both this function and tmp_dir ( ) below are affected by $ TMPDIR ,
* making it a variable that overrides all temporary file storage locations . */
return tmp_dir_internal ( " /var/tmp " , ret ) ;
}
int tmp_dir ( const char * * ret ) {
/* Similar to var_tmp_dir() above, but returns the location for "smaller" temporary files, which is usually
* backed by an in - memory file system : / tmp . */
return tmp_dir_internal ( " /tmp " , ret ) ;
}
2018-02-16 06:58:33 +01:00
int unlink_or_warn ( const char * filename ) {
if ( unlink ( filename ) < 0 & & errno ! = ENOENT )
/* If the file doesn't exist and the fs simply was read-only (in which
* case unlink ( ) returns EROFS even if the file doesn ' t exist ) , don ' t
* complain */
if ( errno ! = EROFS | | access ( filename , F_OK ) > = 0 )
return log_error_errno ( errno , " Failed to remove \" %s \" : %m " , filename ) ;
return 0 ;
}
2016-04-25 00:18:27 +02:00
int inotify_add_watch_fd ( int fd , int what , uint32_t mask ) {
2017-12-14 19:02:29 +01:00
char path [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) + 1 ] ;
2016-04-25 00:18:27 +02:00
int r ;
/* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
xsprintf ( path , " /proc/self/fd/%i " , what ) ;
r = inotify_add_watch ( fd , path , mask ) ;
if ( r < 0 )
return - errno ;
return r ;
}
2016-09-24 12:41:30 +02:00
2018-01-04 19:44:27 +01:00
static bool safe_transition ( const struct stat * a , const struct stat * b ) {
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
* privileged files or directories . Why bother ? So that unprivileged code can ' t symlink to privileged files
* making us believe we read something safe even though it isn ' t safe in the specific context we open it in . */
if ( a - > st_uid = = 0 ) /* Transitioning from privileged to unprivileged is always fine */
return true ;
return a - > st_uid = = b - > st_uid ; /* Otherwise we need to stay within the same UID */
}
2016-11-29 16:49:30 +01:00
int chase_symlinks ( const char * path , const char * original_root , unsigned flags , char * * ret ) {
2016-09-24 12:41:30 +02:00
_cleanup_free_ char * buffer = NULL , * done = NULL , * root = NULL ;
_cleanup_close_ int fd = - 1 ;
2018-04-04 17:06:12 +02:00
unsigned max_follow = CHASE_SYMLINKS_MAX ; /* how many symlinks to follow before giving up and returning ELOOP */
2018-01-04 19:44:27 +01:00
struct stat previous_stat ;
2016-11-29 18:02:45 +01:00
bool exists = true ;
2016-09-24 12:41:30 +02:00
char * todo ;
int r ;
assert ( path ) ;
2018-01-04 20:00:28 +01:00
/* Either the file may be missing, or we return an fd to the final object, but both make no sense */
2018-04-20 15:36:20 +02:00
if ( FLAGS_SET ( flags , CHASE_NONEXISTENT | CHASE_OPEN ) )
2018-01-04 20:00:28 +01:00
return - EINVAL ;
2018-04-20 15:36:20 +02:00
if ( FLAGS_SET ( flags , CHASE_STEP | CHASE_OPEN ) )
2018-04-04 17:03:45 +02:00
return - EINVAL ;
2018-01-17 12:00:12 +01:00
if ( isempty ( path ) )
return - EINVAL ;
2016-09-24 12:41:30 +02:00
/* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
* symlinks relative to a root directory , instead of the root of the host .
*
2016-11-25 18:59:39 +01:00
* Note that " root " primarily matters if we encounter an absolute symlink . It is also used when following
2016-11-29 16:49:30 +01:00
* relative symlinks to ensure they cannot be used to " escape " the root directory . The path parameter passed is
* assumed to be already prefixed by it , except if the CHASE_PREFIX_ROOT flag is set , in which case it is first
* prefixed accordingly .
2016-09-24 12:41:30 +02:00
*
* Algorithmically this operates on two path buffers : " done " are the components of the path we already
* processed and resolved symlinks , " . " and " .. " of . " todo " are the components of the path we still need to
* process . On each iteration , we move one component from " todo " to " done " , processing it ' s special meaning
* each time . The " todo " path always starts with at least one slash , the " done " path always ends in no
* slash . We always keep an O_PATH fd to the component we are currently processing , thus keeping lookup races
2016-11-25 18:59:39 +01:00
* at a minimum .
*
* Suggested usage : whenever you want to canonicalize a path , use this function . Pass the absolute path you got
* as - is : fully qualified and relative to your host ' s root . Optionally , specify the root parameter to tell this
* function what to do when encountering a symlink with an absolute path as directory : prefix it by the
2018-04-04 17:03:45 +02:00
* specified path .
*
* There are three ways to invoke this function :
*
* 1. Without CHASE_STEP or CHASE_OPEN : in this case the path is resolved and the normalized path is returned
* in ` ret ` . The return value is < 0 on error . If CHASE_NONEXISTENT is also set 0 is returned if the file
* doesn ' t exist , > 0 otherwise . If CHASE_NONEXISTENT is not set > = 0 is returned if the destination was
* found , - ENOENT if it doesn ' t .
*
* 2. With CHASE_OPEN : in this case the destination is opened after chasing it as O_PATH and this file
* descriptor is returned as return value . This is useful to open files relative to some root
* directory . Note that the returned O_PATH file descriptors must be converted into a regular one ( using
* fd_reopen ( ) or such ) before it can be used for reading / writing . CHASE_OPEN may not be combined with
* CHASE_NONEXISTENT .
*
* 3. With CHASE_STEP : in this case only a single step of the normalization is executed , i . e . only the first
* symlink or " .. " component of the path is resolved , and the resulting path is returned . This is useful if
* a caller wants to trace the a path through the file system verbosely . Returns < 0 on error , > 0 if the
* path is fully normalized , and = = 0 for each normalization step . This may be combined with
* CHASE_NONEXISTENT , in which case 1 is returned when a component is not found .
*
* */
2016-09-24 12:41:30 +02:00
2018-01-19 10:05:28 +01:00
/* A root directory of "/" or "" is identical to none */
2018-04-18 14:20:49 +02:00
if ( empty_or_root ( original_root ) )
2018-01-19 10:05:28 +01:00
original_root = NULL ;
2018-01-17 11:56:52 +01:00
2018-04-04 17:03:45 +02:00
if ( ! original_root & & ! ret & & ( flags & ( CHASE_NONEXISTENT | CHASE_NO_AUTOFS | CHASE_SAFE | CHASE_OPEN | CHASE_STEP ) ) = = CHASE_OPEN ) {
2018-03-26 16:34:54 +02:00
/* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
* and doesn ' t care about any of the other special features we provide either . */
2018-04-26 22:46:55 +02:00
r = open ( path , O_PATH | O_CLOEXEC | ( ( flags & CHASE_NOFOLLOW ) ? O_NOFOLLOW : 0 ) ) ;
2018-03-26 16:34:54 +02:00
if ( r < 0 )
return - errno ;
return r ;
}
2016-11-29 16:49:30 +01:00
if ( original_root ) {
r = path_make_absolute_cwd ( original_root , & root ) ;
2016-09-24 12:41:30 +02:00
if ( r < 0 )
return r ;
2016-11-29 16:49:30 +01:00
2018-01-17 12:00:40 +01:00
if ( flags & CHASE_PREFIX_ROOT ) {
/* We don't support relative paths in combination with a root directory */
if ( ! path_is_absolute ( path ) )
return - EINVAL ;
2016-11-29 16:49:30 +01:00
path = prefix_roota ( root , path ) ;
2018-01-17 12:00:40 +01:00
}
2016-09-24 12:41:30 +02:00
}
2016-11-29 16:49:30 +01:00
r = path_make_absolute_cwd ( path , & buffer ) ;
if ( r < 0 )
return r ;
2016-09-24 12:41:30 +02:00
fd = open ( " / " , O_CLOEXEC | O_NOFOLLOW | O_PATH ) ;
if ( fd < 0 )
return - errno ;
2018-01-04 19:44:27 +01:00
if ( flags & CHASE_SAFE ) {
if ( fstat ( fd , & previous_stat ) < 0 )
return - errno ;
}
2016-09-24 12:41:30 +02:00
todo = buffer ;
for ( ; ; ) {
_cleanup_free_ char * first = NULL ;
_cleanup_close_ int child = - 1 ;
struct stat st ;
size_t n , m ;
/* Determine length of first component in the path */
n = strspn ( todo , " / " ) ; /* The slashes */
m = n + strcspn ( todo + n , " / " ) ; /* The entire length of the component */
/* Extract the first component. */
first = strndup ( todo , m ) ;
if ( ! first )
return - ENOMEM ;
todo + = m ;
util-lib: use trailing slash in chase_symlinks, fd_is_mount_point, path_is_mount_point
The kernel will reply with -ENOTDIR when we try to access a non-directory under
a name which ends with a slash. But our functions would strip the trailing slash
under various circumstances. Keep the trailing slash, so that
path_is_mount_point("/path/to/file/") return -ENOTDIR when /path/to/file/ is a file.
Tests are added for this change in behaviour.
Also, when called with a trailing slash, path_is_mount_point() would get
"" from basename(), and call name_to_handle_at(3, "", ...), and always
return -ENOENT. Now it'll return -ENOTDIR if the mount point is a file, and
true if it is a directory and a mount point.
v2:
- use strip_trailing_chars()
v3:
- instead of stripping trailing chars(), do the opposite — preserve them.
2017-10-31 11:08:30 +01:00
/* Empty? Then we reached the end. */
if ( isempty ( first ) )
break ;
2016-09-24 12:41:30 +02:00
/* Just a single slash? Then we reached the end. */
util-lib: use trailing slash in chase_symlinks, fd_is_mount_point, path_is_mount_point
The kernel will reply with -ENOTDIR when we try to access a non-directory under
a name which ends with a slash. But our functions would strip the trailing slash
under various circumstances. Keep the trailing slash, so that
path_is_mount_point("/path/to/file/") return -ENOTDIR when /path/to/file/ is a file.
Tests are added for this change in behaviour.
Also, when called with a trailing slash, path_is_mount_point() would get
"" from basename(), and call name_to_handle_at(3, "", ...), and always
return -ENOENT. Now it'll return -ENOTDIR if the mount point is a file, and
true if it is a directory and a mount point.
v2:
- use strip_trailing_chars()
v3:
- instead of stripping trailing chars(), do the opposite — preserve them.
2017-10-31 11:08:30 +01:00
if ( path_equal ( first , " / " ) ) {
/* Preserve the trailing slash */
2018-03-22 19:54:24 +01:00
if ( flags & CHASE_TRAIL_SLASH )
if ( ! strextend ( & done , " / " , NULL ) )
return - ENOMEM ;
util-lib: use trailing slash in chase_symlinks, fd_is_mount_point, path_is_mount_point
The kernel will reply with -ENOTDIR when we try to access a non-directory under
a name which ends with a slash. But our functions would strip the trailing slash
under various circumstances. Keep the trailing slash, so that
path_is_mount_point("/path/to/file/") return -ENOTDIR when /path/to/file/ is a file.
Tests are added for this change in behaviour.
Also, when called with a trailing slash, path_is_mount_point() would get
"" from basename(), and call name_to_handle_at(3, "", ...), and always
return -ENOENT. Now it'll return -ENOTDIR if the mount point is a file, and
true if it is a directory and a mount point.
v2:
- use strip_trailing_chars()
v3:
- instead of stripping trailing chars(), do the opposite — preserve them.
2017-10-31 11:08:30 +01:00
2016-09-24 12:41:30 +02:00
break ;
util-lib: use trailing slash in chase_symlinks, fd_is_mount_point, path_is_mount_point
The kernel will reply with -ENOTDIR when we try to access a non-directory under
a name which ends with a slash. But our functions would strip the trailing slash
under various circumstances. Keep the trailing slash, so that
path_is_mount_point("/path/to/file/") return -ENOTDIR when /path/to/file/ is a file.
Tests are added for this change in behaviour.
Also, when called with a trailing slash, path_is_mount_point() would get
"" from basename(), and call name_to_handle_at(3, "", ...), and always
return -ENOENT. Now it'll return -ENOTDIR if the mount point is a file, and
true if it is a directory and a mount point.
v2:
- use strip_trailing_chars()
v3:
- instead of stripping trailing chars(), do the opposite — preserve them.
2017-10-31 11:08:30 +01:00
}
2016-09-24 12:41:30 +02:00
/* Just a dot? Then let's eat this up. */
if ( path_equal ( first , " /. " ) )
continue ;
/* Two dots? Then chop off the last bit of what we already found out. */
if ( path_equal ( first , " /.. " ) ) {
_cleanup_free_ char * parent = NULL ;
2018-01-21 11:07:10 +01:00
_cleanup_close_ int fd_parent = - 1 ;
2016-09-24 12:41:30 +02:00
2016-11-29 15:54:42 +01:00
/* If we already are at the top, then going up will not change anything. This is in-line with
* how the kernel handles this . */
2018-04-18 14:20:49 +02:00
if ( empty_or_root ( done ) )
2016-11-29 15:54:42 +01:00
continue ;
2016-09-24 12:41:30 +02:00
parent = dirname_malloc ( done ) ;
if ( ! parent )
return - ENOMEM ;
2016-11-29 15:54:42 +01:00
/* Don't allow this to leave the root dir. */
2016-09-24 12:41:30 +02:00
if ( root & &
path_startswith ( done , root ) & &
! path_startswith ( parent , root ) )
2016-11-29 15:54:42 +01:00
continue ;
2016-09-24 12:41:30 +02:00
2016-10-17 01:23:35 +02:00
free_and_replace ( done , parent ) ;
2016-09-24 12:41:30 +02:00
2018-04-04 17:03:45 +02:00
if ( flags & CHASE_STEP )
goto chased_one ;
2016-09-24 12:41:30 +02:00
fd_parent = openat ( fd , " .. " , O_CLOEXEC | O_NOFOLLOW | O_PATH ) ;
if ( fd_parent < 0 )
return - errno ;
2018-01-04 19:44:27 +01:00
if ( flags & CHASE_SAFE ) {
if ( fstat ( fd_parent , & st ) < 0 )
return - errno ;
if ( ! safe_transition ( & previous_stat , & st ) )
return - EPERM ;
previous_stat = st ;
}
2016-09-24 12:41:30 +02:00
safe_close ( fd ) ;
2018-03-22 17:04:29 +01:00
fd = TAKE_FD ( fd_parent ) ;
2016-09-24 12:41:30 +02:00
continue ;
}
/* Otherwise let's see what this is. */
child = openat ( fd , first + n , O_CLOEXEC | O_NOFOLLOW | O_PATH ) ;
2016-11-29 18:02:45 +01:00
if ( child < 0 ) {
if ( errno = = ENOENT & &
2016-12-01 12:49:23 +01:00
( flags & CHASE_NONEXISTENT ) & &
2017-10-27 16:28:15 +02:00
( isempty ( todo ) | | path_is_normalized ( todo ) ) ) {
2016-11-29 18:02:45 +01:00
2016-12-01 12:49:23 +01:00
/* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
2016-11-29 18:02:45 +01:00
* what we got so far . But don ' t allow this if the remaining path contains " ../ or " . / "
* or something else weird . */
2017-11-30 18:19:44 +01:00
/* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
if ( streq_ptr ( done , " / " ) )
* done = ' \0 ' ;
2016-11-29 18:02:45 +01:00
if ( ! strextend ( & done , first , todo , NULL ) )
return - ENOMEM ;
exists = false ;
break ;
}
2016-09-24 12:41:30 +02:00
return - errno ;
2016-11-29 18:02:45 +01:00
}
2016-09-24 12:41:30 +02:00
if ( fstat ( child , & st ) < 0 )
return - errno ;
2018-01-04 19:44:27 +01:00
if ( ( flags & CHASE_SAFE ) & &
! safe_transition ( & previous_stat , & st ) )
return - EPERM ;
previous_stat = st ;
2017-09-04 15:35:07 +02:00
if ( ( flags & CHASE_NO_AUTOFS ) & &
2017-10-31 13:02:10 +01:00
fd_is_fs_type ( child , AUTOFS_SUPER_MAGIC ) > 0 )
2017-09-04 15:35:07 +02:00
return - EREMOTE ;
2016-09-24 12:41:30 +02:00
2018-04-26 22:46:55 +02:00
if ( S_ISLNK ( st . st_mode ) & & ! ( ( flags & CHASE_NOFOLLOW ) & & isempty ( todo ) ) ) {
2017-01-31 14:21:15 +01:00
char * joined ;
2016-09-24 12:41:30 +02:00
_cleanup_free_ char * destination = NULL ;
/* This is a symlink, in this case read the destination. But let's make sure we don't follow
* symlinks without bounds . */
if ( - - max_follow < = 0 )
return - ELOOP ;
r = readlinkat_malloc ( fd , first + n , & destination ) ;
if ( r < 0 )
return r ;
if ( isempty ( destination ) )
return - EINVAL ;
if ( path_is_absolute ( destination ) ) {
/* An absolute destination. Start the loop from the beginning, but use the root
* directory as base . */
safe_close ( fd ) ;
fd = open ( root ? : " / " , O_CLOEXEC | O_NOFOLLOW | O_PATH ) ;
if ( fd < 0 )
return - errno ;
2018-01-04 19:44:27 +01:00
if ( flags & CHASE_SAFE ) {
if ( fstat ( fd , & st ) < 0 )
return - errno ;
if ( ! safe_transition ( & previous_stat , & st ) )
return - EPERM ;
previous_stat = st ;
}
2018-01-21 11:19:25 +01:00
free ( done ) ;
2016-09-24 12:41:30 +02:00
/* Note that we do not revalidate the root, we take it as is. */
if ( isempty ( root ) )
done = NULL ;
else {
done = strdup ( root ) ;
if ( ! done )
return - ENOMEM ;
}
2017-10-04 17:34:03 +02:00
/* Prefix what's left to do with what we just read, and start the loop again, but
* remain in the current directory . */
joined = strjoin ( destination , todo ) ;
} else
joined = strjoin ( " / " , destination , todo ) ;
2017-01-31 14:21:15 +01:00
if ( ! joined )
return - ENOMEM ;
2016-09-24 12:41:30 +02:00
2017-01-31 14:21:15 +01:00
free ( buffer ) ;
todo = buffer = joined ;
2016-09-24 12:41:30 +02:00
2018-04-04 17:03:45 +02:00
if ( flags & CHASE_STEP )
goto chased_one ;
2016-09-24 12:41:30 +02:00
continue ;
}
/* If this is not a symlink, then let's just add the name we read to what we already verified. */
2018-03-22 16:53:26 +01:00
if ( ! done )
done = TAKE_PTR ( first ) ;
else {
2017-11-30 18:19:44 +01:00
/* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
if ( streq ( done , " / " ) )
* done = ' \0 ' ;
2016-09-24 12:41:30 +02:00
if ( ! strextend ( & done , first , NULL ) )
return - ENOMEM ;
}
/* And iterate again, but go one directory further down. */
safe_close ( fd ) ;
2018-03-22 17:04:29 +01:00
fd = TAKE_FD ( child ) ;
2016-09-24 12:41:30 +02:00
}
if ( ! done ) {
/* Special case, turn the empty string into "/", to indicate the root directory. */
done = strdup ( " / " ) ;
if ( ! done )
return - ENOMEM ;
}
2018-03-22 16:53:26 +01:00
if ( ret )
* ret = TAKE_PTR ( done ) ;
2016-09-24 12:41:30 +02:00
2018-01-04 20:00:28 +01:00
if ( flags & CHASE_OPEN ) {
/* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
* opening / proc / self / fd / xyz . */
assert ( fd > = 0 ) ;
2018-03-22 17:04:29 +01:00
return TAKE_FD ( fd ) ;
2018-01-04 20:00:28 +01:00
}
2018-04-04 17:03:45 +02:00
if ( flags & CHASE_STEP )
return 1 ;
2016-11-29 18:02:45 +01:00
return exists ;
2018-04-04 17:03:45 +02:00
chased_one :
if ( ret ) {
char * c ;
2018-05-10 01:55:05 +02:00
c = strjoin ( strempty ( done ) , todo ) ;
if ( ! c )
return - ENOMEM ;
2018-04-04 17:03:45 +02:00
* ret = c ;
}
return 0 ;
2016-09-24 12:41:30 +02:00
}
2017-11-16 18:56:25 +01:00
2018-03-26 14:15:43 +02:00
int chase_symlinks_and_open (
const char * path ,
const char * root ,
unsigned chase_flags ,
int open_flags ,
char * * ret_path ) {
_cleanup_close_ int path_fd = - 1 ;
_cleanup_free_ char * p = NULL ;
int r ;
if ( chase_flags & CHASE_NONEXISTENT )
return - EINVAL ;
2018-04-18 14:20:49 +02:00
if ( empty_or_root ( root ) & & ! ret_path & & ( chase_flags & ( CHASE_NO_AUTOFS | CHASE_SAFE ) ) = = 0 ) {
2018-03-26 14:15:43 +02:00
/* Shortcut this call if none of the special features of this call are requested */
r = open ( path , open_flags ) ;
if ( r < 0 )
return - errno ;
return r ;
}
path_fd = chase_symlinks ( path , root , chase_flags | CHASE_OPEN , ret_path ? & p : NULL ) ;
if ( path_fd < 0 )
return path_fd ;
r = fd_reopen ( path_fd , open_flags ) ;
if ( r < 0 )
return r ;
if ( ret_path )
* ret_path = TAKE_PTR ( p ) ;
return r ;
}
int chase_symlinks_and_opendir (
const char * path ,
const char * root ,
unsigned chase_flags ,
char * * ret_path ,
DIR * * ret_dir ) {
char procfs_path [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) ] ;
_cleanup_close_ int path_fd = - 1 ;
_cleanup_free_ char * p = NULL ;
DIR * d ;
if ( ! ret_dir )
return - EINVAL ;
if ( chase_flags & CHASE_NONEXISTENT )
return - EINVAL ;
2018-04-18 14:20:49 +02:00
if ( empty_or_root ( root ) & & ! ret_path & & ( chase_flags & ( CHASE_NO_AUTOFS | CHASE_SAFE ) ) = = 0 ) {
2018-03-26 14:15:43 +02:00
/* Shortcut this call if none of the special features of this call are requested */
d = opendir ( path ) ;
if ( ! d )
return - errno ;
* ret_dir = d ;
return 0 ;
}
path_fd = chase_symlinks ( path , root , chase_flags | CHASE_OPEN , ret_path ? & p : NULL ) ;
if ( path_fd < 0 )
return path_fd ;
xsprintf ( procfs_path , " /proc/self/fd/%i " , path_fd ) ;
d = opendir ( procfs_path ) ;
if ( ! d )
return - errno ;
if ( ret_path )
* ret_path = TAKE_PTR ( p ) ;
* ret_dir = d ;
return 0 ;
}
2018-04-18 16:19:46 +02:00
int chase_symlinks_and_stat (
const char * path ,
const char * root ,
unsigned chase_flags ,
char * * ret_path ,
struct stat * ret_stat ) {
_cleanup_close_ int path_fd = - 1 ;
_cleanup_free_ char * p = NULL ;
assert ( path ) ;
assert ( ret_stat ) ;
if ( chase_flags & CHASE_NONEXISTENT )
return - EINVAL ;
if ( empty_or_root ( root ) & & ! ret_path & & ( chase_flags & ( CHASE_NO_AUTOFS | CHASE_SAFE ) ) = = 0 ) {
/* Shortcut this call if none of the special features of this call are requested */
if ( stat ( path , ret_stat ) < 0 )
return - errno ;
return 1 ;
}
path_fd = chase_symlinks ( path , root , chase_flags | CHASE_OPEN , ret_path ? & p : NULL ) ;
if ( path_fd < 0 )
return path_fd ;
if ( fstat ( path_fd , ret_stat ) < 0 )
return - errno ;
if ( ret_path )
* ret_path = TAKE_PTR ( p ) ;
if ( chase_flags & CHASE_OPEN )
return TAKE_FD ( path_fd ) ;
return 1 ;
}
2017-11-16 18:56:25 +01:00
int access_fd ( int fd , int mode ) {
2017-12-14 19:02:29 +01:00
char p [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( fd ) + 1 ] ;
2017-11-16 18:56:25 +01:00
int r ;
/* Like access() but operates on an already open fd */
xsprintf ( p , " /proc/self/fd/%i " , fd ) ;
r = access ( p , mode ) ;
if ( r < 0 )
2018-03-26 14:15:43 +02:00
return - errno ;
2017-11-16 18:56:25 +01:00
return r ;
}
2018-02-09 09:50:31 +01:00
2018-05-16 11:35:41 +02:00
void unlink_tempfilep ( char ( * p ) [ ] ) {
/* If the file is created with mkstemp(), it will (almost always)
* change the suffix . Treat this as a sign that the file was
* successfully created . We ignore both the rare case where the
* original suffix is used and unlink failures . */
if ( ! endswith ( * p , " .XXXXXX " ) )
2018-05-18 20:14:54 +02:00
( void ) unlink_noerrno ( * p ) ;
2018-05-16 11:35:41 +02:00
}
2018-02-09 09:50:31 +01:00
int unlinkat_deallocate ( int fd , const char * name , int flags ) {
_cleanup_close_ int truncate_fd = - 1 ;
struct stat st ;
off_t l , bs ;
/* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other
* link to it . This is useful to ensure that other processes that might have the file open for reading won ' t be
* able to keep the data pinned on disk forever . This call is particular useful whenever we execute clean - up
* jobs ( " vacuuming " ) , where we want to make sure the data is really gone and the disk space released and
* returned to the free pool .
*
* Deallocation is preferably done by FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE ( 👊 ) if supported , which means
* the file won ' t change size . That ' s a good thing since we shouldn ' t needlessly trigger SIGBUS in other
* programs that have mmap ( ) ed the file . ( The assumption here is that changing file contents to all zeroes
* underneath those programs is the better choice than simply triggering SIGBUS in them which truncation does . )
* However if hole punching is not implemented in the kernel or file system we ' ll fall back to normal file
* truncation ( 🔪 ) , as our goal of deallocating the data space trumps our goal of being nice to readers ( 💐 ) .
*
* Note that we attempt deallocation , but failure to succeed with that is not considered fatal , as long as the
* primary job – to delete the file – is accomplished . */
if ( ( flags & AT_REMOVEDIR ) = = 0 ) {
truncate_fd = openat ( fd , name , O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK ) ;
if ( truncate_fd < 0 ) {
/* If this failed because the file doesn't exist propagate the error right-away. Also,
* AT_REMOVEDIR wasn ' t set , and we tried to open the file for writing , which means EISDIR is
* returned when this is a directory but we are not supposed to delete those , hence propagate
* the error right - away too . */
if ( IN_SET ( errno , ENOENT , EISDIR ) )
return - errno ;
if ( errno ! = ELOOP ) /* don't complain if this is a symlink */
log_debug_errno ( errno , " Failed to open file '%s' for deallocation, ignoring: %m " , name ) ;
}
}
if ( unlinkat ( fd , name , flags ) < 0 )
return - errno ;
if ( truncate_fd < 0 ) /* Don't have a file handle, can't do more ☹️ */
return 0 ;
if ( fstat ( truncate_fd , & st ) < 0 ) {
log_debug_errno ( errno , " Failed to stat file '%s' for deallocation, ignoring. " , name ) ;
return 0 ;
}
if ( ! S_ISREG ( st . st_mode ) | | st . st_blocks = = 0 | | st . st_nlink > 0 )
return 0 ;
/* If this is a regular file, it actually took up space on disk and there are no other links it's time to
* punch - hole / truncate this to release the disk space . */
bs = MAX ( st . st_blksize , 512 ) ;
l = DIV_ROUND_UP ( st . st_size , bs ) * bs ; /* Round up to next block size */
if ( fallocate ( truncate_fd , FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE , 0 , l ) > = 0 )
return 0 ; /* Successfully punched a hole! 😊 */
/* Fall back to truncation */
if ( ftruncate ( truncate_fd , 0 ) < 0 ) {
log_debug_errno ( errno , " Failed to truncate file to 0, ignoring: %m " ) ;
return 0 ;
}
return 0 ;
}
2018-02-19 18:23:38 +01:00
int fsync_directory_of_file ( int fd ) {
2018-07-20 12:02:14 +02:00
_cleanup_free_ char * path = NULL ;
2018-02-19 18:23:38 +01:00
_cleanup_close_ int dfd = - 1 ;
int r ;
r = fd_verify_regular ( fd ) ;
if ( r < 0 )
return r ;
r = fd_get_path ( fd , & path ) ;
basic/fs-util: skip fsync_directory_of_file() if /proc/self/fd/ is not available (#8386)
When systemd is running under lorax (in Fedora compose process), it'd think that
it failed to write /etc/machine-id, even though the write succeeded, because
fsync_directory_of_file() would fail, because /proc/self/fd/ is not available.
fsync_directory_of_file() is mostly an additional safety net, so I think it's best
to just silently ignore the error.
Strace of pid1:
35791 stat("/etc", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
35791 openat(AT_FDCWD, "/etc/machine-id", O_RDWR|O_CREAT|O_NOCTTY|O_CLOEXEC, 0444) = 3
35791 umask(022) = 000
35791 read(3, "", 38) = 0
35791 openat(AT_FDCWD, "/var/lib/dbus/machine-id", O_RDONLY|O_NOCTTY|O_NOFOLLOW|O_CLOEXEC) = -1 ENOENT (No such file o
r directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/product_name", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/sys_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/board_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/bios_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 access("/proc/xen", F_OK) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/hypervisor/type", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/proc/cpuinfo", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 getrandom("\xb8\x82\xed\xd4\x35\x11\xd0\xeb\xa6\x79\xd7\x31\x6e\x7b\x99\xce", 16, GRND_NONBLOCK) = 16
35791 writev(2, [{iov_base="Initializing machine ID from random generator.", iov_len=46}, {iov_base="\n", iov_len=1}],
2) = 47
35791 lseek(3, 0, SEEK_SET) = 0
35791 ftruncate(3, 0) = 0
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 fsync(3) = 0
35791 fstat(3, {st_mode=S_IFREG|0444, st_size=33, ...}) = 0
35791 readlinkat(AT_FDCWD, "/proc/self/fd/3", 0x564df8c694c0, 99) = -1 ENOENT (No such file or directory)
35791 close(3) = 0
35791 umask(022) = 022
35791 openat(AT_FDCWD, "/run/machine-id", O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0444) = 3
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 close(3) = 0
35791 umask(022) = 022
35791 mount("/run/machine-id", "/etc/machine-id", NULL, MS_BIND, NULL) = 0
35791 writev(2, [{iov_base="Installed transient /etc/machine-id file.", iov_len=41}, {iov_base="\n", iov_len=1}], 2) = 42
35791 mount(NULL, "/etc/machine-id", NULL, MS_RDONLY|MS_REMOUNT|MS_BIND, NULL) = 0
https://bugzilla.redhat.com/show_bug.cgi?id=1552843
2018-03-20 18:20:01 +01:00
if ( r < 0 ) {
2018-06-05 20:18:47 +02:00
log_debug_errno ( r , " Failed to query /proc/self/fd/%d%s: %m " ,
fd ,
r = = - EOPNOTSUPP ? " , ignoring " : " " ) ;
basic/fs-util: skip fsync_directory_of_file() if /proc/self/fd/ is not available (#8386)
When systemd is running under lorax (in Fedora compose process), it'd think that
it failed to write /etc/machine-id, even though the write succeeded, because
fsync_directory_of_file() would fail, because /proc/self/fd/ is not available.
fsync_directory_of_file() is mostly an additional safety net, so I think it's best
to just silently ignore the error.
Strace of pid1:
35791 stat("/etc", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
35791 openat(AT_FDCWD, "/etc/machine-id", O_RDWR|O_CREAT|O_NOCTTY|O_CLOEXEC, 0444) = 3
35791 umask(022) = 000
35791 read(3, "", 38) = 0
35791 openat(AT_FDCWD, "/var/lib/dbus/machine-id", O_RDONLY|O_NOCTTY|O_NOFOLLOW|O_CLOEXEC) = -1 ENOENT (No such file o
r directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/product_name", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/sys_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/board_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/bios_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 access("/proc/xen", F_OK) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/hypervisor/type", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/proc/cpuinfo", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 getrandom("\xb8\x82\xed\xd4\x35\x11\xd0\xeb\xa6\x79\xd7\x31\x6e\x7b\x99\xce", 16, GRND_NONBLOCK) = 16
35791 writev(2, [{iov_base="Initializing machine ID from random generator.", iov_len=46}, {iov_base="\n", iov_len=1}],
2) = 47
35791 lseek(3, 0, SEEK_SET) = 0
35791 ftruncate(3, 0) = 0
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 fsync(3) = 0
35791 fstat(3, {st_mode=S_IFREG|0444, st_size=33, ...}) = 0
35791 readlinkat(AT_FDCWD, "/proc/self/fd/3", 0x564df8c694c0, 99) = -1 ENOENT (No such file or directory)
35791 close(3) = 0
35791 umask(022) = 022
35791 openat(AT_FDCWD, "/run/machine-id", O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0444) = 3
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 close(3) = 0
35791 umask(022) = 022
35791 mount("/run/machine-id", "/etc/machine-id", NULL, MS_BIND, NULL) = 0
35791 writev(2, [{iov_base="Installed transient /etc/machine-id file.", iov_len=41}, {iov_base="\n", iov_len=1}], 2) = 42
35791 mount(NULL, "/etc/machine-id", NULL, MS_RDONLY|MS_REMOUNT|MS_BIND, NULL) = 0
https://bugzilla.redhat.com/show_bug.cgi?id=1552843
2018-03-20 18:20:01 +01:00
if ( r = = - EOPNOTSUPP )
/* If /proc is not available, we're most likely running in some
* chroot environment , and syncing the directory is not very
* important in that case . Let ' s just silently do nothing . */
return 0 ;
2018-02-19 18:23:38 +01:00
return r ;
basic/fs-util: skip fsync_directory_of_file() if /proc/self/fd/ is not available (#8386)
When systemd is running under lorax (in Fedora compose process), it'd think that
it failed to write /etc/machine-id, even though the write succeeded, because
fsync_directory_of_file() would fail, because /proc/self/fd/ is not available.
fsync_directory_of_file() is mostly an additional safety net, so I think it's best
to just silently ignore the error.
Strace of pid1:
35791 stat("/etc", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
35791 openat(AT_FDCWD, "/etc/machine-id", O_RDWR|O_CREAT|O_NOCTTY|O_CLOEXEC, 0444) = 3
35791 umask(022) = 000
35791 read(3, "", 38) = 0
35791 openat(AT_FDCWD, "/var/lib/dbus/machine-id", O_RDONLY|O_NOCTTY|O_NOFOLLOW|O_CLOEXEC) = -1 ENOENT (No such file o
r directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/product_name", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/sys_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/board_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/class/dmi/id/bios_vendor", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 access("/proc/xen", F_OK) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/sys/hypervisor/type", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 openat(AT_FDCWD, "/proc/cpuinfo", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
35791 getrandom("\xb8\x82\xed\xd4\x35\x11\xd0\xeb\xa6\x79\xd7\x31\x6e\x7b\x99\xce", 16, GRND_NONBLOCK) = 16
35791 writev(2, [{iov_base="Initializing machine ID from random generator.", iov_len=46}, {iov_base="\n", iov_len=1}],
2) = 47
35791 lseek(3, 0, SEEK_SET) = 0
35791 ftruncate(3, 0) = 0
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 fsync(3) = 0
35791 fstat(3, {st_mode=S_IFREG|0444, st_size=33, ...}) = 0
35791 readlinkat(AT_FDCWD, "/proc/self/fd/3", 0x564df8c694c0, 99) = -1 ENOENT (No such file or directory)
35791 close(3) = 0
35791 umask(022) = 022
35791 openat(AT_FDCWD, "/run/machine-id", O_WRONLY|O_CREAT|O_NOCTTY|O_TRUNC|O_CLOEXEC, 0444) = 3
35791 write(3, "b882edd4351140eba679d7316e7b99ce\n", 33) = 33
35791 close(3) = 0
35791 umask(022) = 022
35791 mount("/run/machine-id", "/etc/machine-id", NULL, MS_BIND, NULL) = 0
35791 writev(2, [{iov_base="Installed transient /etc/machine-id file.", iov_len=41}, {iov_base="\n", iov_len=1}], 2) = 42
35791 mount(NULL, "/etc/machine-id", NULL, MS_RDONLY|MS_REMOUNT|MS_BIND, NULL) = 0
https://bugzilla.redhat.com/show_bug.cgi?id=1552843
2018-03-20 18:20:01 +01:00
}
2018-02-19 18:23:38 +01:00
if ( ! path_is_absolute ( path ) )
return - EINVAL ;
2018-07-20 12:02:14 +02:00
dfd = open_parent ( path , O_CLOEXEC , 0 ) ;
2018-02-19 18:23:38 +01:00
if ( dfd < 0 )
2018-07-20 12:02:14 +02:00
return dfd ;
2018-02-19 18:23:38 +01:00
if ( fsync ( dfd ) < 0 )
return - errno ;
return 0 ;
}
2018-07-20 11:57:24 +02:00
int open_parent ( const char * path , int flags , mode_t mode ) {
_cleanup_free_ char * parent = NULL ;
int fd ;
if ( isempty ( path ) )
return - EINVAL ;
if ( path_equal ( path , " / " ) ) /* requesting the parent of the root dir is fishy, let's prohibit that */
return - EINVAL ;
parent = dirname_malloc ( path ) ;
if ( ! parent )
return - ENOMEM ;
/* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
* O_TMPFILE file , because in that case we are actually create a regular file below the parent directory . */
if ( ( flags & O_PATH ) = = O_PATH )
flags | = O_DIRECTORY ;
else if ( ( flags & O_TMPFILE ) ! = O_TMPFILE )
flags | = O_DIRECTORY | O_RDONLY ;
fd = open ( parent , flags , mode ) ;
if ( fd < 0 )
return - errno ;
return fd ;
}