2010-03-31 18:29:55 +04:00
/*-*- Mode: C; c-basic-offset: 8 -*-*/
/***
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 General Public License as published by
the Free Software Foundation ; either version 2 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
General Public License for more details .
You should have received a copy of the GNU General Public License
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include <errno.h>
# include <assert.h>
# include <unistd.h>
# include <sys/types.h>
# include <signal.h>
# include <sys/mount.h>
# include "cgroup.h"
# include "log.h"
static int translate_error ( int error , int _errno ) {
switch ( error ) {
case ECGROUPNOTCOMPILED :
case ECGROUPNOTMOUNTED :
case ECGROUPNOTEXIST :
case ECGROUPNOTCREATED :
return - ENOENT ;
case ECGINVAL :
return - EINVAL ;
case ECGROUPNOTALLOWED :
return - EPERM ;
case ECGOTHER :
return - _errno ;
}
return - EIO ;
}
int cgroup_bonding_realize ( CGroupBonding * b ) {
int r ;
assert ( b ) ;
assert ( b - > path ) ;
assert ( b - > controller ) ;
if ( b - > cgroup )
return 0 ;
if ( ! ( b - > cgroup = cgroup_new_cgroup ( b - > path ) ) )
return - ENOMEM ;
if ( ! cgroup_add_controller ( b - > cgroup , b - > controller ) ) {
r = - ENOMEM ;
goto fail ;
}
2010-06-16 07:07:30 +04:00
if ( ( r = cgroup_create_cgroup ( b - > cgroup , true ) ) ! = 0 ) {
2010-03-31 18:29:55 +04:00
r = translate_error ( r , errno ) ;
goto fail ;
}
return 0 ;
fail :
cgroup_free ( & b - > cgroup ) ;
b - > cgroup = NULL ;
return r ;
}
int cgroup_bonding_realize_list ( CGroupBonding * first ) {
CGroupBonding * b ;
LIST_FOREACH ( by_unit , b , first ) {
int r ;
if ( ( r = cgroup_bonding_realize ( b ) ) < 0 )
return r ;
}
return 0 ;
}
void cgroup_bonding_free ( CGroupBonding * b ) {
assert ( b ) ;
if ( b - > unit ) {
CGroupBonding * f ;
LIST_REMOVE ( CGroupBonding , by_unit , b - > unit - > meta . cgroup_bondings , b ) ;
assert_se ( f = hashmap_get ( b - > unit - > meta . manager - > cgroup_bondings , b - > path ) ) ;
LIST_REMOVE ( CGroupBonding , by_path , f , b ) ;
if ( f )
hashmap_replace ( b - > unit - > meta . manager - > cgroup_bondings , b - > path , f ) ;
else
hashmap_remove ( b - > unit - > meta . manager - > cgroup_bondings , b - > path ) ;
}
if ( b - > cgroup ) {
2010-04-21 05:15:58 +04:00
if ( b - > only_us & & b - > clean_up & & cgroup_bonding_is_empty ( b ) > 0 )
cgroup_delete_cgroup_ext ( b - > cgroup , true ) ;
2010-03-31 18:29:55 +04:00
cgroup_free ( & b - > cgroup ) ;
}
2010-04-21 05:15:58 +04:00
free ( b - > controller ) ;
free ( b - > path ) ;
2010-03-31 18:29:55 +04:00
free ( b ) ;
}
void cgroup_bonding_free_list ( CGroupBonding * first ) {
CGroupBonding * b , * n ;
LIST_FOREACH_SAFE ( by_unit , b , n , first )
cgroup_bonding_free ( b ) ;
}
int cgroup_bonding_install ( CGroupBonding * b , pid_t pid ) {
int r ;
assert ( b ) ;
assert ( pid > = 0 ) ;
if ( pid = = 0 )
pid = getpid ( ) ;
if ( ! b - > cgroup )
return - ENOENT ;
if ( ( r = cgroup_attach_task_pid ( b - > cgroup , pid ) ) )
return translate_error ( r , errno ) ;
return 0 ;
}
int cgroup_bonding_install_list ( CGroupBonding * first , pid_t pid ) {
CGroupBonding * b ;
LIST_FOREACH ( by_unit , b , first ) {
int r ;
if ( ( r = cgroup_bonding_install ( b , pid ) ) < 0 )
return r ;
}
return 0 ;
}
int cgroup_bonding_kill ( CGroupBonding * b , int sig ) {
int r ;
Set * s ;
bool done ;
2010-04-08 02:52:14 +04:00
bool killed = false ;
2010-03-31 18:29:55 +04:00
assert ( b ) ;
assert ( sig > 0 ) ;
2010-04-08 02:52:14 +04:00
if ( ! b - > only_us )
return - EAGAIN ;
2010-03-31 18:29:55 +04:00
if ( ! ( s = set_new ( trivial_hash_func , trivial_compare_func ) ) )
return - ENOMEM ;
do {
void * iterator ;
pid_t pid ;
done = true ;
if ( ( r = cgroup_get_task_begin ( b - > path , b - > controller , & iterator , & pid ) ) ! = 0 ) {
if ( r = = ECGEOF ) {
r = 0 ;
goto kill_done ;
} else {
2010-05-20 22:45:00 +04:00
if ( r = = ECGOTHER & & errno = = ENOENT )
r = ESRCH ;
else
r = translate_error ( r , errno ) ;
2010-03-31 18:29:55 +04:00
break ;
}
}
for ( ; ; ) {
if ( set_get ( s , INT_TO_PTR ( pid ) ) ! = INT_TO_PTR ( pid ) ) {
/* If we haven't killed this process
* yet , kill it */
if ( kill ( pid , sig ) < 0 & & errno ! = ESRCH ) {
r = - errno ;
break ;
}
2010-04-08 02:52:14 +04:00
killed = true ;
2010-03-31 18:29:55 +04:00
done = false ;
if ( ( r = set_put ( s , INT_TO_PTR ( pid ) ) ) < 0 )
break ;
}
if ( ( r = cgroup_get_task_next ( & iterator , & pid ) ) ! = 0 ) {
if ( r = = ECGEOF )
r = 0 ;
else
r = translate_error ( r , errno ) ;
break ;
}
}
kill_done :
assert_se ( cgroup_get_task_end ( & iterator ) = = 0 ) ;
/* To avoid racing against processes which fork
* quicker than we can kill them we repeat this until
* no new pids need to be killed . */
} while ( ! done & & r > = 0 ) ;
set_free ( s ) ;
2010-04-08 02:52:14 +04:00
if ( r < 0 )
return r ;
return killed ? 0 : - ESRCH ;
2010-03-31 18:29:55 +04:00
}
int cgroup_bonding_kill_list ( CGroupBonding * first , int sig ) {
CGroupBonding * b ;
2010-04-08 02:52:14 +04:00
int r = - EAGAIN ;
2010-03-31 18:29:55 +04:00
LIST_FOREACH ( by_unit , b , first ) {
2010-04-08 02:52:14 +04:00
if ( ( r = cgroup_bonding_kill ( b , sig ) ) < 0 ) {
if ( r = = - EAGAIN | | - ESRCH )
continue ;
2010-03-31 18:29:55 +04:00
return r ;
2010-04-08 02:52:14 +04:00
}
return 0 ;
2010-03-31 18:29:55 +04:00
}
2010-04-08 02:52:14 +04:00
return r ;
2010-03-31 18:29:55 +04:00
}
/* Returns 1 if the group is empty, 0 if it is not, -EAGAIN if we
* cannot know */
int cgroup_bonding_is_empty ( CGroupBonding * b ) {
void * iterator ;
pid_t pid ;
int r ;
assert ( b ) ;
r = cgroup_get_task_begin ( b - > path , b - > controller , & iterator , & pid ) ;
if ( r = = 0 | | r = = ECGEOF )
cgroup_get_task_end ( & iterator ) ;
/* Hmm, no PID in this group? Then it is definitely empty */
if ( r = = ECGEOF )
return 1 ;
/* Some error? Let's return it */
if ( r ! = 0 )
return translate_error ( r , errno ) ;
/* It's not empty, and we are the only user, then it is
* definitely not empty */
if ( b - > only_us )
return 0 ;
/* There are PIDs in the group but we aren't the only users,
* hence we cannot say */
return - EAGAIN ;
}
int cgroup_bonding_is_empty_list ( CGroupBonding * first ) {
CGroupBonding * b ;
LIST_FOREACH ( by_unit , b , first ) {
int r ;
if ( ( r = cgroup_bonding_is_empty ( b ) ) < 0 ) {
/* If this returned -EAGAIN, then we don't know if the
* group is empty , so let ' s see if another group can
* tell us */
if ( r ! = - EAGAIN )
return r ;
} else
return r ;
}
return - EAGAIN ;
}
static int install_release_agent ( Manager * m , const char * mount_point ) {
char * p , * c , * sc ;
int r ;
assert ( m ) ;
assert ( mount_point ) ;
if ( asprintf ( & p , " %s/release_agent " , mount_point ) < 0 )
return - ENOMEM ;
if ( ( r = read_one_line_file ( p , & c ) ) < 0 ) {
free ( p ) ;
return r ;
}
sc = strstrip ( c ) ;
if ( sc [ 0 ] = = 0 ) {
if ( ( r = write_one_line_file ( p , CGROUP_AGENT_PATH " \n " ) ) < 0 ) {
free ( p ) ;
free ( c ) ;
return r ;
}
} else if ( ! streq ( sc , CGROUP_AGENT_PATH ) ) {
free ( p ) ;
free ( c ) ;
return - EEXIST ;
}
free ( c ) ;
free ( p ) ;
if ( asprintf ( & p , " %s/notify_on_release " , mount_point ) < 0 )
return - ENOMEM ;
if ( ( r = read_one_line_file ( p , & c ) ) < 0 ) {
free ( p ) ;
return r ;
}
sc = strstrip ( c ) ;
if ( streq ( sc , " 0 " ) ) {
if ( ( r = write_one_line_file ( p , " 1 \n " ) ) < 0 ) {
free ( p ) ;
free ( c ) ;
return r ;
}
} else if ( ! streq ( sc , " 1 " ) ) {
free ( p ) ;
free ( c ) ;
return - EIO ;
}
2010-04-06 04:35:59 +04:00
free ( p ) ;
free ( c ) ;
2010-03-31 18:29:55 +04:00
return 0 ;
}
static int create_hierarchy_cgroup ( Manager * m ) {
struct cgroup * cg ;
int r ;
assert ( m ) ;
if ( ! ( cg = cgroup_new_cgroup ( m - > cgroup_hierarchy ) ) )
return - ENOMEM ;
if ( ! ( cgroup_add_controller ( cg , m - > cgroup_controller ) ) ) {
r = - ENOMEM ;
goto finish ;
}
if ( ( r = cgroup_create_cgroup ( cg , true ) ) ! = 0 ) {
log_error ( " Failed to create cgroup hierarchy group: %s " , cgroup_strerror ( r ) ) ;
r = translate_error ( r , errno ) ;
goto finish ;
}
if ( ( r = cgroup_attach_task ( cg ) ) ! = 0 ) {
log_error ( " Failed to add ourselves to hierarchy group: %s " , cgroup_strerror ( r ) ) ;
r = translate_error ( r , errno ) ;
goto finish ;
}
r = 0 ;
finish :
cgroup_free ( & cg ) ;
return r ;
}
int manager_setup_cgroup ( Manager * m ) {
char * mp , * cp ;
int r ;
pid_t pid ;
2010-04-21 05:18:05 +04:00
char suffix [ 32 ] ;
2010-03-31 18:29:55 +04:00
assert ( m ) ;
if ( ( r = cgroup_init ( ) ) ! = 0 ) {
log_error ( " Failed to initialize libcg: %s " , cgroup_strerror ( r ) ) ;
return translate_error ( r , errno ) ;
}
free ( m - > cgroup_controller ) ;
2010-05-18 01:54:44 +04:00
if ( ! ( m - > cgroup_controller = strdup ( " name=systemd " ) ) )
2010-03-31 18:29:55 +04:00
return - ENOMEM ;
if ( ( r = cgroup_get_subsys_mount_point ( m - > cgroup_controller , & mp ) ) )
return translate_error ( r , errno ) ;
pid = getpid ( ) ;
if ( ( r = cgroup_get_current_controller_path ( pid , m - > cgroup_controller , & cp ) ) ) {
free ( mp ) ;
return translate_error ( r , errno ) ;
}
2010-04-21 05:18:05 +04:00
snprintf ( suffix , sizeof ( suffix ) , " /systemd-%u " , ( unsigned ) pid ) ;
char_array_0 ( suffix ) ;
2010-03-31 18:29:55 +04:00
free ( m - > cgroup_hierarchy ) ;
2010-04-21 05:18:05 +04:00
if ( endswith ( cp , suffix ) )
/* We probably got reexecuted and can continue to use our root cgroup */
m - > cgroup_hierarchy = cp ;
else {
/* We need a new root cgroup */
m - > cgroup_hierarchy = NULL ;
r = asprintf ( & m - > cgroup_hierarchy , " %s%s " , streq ( cp , " / " ) ? " " : cp , suffix ) ;
2010-03-31 18:29:55 +04:00
free ( cp ) ;
2010-04-21 05:18:05 +04:00
if ( r < 0 ) {
free ( mp ) ;
return - ENOMEM ;
}
2010-03-31 18:29:55 +04:00
}
2010-05-14 04:50:32 +04:00
log_debug ( " Using cgroup controller <%s>, hierarchy mounted at <%s>, using root group <%s>. " ,
m - > cgroup_controller ,
mp ,
m - > cgroup_hierarchy ) ;
2010-03-31 18:29:55 +04:00
if ( ( r = install_release_agent ( m , mp ) ) < 0 )
log_warning ( " Failed to install release agent, ignoring: %s " , strerror ( - r ) ) ;
else
2010-05-14 04:50:32 +04:00
log_debug ( " Installed release agent, or already installed. " ) ;
2010-03-31 18:29:55 +04:00
free ( mp ) ;
if ( ( r = create_hierarchy_cgroup ( m ) ) < 0 )
log_error ( " Failed to create root cgroup hierarchy: %s " , strerror ( - r ) ) ;
else
2010-05-14 04:50:32 +04:00
log_debug ( " Created root group. " ) ;
2010-03-31 18:29:55 +04:00
return r ;
}
2010-04-21 05:27:44 +04:00
int manager_shutdown_cgroup ( Manager * m , bool delete ) {
2010-03-31 18:29:55 +04:00
struct cgroup * cg ;
int r ;
assert ( m ) ;
if ( ! m - > cgroup_hierarchy )
return 0 ;
if ( ! ( cg = cgroup_new_cgroup ( m - > cgroup_hierarchy ) ) )
return - ENOMEM ;
if ( ! ( cgroup_add_controller ( cg , m - > cgroup_controller ) ) ) {
r = - ENOMEM ;
goto finish ;
}
2010-04-21 05:27:44 +04:00
/* Often enough we won't be able to delete the cgroup we
* ourselves are in , hence ignore all errors here */
if ( delete )
cgroup_delete_cgroup_ext ( cg , CGFLAG_DELETE_IGNORE_MIGRATION | CGFLAG_DELETE_RECURSIVE ) ;
2010-03-31 18:29:55 +04:00
r = 0 ;
finish :
cgroup_free ( & cg ) ;
return r ;
}
int cgroup_notify_empty ( Manager * m , const char * group ) {
CGroupBonding * l , * b ;
assert ( m ) ;
assert ( group ) ;
if ( ! ( l = hashmap_get ( m - > cgroup_bondings , group ) ) )
return 0 ;
LIST_FOREACH ( by_path , b , l ) {
int t ;
if ( ! b - > unit )
continue ;
if ( ( t = cgroup_bonding_is_empty_list ( b ) ) < 0 ) {
/* If we don't know, we don't know */
if ( t ! = - EAGAIN )
log_warning ( " Failed to check whether cgroup is empty: %s " , strerror ( errno ) ) ;
continue ;
}
if ( t > 0 )
if ( UNIT_VTABLE ( b - > unit ) - > cgroup_notify_empty )
UNIT_VTABLE ( b - > unit ) - > cgroup_notify_empty ( b - > unit ) ;
}
return 0 ;
}
CGroupBonding * cgroup_bonding_find_list ( CGroupBonding * first , const char * controller ) {
CGroupBonding * b ;
assert ( controller ) ;
LIST_FOREACH ( by_unit , b , first )
if ( streq ( b - > controller , controller ) )
return b ;
return NULL ;
}
2010-04-18 05:04:54 +04:00
char * cgroup_bonding_to_string ( CGroupBonding * b ) {
char * r ;
assert ( b ) ;
if ( asprintf ( & r , " %s:%s " , b - > controller , b - > path ) < 0 )
return NULL ;
return r ;
}