2019-11-12 01:27:17 +00:00
// SPDX-License-Identifier: GPL-2.0
# define _GNU_SOURCE
# include <errno.h>
# include <fcntl.h>
# include <sched.h>
# include <stdio.h>
# include <stdbool.h>
# include <sys/stat.h>
# include <sys/syscall.h>
# include <sys/types.h>
# include <time.h>
# include <unistd.h>
# include <string.h>
# include "log.h"
# include "timens.h"
/*
* Test shouldn ' t be run for a day , so add 10 days to child
* time and check parent ' s time to be in the same day .
*/
# define DAY_IN_SEC (60*60*24)
# define TEN_DAYS_IN_SEC (10*DAY_IN_SEC)
# define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
struct test_clock {
clockid_t id ;
char * name ;
/*
* off_id is - 1 if a clock has own offset , or it contains an index
* which contains a right offset of this clock .
*/
int off_id ;
time_t offset ;
} ;
# define ct(clock, off_id) { clock, #clock, off_id }
static struct test_clock clocks [ ] = {
ct ( CLOCK_BOOTTIME , - 1 ) ,
ct ( CLOCK_BOOTTIME_ALARM , 1 ) ,
ct ( CLOCK_MONOTONIC , - 1 ) ,
ct ( CLOCK_MONOTONIC_COARSE , 1 ) ,
ct ( CLOCK_MONOTONIC_RAW , 1 ) ,
} ;
# undef ct
static int child_ns , parent_ns = - 1 ;
static int switch_ns ( int fd )
{
if ( setns ( fd , CLONE_NEWTIME ) ) {
pr_perror ( " setns() " ) ;
return - 1 ;
}
return 0 ;
}
static int init_namespaces ( void )
{
char path [ ] = " /proc/self/ns/time_for_children " ;
struct stat st1 , st2 ;
if ( parent_ns = = - 1 ) {
parent_ns = open ( path , O_RDONLY ) ;
if ( parent_ns < = 0 )
return pr_perror ( " Unable to open %s " , path ) ;
}
if ( fstat ( parent_ns , & st1 ) )
return pr_perror ( " Unable to stat the parent timens " ) ;
if ( unshare_timens ( ) )
return - 1 ;
child_ns = open ( path , O_RDONLY ) ;
if ( child_ns < = 0 )
return pr_perror ( " Unable to open %s " , path ) ;
if ( fstat ( child_ns , & st2 ) )
return pr_perror ( " Unable to stat the timens " ) ;
if ( st1 . st_ino = = st2 . st_ino )
return pr_perror ( " The same child_ns after CLONE_NEWTIME " ) ;
return 0 ;
}
static int test_gettime ( clockid_t clock_index , bool raw_syscall , time_t offset )
{
struct timespec child_ts_new , parent_ts_old , cur_ts ;
char * entry = raw_syscall ? " syscall " : " vdso " ;
double precision = 0.0 ;
if ( check_skip ( clocks [ clock_index ] . id ) )
return 0 ;
switch ( clocks [ clock_index ] . id ) {
case CLOCK_MONOTONIC_COARSE :
case CLOCK_MONOTONIC_RAW :
precision = - 2.0 ;
break ;
}
if ( switch_ns ( parent_ns ) )
return pr_err ( " switch_ns(%d) " , child_ns) ;
if ( _gettime ( clocks [ clock_index ] . id , & parent_ts_old , raw_syscall ) )
return - 1 ;
child_ts_new . tv_nsec = parent_ts_old . tv_nsec ;
child_ts_new . tv_sec = parent_ts_old . tv_sec + offset ;
if ( switch_ns ( child_ns ) )
return pr_err ( " switch_ns(%d) " , child_ns) ;
if ( _gettime ( clocks [ clock_index ] . id , & cur_ts , raw_syscall ) )
return - 1 ;
if ( difftime ( cur_ts . tv_sec , child_ts_new . tv_sec ) < precision ) {
ksft_test_result_fail (
" Child's %s (%s) time has not changed: %lu -> %lu [%lu] \n " ,
clocks [ clock_index ] . name , entry , parent_ts_old . tv_sec ,
child_ts_new . tv_sec , cur_ts . tv_sec ) ;
return - 1 ;
}
if ( switch_ns ( parent_ns ) )
return pr_err ( " switch_ns(%d) " , parent_ns) ;
if ( _gettime ( clocks [ clock_index ] . id , & cur_ts , raw_syscall ) )
return - 1 ;
if ( difftime ( cur_ts . tv_sec , parent_ts_old . tv_sec ) > DAY_IN_SEC ) {
ksft_test_result_fail (
" Parent's %s (%s) time has changed: %lu -> %lu [%lu] \n " ,
clocks [ clock_index ] . name , entry , parent_ts_old . tv_sec ,
child_ts_new . tv_sec , cur_ts . tv_sec ) ;
/* Let's play nice and put it closer to original */
clock_settime ( clocks [ clock_index ] . id , & cur_ts ) ;
return - 1 ;
}
ksft_test_result_pass ( " Passed for %s (%s) \n " ,
clocks [ clock_index ] . name , entry ) ;
return 0 ;
}
int main ( int argc , char * argv [ ] )
{
unsigned int i ;
time_t offset ;
int ret = 0 ;
nscheck ( ) ;
2020-05-21 00:52:52 -07:00
check_supported_timers ( ) ;
2019-11-12 01:27:17 +00:00
ksft_set_plan ( ARRAY_SIZE ( clocks ) * 2 ) ;
if ( init_namespaces ( ) )
return 1 ;
/* Offsets have to be set before tasks enter the namespace. */
for ( i = 0 ; i < ARRAY_SIZE ( clocks ) ; i + + ) {
if ( clocks [ i ] . off_id ! = - 1 )
continue ;
offset = TEN_DAYS_IN_SEC + i * 1000 ;
clocks [ i ] . offset = offset ;
if ( _settime ( clocks [ i ] . id , offset ) )
return 1 ;
}
for ( i = 0 ; i < ARRAY_SIZE ( clocks ) ; i + + ) {
if ( clocks [ i ] . off_id ! = - 1 )
offset = clocks [ clocks [ i ] . off_id ] . offset ;
else
offset = clocks [ i ] . offset ;
ret | = test_gettime ( i , true , offset ) ;
ret | = test_gettime ( i , false , offset ) ;
}
if ( ret )
ksft_exit_fail ( ) ;
ksft_exit_pass ( ) ;
return ! ! ret ;
}