2020-11-09 05:23:58 +01:00
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2015-10-26 21:16:26 +01:00
2015-11-30 21:43:37 +01:00
# include <errno.h>
# include <stddef.h>
# include <stdlib.h>
2019-02-26 22:09:40 +01:00
# include <linux/falloc.h>
2017-09-04 15:35:07 +02:00
# include <linux/magic.h>
2015-11-30 21:43:37 +01:00
# include <unistd.h>
2015-10-27 03:01:06 +01:00
# include "alloc-util.h"
2020-05-01 19:37:24 +02:00
# include "blockdev-util.h"
2015-10-26 21:16:26 +01:00
# include "dirent-util.h"
# include "fd-util.h"
2020-05-01 19:37:24 +02:00
# include "fileio.h"
2015-10-26 21:16:26 +01:00
# include "fs-util.h"
2018-11-29 11:08:52 +01:00
# include "locale-util.h"
2015-11-30 21:43:37 +01:00
# include "log.h"
# include "macro.h"
2019-11-07 07:25:43 +01:00
# include "missing_fcntl.h"
2019-10-31 03:07:23 +01:00
# include "missing_fs.h"
# include "missing_syscall.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"
2020-04-29 13:58:53 +02:00
# include "random-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"
2018-11-30 21:05:27 +01:00
# include "tmpfile-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 ;
2020-08-20 11:23:26 +02:00
/* OK, neither RENAME_NOREPLACE nor linkat()+unlinkat() worked. Let's then fall back to the racy TOCTOU
2018-10-02 13:34:18 +02:00
* 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 ) {
2018-10-25 21:16:47 +02:00
size_t l = FILENAME_MAX + 1 ;
2015-10-26 21:16:26 +01:00
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 ) {
2018-07-24 17:12:27 +02:00
_cleanup_close_ int fd = - 1 ;
2019-03-14 16:47:03 +01:00
2015-10-26 21:16:26 +01:00
assert ( path ) ;
2019-03-14 16:47:03 +01:00
fd = open ( path , O_PATH | O_CLOEXEC | O_NOFOLLOW ) ; /* Let's acquire an O_PATH fd, as precaution to change
* mode / owner on the same file */
2018-07-24 17:12:27 +02:00
if ( fd < 0 )
return - errno ;
2019-04-29 20:15:06 +02:00
return fchmod_and_chown ( fd , mode , uid , gid ) ;
2018-06-14 04:26:29 +02:00
}
int fchmod_and_chown ( int fd , mode_t mode , uid_t uid , gid_t gid ) {
2019-04-29 20:15:06 +02:00
bool do_chown , do_chmod ;
2019-03-14 16:47:03 +01:00
struct stat st ;
2020-09-09 20:35:33 +02:00
int r ;
2019-03-14 16:47:03 +01:00
2019-04-29 20:15:06 +02:00
/* Change ownership and access mode of the specified fd. Tries to do so safely, ensuring that at no
* point in time the access mode is above the old access mode under the old ownership or the new
* access mode under the new ownership . Note : this call tries hard to leave the access mode
* unaffected if the uid / gid is changed , i . e . it undoes implicit suid / sgid dropping the kernel does
* on chown ( ) .
*
2019-06-06 14:05:27 +02:00
* This call is happy with O_PATH fds . */
2018-06-14 04:26:29 +02:00
2019-06-06 14:05:27 +02:00
if ( fstat ( fd , & st ) < 0 )
2019-04-29 20:15:06 +02:00
return - errno ;
2018-07-24 17:12:27 +02:00
2019-04-29 20:15:06 +02:00
do_chown =
( uid ! = UID_INVALID & & st . st_uid ! = uid ) | |
( gid ! = GID_INVALID & & st . st_gid ! = gid ) ;
2018-07-24 17:12:27 +02:00
2019-04-29 20:15:06 +02:00
do_chmod =
! S_ISLNK ( st . st_mode ) & & /* chmod is not defined on symlinks */
( ( mode ! = MODE_INVALID & & ( ( st . st_mode ^ mode ) & 07777 ) ! = 0 ) | |
do_chown ) ; /* If we change ownership, make sure we reset the mode afterwards, since chown()
* modifies the access mode too */
2019-03-14 16:47:03 +01:00
2019-04-29 20:15:06 +02:00
if ( mode = = MODE_INVALID )
mode = st . st_mode ; /* If we only shall do a chown(), save original mode, since chown() might break it. */
else if ( ( mode & S_IFMT ) ! = 0 & & ( ( mode ^ st . st_mode ) & S_IFMT ) ! = 0 )
return - EINVAL ; /* insist on the right file type if it was specified */
2018-07-24 17:12:27 +02:00
2019-04-29 20:15:06 +02:00
if ( do_chown & & do_chmod ) {
mode_t minimal = st . st_mode & mode ; /* the subset of the old and the new mask */
2019-03-14 16:47:03 +01:00
2020-09-09 20:35:33 +02:00
if ( ( ( minimal ^ st . st_mode ) & 07777 ) ! = 0 ) {
r = fchmod_opath ( fd , minimal & 07777 ) ;
if ( r < 0 )
return r ;
}
2018-07-24 17:12:27 +02:00
}
2018-06-14 04:26:29 +02:00
2019-04-29 20:15:06 +02:00
if ( do_chown )
2019-06-06 14:05:27 +02:00
if ( fchownat ( fd , " " , uid , gid , AT_EMPTY_PATH ) < 0 )
2019-04-29 20:15:06 +02:00
return - errno ;
2019-03-14 16:47:03 +01:00
2020-09-09 20:35:33 +02:00
if ( do_chmod ) {
r = fchmod_opath ( fd , mode & 07777 ) ;
if ( r < 0 )
return r ;
}
2019-03-14 16:47:03 +01:00
2019-04-29 20:15:06 +02:00
return do_chown | | do_chmod ;
2015-10-26 21:16:26 +01:00
}
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 ) ;
2020-04-23 14:52:10 +02:00
if ( chmod ( procfs_path , m ) < 0 ) {
if ( errno ! = ENOENT )
return - errno ;
if ( proc_mounted ( ) = = 0 )
return - ENOSYS ; /* if we have no /proc/, the concept is not implementable */
return - ENOENT ;
}
2018-04-11 16:58:49 +02:00
return 0 ;
}
2020-09-25 16:40:02 +02:00
int futimens_opath ( int fd , const struct timespec ts [ 2 ] ) {
char procfs_path [ STRLEN ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) ] ;
/* Similar to fchmod_path() but for futimens() */
xsprintf ( procfs_path , " /proc/self/fd/%i " , fd ) ;
if ( utimensat ( AT_FDCWD , procfs_path , ts , 0 ) < 0 ) {
if ( errno ! = ENOENT )
return - errno ;
if ( proc_mounted ( ) = = 0 )
return - ENOSYS ; /* if we have no /proc/, the concept is not implementable */
return - ENOENT ;
}
return 0 ;
}
2020-06-02 16:44:34 +02:00
int stat_warn_permissions ( const char * path , const struct stat * st ) {
assert ( path ) ;
assert ( st ) ;
2015-10-26 21:16:26 +01:00
2019-04-01 20:13:36 +02:00
/* Don't complain if we are reading something that is not a file, for example /dev/null */
2020-06-02 16:44:34 +02:00
if ( ! S_ISREG ( st - > st_mode ) )
2019-04-01 20:13:36 +02:00
return 0 ;
2020-06-02 16:44:34 +02:00
if ( st - > st_mode & 0111 )
2015-10-26 21:16:26 +01:00
log_warning ( " Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway. " , path ) ;
2020-06-02 16:44:34 +02:00
if ( st - > st_mode & 0002 )
2015-10-26 21:16:26 +01:00
log_warning ( " Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway. " , path ) ;
2020-06-02 16:44:34 +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 ;
}
2020-06-02 16:44:34 +02:00
int fd_warn_permissions ( const char * path , int fd ) {
struct stat st ;
assert ( path ) ;
assert ( fd > = 0 ) ;
if ( fstat ( fd , & st ) < 0 )
return - errno ;
return stat_warn_permissions ( path , & st ) ;
}
2015-10-26 21:16:26 +01:00
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 ) ;
2019-04-30 19:25:29 +02:00
ret = fchmod_and_chown ( fd , mode , uid , gid ) ;
2017-12-27 16:20:28 +01:00
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 ] ;
2020-04-09 18:21:49 +02:00
int wd ;
2016-04-25 00:18:27 +02:00
/* 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 ) ;
2020-04-09 18:21:49 +02:00
wd = inotify_add_watch ( fd , path , mask ) ;
if ( wd < 0 )
2016-04-25 00:18:27 +02:00
return - errno ;
2020-04-09 18:21:49 +02:00
return wd ;
2016-04-25 00:18:27 +02:00
}
2016-09-24 12:41:30 +02:00
2019-09-17 11:16:52 +02:00
int inotify_add_watch_and_warn ( int fd , const char * pathname , uint32_t mask ) {
2020-04-09 18:21:49 +02:00
int wd ;
2019-09-17 11:16:52 +02:00
2020-04-09 18:21:49 +02:00
wd = inotify_add_watch ( fd , pathname , mask ) ;
if ( wd < 0 ) {
2019-09-17 11:16:52 +02:00
if ( errno = = ENOSPC )
2019-11-01 11:43:34 +01:00
return log_error_errno ( errno , " Failed to add a watch for %s: inotify watch limit reached " , pathname ) ;
2019-09-17 11:16:52 +02:00
2019-11-01 11:43:34 +01:00
return log_error_errno ( errno , " Failed to add a watch for %s: %m " , pathname ) ;
2019-09-17 11:16:52 +02:00
}
2020-04-09 18:21:49 +02:00
return wd ;
2019-09-17 11:16:52 +02:00
}
2018-11-29 11:21:12 +01:00
static bool unsafe_transition ( const struct stat * a , const struct stat * b ) {
2018-01-04 19:44:27 +01:00
/* 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 */
2018-11-29 11:21:12 +01:00
return false ;
2018-01-04 19:44:27 +01:00
2018-11-29 11:21:12 +01:00
return a - > st_uid ! = b - > st_uid ; /* Otherwise we need to stay within the same UID */
2018-01-04 19:44:27 +01:00
}
2018-11-29 11:08:52 +01:00
static int log_unsafe_transition ( int a , int b , const char * path , unsigned flags ) {
_cleanup_free_ char * n1 = NULL , * n2 = NULL ;
if ( ! FLAGS_SET ( flags , CHASE_WARN ) )
2018-11-30 15:13:44 +01:00
return - ENOLINK ;
2018-11-29 11:08:52 +01:00
( void ) fd_get_path ( a , & n1 ) ;
( void ) fd_get_path ( b , & n2 ) ;
2018-11-30 15:13:44 +01:00
return log_warning_errno ( SYNTHETIC_ERRNO ( ENOLINK ) ,
2018-11-29 11:08:52 +01:00
" Detected unsafe path transition %s %s %s during canonicalization of %s. " ,
2020-09-23 18:18:03 +02:00
strna ( n1 ) , special_glyph ( SPECIAL_GLYPH_ARROW ) , strna ( n2 ) , path ) ;
2018-11-29 11:08:52 +01:00
}
2018-11-30 15:43:13 +01:00
static int log_autofs_mount_point ( int fd , const char * path , unsigned flags ) {
_cleanup_free_ char * n1 = NULL ;
if ( ! FLAGS_SET ( flags , CHASE_WARN ) )
return - EREMOTE ;
( void ) fd_get_path ( fd , & n1 ) ;
return log_warning_errno ( SYNTHETIC_ERRNO ( EREMOTE ) ,
" Detected autofs mount point %s during canonicalization of %s. " ,
2020-09-23 18:18:03 +02:00
strna ( n1 ) , path ) ;
2018-01-04 19:44:27 +01:00
}
2019-10-24 10:33:20 +02:00
int chase_symlinks ( const char * path , const char * original_root , unsigned flags , char * * ret_path , int * ret_fd ) {
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 */
2019-10-24 10:33:20 +02:00
if ( ( flags & CHASE_NONEXISTENT ) & & ret_fd )
2018-01-04 20:00:28 +01:00
return - EINVAL ;
2019-10-24 10:33:20 +02:00
if ( ( flags & CHASE_STEP ) & & ret_fd )
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
2019-07-18 13:14:17 +02:00
* to a minimum .
2016-11-25 18:59:39 +01:00
*
* 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 .
*
2019-10-24 10:33:20 +02:00
* There are five ways to invoke this function :
2018-04-04 17:03:45 +02:00
*
2019-10-24 10:33:20 +02:00
* 1. Without CHASE_STEP or ret_fd : in this case the path is resolved and the normalized path is
* returned in ` ret_path ` . 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 wasn ' t .
2018-04-04 17:03:45 +02:00
*
2019-10-24 10:33:20 +02:00
* 2. With ret_fd : in this case the destination is opened after chasing it as O_PATH and this file
2018-04-04 17:03:45 +02:00
* 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
2019-10-24 10:33:20 +02:00
* fd_reopen ( ) or such ) before it can be used for reading / writing . ret_fd may not be combined with
2018-04-04 17:03:45 +02:00
* 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
2020-11-13 08:19:49 +01:00
* a caller wants to trace the path through the file system verbosely . Returns < 0 on error , > 0 if the
2018-04-04 17:03:45 +02:00
* 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 .
*
2018-11-30 15:13:44 +01:00
* 4. With CHASE_SAFE : in this case the path must not contain unsafe transitions , i . e . transitions from
* unprivileged to privileged files or directories . In such cases the return value is - ENOLINK . If
2019-07-18 13:14:17 +02:00
* CHASE_WARN is also set , a warning describing the unsafe transition is emitted .
2018-11-30 15:13:44 +01:00
*
2019-07-18 13:14:17 +02:00
* 5. With CHASE_NO_AUTOFS : in this case if an autofs mount point is encountered , path normalization
* is aborted and - EREMOTE is returned . If CHASE_WARN is also set , a warning showing the path of
* the mount point is emitted .
*/
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
2019-10-24 10:33:20 +02:00
if ( ! original_root & & ! ret_path & & ! ( flags & ( CHASE_NONEXISTENT | CHASE_NO_AUTOFS | CHASE_SAFE | CHASE_STEP ) ) & & ret_fd ) {
/* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root set
2018-03-26 16:34:54 +02:00
* 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 ;
2019-10-24 10:33:20 +02:00
* ret_fd = r ;
return 0 ;
2018-03-26 16:34:54 +02:00
}
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
2020-01-28 21:56:10 +01:00
/* Simplify the root directory, so that it has no duplicate slashes and nothing at the
* end . While we won ' t resolve the root path we still simplify it . Note that dropping the
* trailing slash should not change behaviour , since when opening it we specify O_DIRECTORY
* anyway . Moreover at the end of this function after processing everything we ' ll always turn
* the empty string back to " / " . */
delete_trailing_chars ( root , " / " ) ;
path_simplify ( root , true ) ;
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 ;
2020-01-28 21:02:29 +01:00
fd = open ( root ? : " / " , O_CLOEXEC | O_DIRECTORY | O_PATH ) ;
2016-09-24 12:41:30 +02:00
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 ;
}
2020-01-28 21:02:29 +01:00
if ( root ) {
_cleanup_free_ char * absolute = NULL ;
const char * e ;
/* If we are operating on a root directory, let's take the root directory as it is. */
e = path_startswith ( buffer , root ) ;
if ( ! e )
return log_full_errno ( flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG ,
SYNTHETIC_ERRNO ( ECHRNG ) ,
" Specified path '%s' is outside of specified root directory '%s', refusing to resolve. " ,
path , root ) ;
done = strdup ( root ) ;
if ( ! done )
return - ENOMEM ;
/* Make sure "todo" starts with a slash */
absolute = strjoin ( " / " , e ) ;
if ( ! absolute )
return - ENOMEM ;
free_and_replace ( buffer , absolute ) ;
}
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 */
2020-01-28 21:56:10 +01:00
if ( n > 1 ) {
/* If we are looking at more than a single slash then skip all but one, so that when
* we are done with everything we have a normalized path with only single slashes
* separating the path components . */
todo + = n - 1 ;
n = 1 ;
}
2016-09-24 12:41:30 +02:00
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 )
2021-01-05 15:03:41 +01:00
if ( ! strextend ( & done , " / " ) )
2018-03-22 19:54:24 +01:00
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 ;
2018-11-29 11:21:12 +01:00
if ( unsafe_transition ( & previous_stat , & st ) )
2018-11-29 11:08:52 +01:00
return log_unsafe_transition ( fd , fd_parent , path , flags ) ;
2018-01-04 19:44:27 +01:00
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 ' ;
2021-01-05 15:03:41 +01:00
if ( ! strextend ( & done , first , todo ) )
2016-11-29 18:02:45 +01:00
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 ) & &
2018-11-29 11:21:12 +01:00
unsafe_transition ( & previous_stat , & st ) )
2018-11-29 11:08:52 +01:00
return log_unsafe_transition ( fd , child , path , flags ) ;
2018-01-04 19:44:27 +01:00
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 )
2018-11-30 15:43:13 +01:00
return log_autofs_mount_point ( child , path , flags ) ;
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 ) ;
2020-01-28 21:02:29 +01:00
fd = open ( root ? : " / " , O_CLOEXEC | O_DIRECTORY | O_PATH ) ;
2016-09-24 12:41:30 +02:00
if ( fd < 0 )
return - errno ;
2018-01-04 19:44:27 +01:00
if ( flags & CHASE_SAFE ) {
if ( fstat ( fd , & st ) < 0 )
return - errno ;
2018-11-29 11:21:12 +01:00
if ( unsafe_transition ( & previous_stat , & st ) )
2018-11-29 11:08:52 +01:00
return log_unsafe_transition ( child , fd , path , flags ) ;
2018-01-04 19:44:27 +01:00
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 . */
2019-06-24 16:59:38 +02:00
joined = path_join ( destination , todo ) ;
2017-10-04 17:34:03 +02:00
} else
2019-06-24 16:59:38 +02:00
joined = path_join ( " / " , 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 ' ;
2021-01-05 15:03:41 +01:00
if ( ! strextend ( & done , first ) )
2016-09-24 12:41:30 +02:00
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 ;
}
2019-10-24 10:33:20 +02:00
if ( ret_path )
* ret_path = TAKE_PTR ( done ) ;
2016-09-24 12:41:30 +02:00
2019-10-24 10:33:20 +02:00
if ( ret_fd ) {
/* 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 . */
2018-01-04 20:00:28 +01:00
assert ( fd > = 0 ) ;
2019-10-24 10:33:20 +02:00
* ret_fd = 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 :
2019-10-24 10:33:20 +02:00
if ( ret_path ) {
2018-04-04 17:03:45 +02:00
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
2019-10-24 10:33:20 +02:00
* ret_path = c ;
2018-04-04 17:03:45 +02:00
}
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 ;
}
2019-10-24 10:33:20 +02:00
r = chase_symlinks ( path , root , chase_flags , ret_path ? & p : NULL , & path_fd ) ;
if ( r < 0 )
return r ;
assert ( path_fd > = 0 ) ;
2018-03-26 14:15:43 +02:00
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 ;
2019-10-24 10:33:20 +02:00
int r ;
2018-03-26 14:15:43 +02:00
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 ;
}
2019-10-24 10:33:20 +02:00
r = chase_symlinks ( path , root , chase_flags , ret_path ? & p : NULL , & path_fd ) ;
if ( r < 0 )
return r ;
assert ( path_fd > = 0 ) ;
2018-03-26 14:15:43 +02:00
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 ,
2019-10-24 10:33:20 +02:00
struct stat * ret_stat ,
int * ret_fd ) {
2018-04-18 16:19:46 +02:00
_cleanup_close_ int path_fd = - 1 ;
_cleanup_free_ char * p = NULL ;
2019-10-24 10:33:20 +02:00
int r ;
2018-04-18 16:19:46 +02:00
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 ;
}
2019-10-24 10:33:20 +02:00
r = chase_symlinks ( path , root , chase_flags , ret_path ? & p : NULL , & path_fd ) ;
if ( r < 0 )
return r ;
assert ( path_fd > = 0 ) ;
2018-04-18 16:19:46 +02:00
if ( fstat ( path_fd , ret_stat ) < 0 )
return - errno ;
if ( ret_path )
* ret_path = TAKE_PTR ( p ) ;
2019-10-24 10:33:20 +02:00
if ( ret_fd )
* ret_fd = TAKE_FD ( path_fd ) ;
2018-04-18 16:19:46 +02:00
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
/* Like access() but operates on an already open fd */
xsprintf ( p , " /proc/self/fd/%i " , fd ) ;
2020-09-23 18:19:21 +02:00
if ( access ( p , mode ) < 0 ) {
if ( errno ! = ENOENT )
return - errno ;
2017-11-16 18:56:25 +01:00
2020-09-23 18:19:21 +02:00
/* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's
* make things debuggable and distinguish the two . */
if ( proc_mounted ( ) = = 0 )
return - ENOSYS ; /* /proc is not available or not set up properly, we're most likely in some chroot
* environment . */
return - EBADF ; /* The directory exists, hence it's the fd that doesn't. */
}
return 0 ;
2017-11-16 18:56:25 +01:00
}
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
}
2020-04-29 13:58:53 +02:00
int unlinkat_deallocate ( int fd , const char * name , UnlinkDeallocateFlags flags ) {
2018-02-09 09:50:31 +01:00
_cleanup_close_ int truncate_fd = - 1 ;
struct stat st ;
off_t l , bs ;
2020-04-29 13:58:53 +02:00
assert ( ( flags & ~ ( UNLINK_REMOVEDIR | UNLINK_ERASE ) ) = = 0 ) ;
2018-02-09 09:50:31 +01:00
/* 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 . */
2020-04-29 13:58:53 +02:00
if ( ! FLAGS_SET ( flags , UNLINK_REMOVEDIR ) ) {
2018-02-09 09:50:31 +01:00
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 ) ;
}
}
2020-04-29 13:58:53 +02:00
if ( unlinkat ( fd , name , FLAGS_SET ( flags , UNLINK_REMOVEDIR ) ? AT_REMOVEDIR : 0 ) < 0 )
2018-02-09 09:50:31 +01:00
return - errno ;
if ( truncate_fd < 0 ) /* Don't have a file handle, can't do more ☹️ */
return 0 ;
if ( fstat ( truncate_fd , & st ) < 0 ) {
2018-10-19 17:48:21 +02:00
log_debug_errno ( errno , " Failed to stat file '%s' for deallocation, ignoring: %m " , name ) ;
2018-02-09 09:50:31 +01:00
return 0 ;
}
2020-04-29 13:58:53 +02:00
if ( ! S_ISREG ( st . st_mode ) )
return 0 ;
if ( FLAGS_SET ( flags , UNLINK_ERASE ) & & st . st_size > 0 & & st . st_nlink = = 0 ) {
uint64_t left = st . st_size ;
char buffer [ 64 * 1024 ] ;
/* If erasing is requested, let's overwrite the file with random data once before deleting
* it . This isn ' t going to give you shred ( 1 ) semantics , but hopefully should be good enough
* for stuff backed by tmpfs at least .
*
2020-05-24 23:00:13 +02:00
* Note that we only erase like this if the link count of the file is zero . If it is higher it
2020-04-29 13:58:53 +02:00
* is still linked by someone else and we ' ll leave it to them to remove it securely
* eventually ! */
random_bytes ( buffer , sizeof ( buffer ) ) ;
while ( left > 0 ) {
ssize_t n ;
n = write ( truncate_fd , buffer , MIN ( sizeof ( buffer ) , left ) ) ;
if ( n < 0 ) {
log_debug_errno ( errno , " Failed to erase data in file '%s', ignoring. " , name ) ;
break ;
}
assert ( left > = ( size_t ) n ) ;
left - = n ;
}
/* Let's refresh metadata */
if ( fstat ( truncate_fd , & st ) < 0 ) {
log_debug_errno ( errno , " Failed to stat file '%s' for deallocation, ignoring: %m " , name ) ;
return 0 ;
}
}
/* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */
if ( st . st_blocks = = 0 | | st . st_nlink > 0 )
2018-02-09 09:50:31 +01:00
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 ,
2020-09-23 18:16:34 +02:00
r = = - ENOSYS ? " , 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
2020-09-23 18:16:34 +02:00
if ( r = = - ENOSYS )
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 /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
2019-07-22 13:48:12 +02:00
int fsync_full ( int fd ) {
int r , q ;
/* Sync both the file and the directory */
r = fsync ( fd ) < 0 ? - errno : 0 ;
q = fsync_directory_of_file ( fd ) ;
return r < 0 ? r : q ;
}
2018-06-25 17:24:09 +02:00
int fsync_path_at ( int at_fd , const char * path ) {
_cleanup_close_ int opened_fd = - 1 ;
int fd ;
if ( isempty ( path ) ) {
if ( at_fd = = AT_FDCWD ) {
opened_fd = open ( " . " , O_RDONLY | O_DIRECTORY | O_CLOEXEC ) ;
if ( opened_fd < 0 )
return - errno ;
fd = opened_fd ;
} else
fd = at_fd ;
} else {
opened_fd = openat ( at_fd , path , O_RDONLY | O_CLOEXEC ) ;
if ( opened_fd < 0 )
return - errno ;
fd = opened_fd ;
}
if ( fsync ( fd ) < 0 )
return - errno ;
return 0 ;
}
2019-01-23 16:08:55 +01:00
int syncfs_path ( int atfd , const char * path ) {
_cleanup_close_ int fd = - 1 ;
assert ( path ) ;
fd = openat ( atfd , path , O_CLOEXEC | O_RDONLY | O_NONBLOCK ) ;
if ( fd < 0 )
return - errno ;
if ( syncfs ( fd ) < 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 . */
2019-04-02 14:49:28 +02:00
if ( FLAGS_SET ( flags , O_PATH ) )
2018-07-20 11:57:24 +02:00
flags | = O_DIRECTORY ;
2019-04-02 14:49:28 +02:00
else if ( ! FLAGS_SET ( flags , O_TMPFILE ) )
2018-07-20 11:57:24 +02:00
flags | = O_DIRECTORY | O_RDONLY ;
fd = open ( parent , flags , mode ) ;
if ( fd < 0 )
return - errno ;
return fd ;
}
2020-05-01 19:37:24 +02:00
2020-05-07 15:34:50 +02:00
static int blockdev_is_encrypted ( const char * sysfs_path , unsigned depth_left ) {
_cleanup_free_ char * p = NULL , * uuids = NULL ;
_cleanup_closedir_ DIR * d = NULL ;
int r , found_encrypted = false ;
assert ( sysfs_path ) ;
if ( depth_left = = 0 )
return - EINVAL ;
p = path_join ( sysfs_path , " dm/uuid " ) ;
if ( ! p )
return - ENOMEM ;
r = read_one_line_file ( p , & uuids ) ;
if ( r ! = - ENOENT ) {
if ( r < 0 )
return r ;
/* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
if ( startswith ( uuids , " CRYPT- " ) )
return true ;
}
/* Not a dm-crypt device itself. But maybe it is on top of one? Follow the links in the "slaves/"
* subdir . */
p = mfree ( p ) ;
p = path_join ( sysfs_path , " slaves " ) ;
if ( ! p )
return - ENOMEM ;
d = opendir ( p ) ;
if ( ! d ) {
2020-06-23 08:31:16 +02:00
if ( errno = = ENOENT ) /* Doesn't have underlying devices */
2020-05-07 15:34:50 +02:00
return false ;
return - errno ;
}
for ( ; ; ) {
_cleanup_free_ char * q = NULL ;
struct dirent * de ;
errno = 0 ;
de = readdir_no_dot ( d ) ;
if ( ! de ) {
if ( errno ! = 0 )
return - errno ;
2020-06-23 08:31:16 +02:00
break ; /* No more underlying devices */
2020-05-07 15:34:50 +02:00
}
q = path_join ( p , de - > d_name ) ;
if ( ! q )
return - ENOMEM ;
r = blockdev_is_encrypted ( q , depth_left - 1 ) ;
if ( r < 0 )
return r ;
if ( r = = 0 ) /* we found one that is not encrypted? then propagate that immediately */
return false ;
found_encrypted = true ;
}
return found_encrypted ;
}
2020-05-01 19:37:24 +02:00
int path_is_encrypted ( const char * path ) {
2020-05-07 15:34:50 +02:00
char p [ SYS_BLOCK_PATH_MAX ( NULL ) ] ;
2020-05-01 19:37:24 +02:00
dev_t devt ;
int r ;
r = get_block_device ( path , & devt ) ;
if ( r < 0 )
return r ;
if ( r = = 0 ) /* doesn't have a block device */
return false ;
2020-05-07 15:34:50 +02:00
xsprintf_sys_block_path ( p , NULL , devt ) ;
2020-05-01 19:37:24 +02:00
2020-05-07 15:34:50 +02:00
return blockdev_is_encrypted ( p , 10 /* safety net: maximum recursion depth */ ) ;
2020-05-01 19:37:24 +02:00
}
2020-11-18 15:11:43 +01:00
int conservative_rename (
int olddirfd , const char * oldpath ,
int newdirfd , const char * newpath ) {
_cleanup_close_ int old_fd = - 1 , new_fd = - 1 ;
struct stat old_stat , new_stat ;
/* Renames the old path to thew new path, much like renameat() — except if both are regular files and
* have the exact same contents and basic file attributes already . In that case remove the new file
* instead . This call is useful for reducing inotify wakeups on files that are updated but don ' t
* actually change . This function is written in a style that we rather rename too often than suppress
* too much . i . e . whenever we are in doubt we rather rename than fail . After all reducing inotify
* events is an optimization only , not more . */
old_fd = openat ( olddirfd , oldpath , O_CLOEXEC | O_RDONLY | O_NOCTTY | O_NOFOLLOW ) ;
if ( old_fd < 0 )
goto do_rename ;
new_fd = openat ( newdirfd , newpath , O_CLOEXEC | O_RDONLY | O_NOCTTY | O_NOFOLLOW ) ;
if ( new_fd < 0 )
goto do_rename ;
if ( fstat ( old_fd , & old_stat ) < 0 )
goto do_rename ;
if ( ! S_ISREG ( old_stat . st_mode ) )
goto do_rename ;
if ( fstat ( new_fd , & new_stat ) < 0 )
goto do_rename ;
if ( new_stat . st_ino = = old_stat . st_ino & &
new_stat . st_dev = = old_stat . st_dev )
goto is_same ;
if ( old_stat . st_mode ! = new_stat . st_mode | |
old_stat . st_size ! = new_stat . st_size | |
old_stat . st_uid ! = new_stat . st_uid | |
old_stat . st_gid ! = new_stat . st_gid )
goto do_rename ;
for ( ; ; ) {
char buf1 [ 16 * 1024 ] ;
char buf2 [ sizeof ( buf1 ) + 1 ] ;
ssize_t l1 , l2 ;
l1 = read ( old_fd , buf1 , sizeof ( buf1 ) ) ;
if ( l1 < 0 )
goto do_rename ;
l2 = read ( new_fd , buf2 , l1 + 1 ) ;
if ( l1 ! = l2 )
goto do_rename ;
if ( l1 = = 0 ) /* EOF on both! And everything's the same so far, yay! */
break ;
if ( memcmp ( buf1 , buf2 , l1 ) ! = 0 )
goto do_rename ;
}
is_same :
/* Everything matches? Then don't rename, instead remove the source file, and leave the existing
* destination in place */
if ( unlinkat ( olddirfd , oldpath , 0 ) < 0 )
goto do_rename ;
return 0 ;
do_rename :
if ( renameat ( olddirfd , oldpath , newdirfd , newpath ) < 0 )
return - errno ;
return 1 ;
}