2015-10-26 21:16:26 +01:00
/***
This file is part of systemd .
Copyright 2010 Lennart Poettering
systemd is free software ; you can redistribute it and / or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
Lesser General Public License for more details .
You should have received a copy of the GNU Lesser General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
2015-11-30 21:43:37 +01:00
# include <dirent.h>
# include <errno.h>
# include <stddef.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
# 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"
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 ) {
struct stat buf ;
int ret ;
ret = renameat2 ( olddirfd , oldpath , newdirfd , newpath , RENAME_NOREPLACE ) ;
if ( ret > = 0 )
return 0 ;
/* renameat2() exists since Linux 3.15, btrfs added support for it later.
* If it is not implemented , fallback to another method . */
if ( ! IN_SET ( errno , EINVAL , ENOSYS ) )
return - errno ;
/* The link()/unlink() fallback does not work on directories. But
* renameat ( ) without RENAME_NOREPLACE gives the same semantics on
* directories , except when newpath is an * empty * directory . This is
* good enough . */
ret = fstatat ( olddirfd , oldpath , & buf , AT_SYMLINK_NOFOLLOW ) ;
if ( ret > = 0 & & S_ISDIR ( buf . st_mode ) ) {
ret = renameat ( olddirfd , oldpath , newdirfd , newpath ) ;
return ret > = 0 ? 0 : - errno ;
}
/* If it is not a directory, use the link()/unlink() fallback. */
ret = linkat ( olddirfd , oldpath , newdirfd , newpath , 0 ) ;
if ( ret < 0 )
return - errno ;
ret = unlinkat ( olddirfd , oldpath , 0 ) ;
if ( ret < 0 ) {
/* backup errno before the following unlinkat() alters it */
ret = errno ;
( void ) unlinkat ( newdirfd , newpath , 0 ) ;
errno = ret ;
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 readlink_and_canonicalize ( const char * p , char * * r ) {
char * t , * s ;
int j ;
assert ( p ) ;
assert ( r ) ;
j = readlink_and_make_absolute ( p , & t ) ;
if ( j < 0 )
return j ;
s = canonicalize_file_name ( t ) ;
if ( s ) {
free ( t ) ;
* r = s ;
} else
* r = t ;
path_kill_slashes ( * r ) ;
return 0 ;
}
install: follow unit file symlinks in /usr, but not /etc when looking for [Install] data
Some distributions use alias unit files via symlinks in /usr to cover
for legacy service names. With this change we'll allow "systemctl
enable" on such aliases.
Previously, our rule was that symlinks are user configuration that
"systemctl enable" + "systemctl disable" creates and removes, while unit
files is where the instructions to do so are store. As a result of the
rule we'd never read install information through symlinks, since that
would mix enablement state with installation instructions.
Now, the new rule is that only symlinks inside of /etc are
configuration. Unit files, and symlinks in /usr are now valid for
installation instructions.
This patch is quite a rework of the whole install logic, and makes the
following addional changes:
- Adds a complete test "test-instal-root" that tests the install logic
pretty comprehensively.
- Never uses canonicalize_file_name(), because that's incompatible with
operation relative to a specific root directory.
- unit_file_get_state() is reworked to return a proper error, and
returns the state in a call-by-ref parameter. This cleans up confusion
between the enum type and errno-like errors.
- The new logic puts a limit on how long to follow unit file symlinks:
it will do so only for 64 steps at max.
- The InstallContext object's fields are renamed to will_process and
has_processed (will_install and has_installed) since they are also
used for deinstallation and all kinds of other operations.
- The root directory is always verified before use.
- install.c is reordered to place the exported functions together.
- Stricter rules are followed when traversing symlinks: the unit suffix
must say identical, and it's not allowed to link between regular units
and templated units.
- Various modernizations
- The "invalid" unit file state has been renamed to "bad", in order to
avoid confusion between UNIT_FILE_INVALID and
_UNIT_FILE_STATE_INVALID. Given that the state should normally not be
seen and is not documented this should not be a problematic change.
The new name is now documented however.
Fixes #1375, #1718, #1706
2015-10-08 22:31:56 +02:00
int readlink_and_make_absolute_root ( const char * root , const char * path , char * * ret ) {
_cleanup_free_ char * target = NULL , * t = NULL ;
const char * full ;
int r ;
full = prefix_roota ( root , path ) ;
r = readlink_malloc ( full , & target ) ;
if ( r < 0 )
return r ;
t = file_in_same_dir ( path , target ) ;
if ( ! t )
return - ENOMEM ;
* ret = t ;
t = NULL ;
return 0 ;
}
2015-10-26 21:16:26 +01:00
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 ;
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 ;
}
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 ) ;
if ( getpid ( ) = = 1 & & ( st . st_mode & 0044 ) ! = 0044 )
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 ) {
_cleanup_close_ int fd ;
int r ;
assert ( path ) ;
if ( parents )
mkdir_parents ( path , 0755 ) ;
2016-01-29 22:36:08 +01:00
fd = open ( path , O_WRONLY | O_CREAT | O_CLOEXEC | O_NOCTTY ,
( mode = = 0 | | mode = = MODE_INVALID ) ? 0644 : mode ) ;
2015-10-26 21:16:26 +01:00
if ( fd < 0 )
return - errno ;
2015-11-11 22:54:56 +01:00
if ( mode ! = MODE_INVALID ) {
2015-10-26 21:16:26 +01:00
r = fchmod ( fd , mode ) ;
if ( r < 0 )
return - errno ;
}
if ( uid ! = UID_INVALID | | gid ! = GID_INVALID ) {
r = fchown ( fd , uid , gid ) ;
if ( r < 0 )
return - errno ;
}
if ( stamp ! = USEC_INFINITY ) {
struct timespec ts [ 2 ] ;
timespec_store ( & ts [ 0 ] , stamp ) ;
ts [ 1 ] = ts [ 0 ] ;
r = futimens ( fd , ts ) ;
} else
r = futimens ( fd , NULL ) ;
if ( r < 0 )
return - errno ;
return 0 ;
}
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
}
int symlink_idempotent ( const char * from , const char * to ) {
_cleanup_free_ char * p = NULL ;
int r ;
assert ( from ) ;
assert ( to ) ;
if ( symlink ( from , to ) < 0 ) {
if ( errno ! = EEXIST )
return - errno ;
r = readlink_malloc ( to , & p ) ;
if ( r < 0 )
return r ;
if ( ! streq ( p , from ) )
return - EINVAL ;
}
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 ) {
unlink_noerrno ( t ) ;
return - errno ;
}
return 0 ;
}
int get_files_in_directory ( const char * path , char * * * list ) {
_cleanup_closedir_ DIR * d = NULL ;
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 ;
for ( ; ; ) {
struct dirent * de ;
errno = 0 ;
de = readdir ( d ) ;
2016-01-11 20:31:14 +01:00
if ( ! de & & errno > 0 )
2015-10-26 21:16:26 +01:00
return - errno ;
if ( ! de )
break ;
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 + + ;
}
if ( list ) {
* list = l ;
l = NULL ; /* avoid freeing */
}
return n ;
}
2016-04-25 00:18:27 +02:00
int inotify_add_watch_fd ( int fd , int what , uint32_t mask ) {
char path [ strlen ( " /proc/self/fd/ " ) + DECIMAL_STR_MAX ( int ) + 1 ] ;
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 ;
}