2014-02-06 15:34:21 +01:00
/*
* Copyright ( C ) 2014 The Android Open Source Project
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
# include <gtest/gtest.h>
# include <dlfcn.h>
2014-02-27 14:18:00 +01:00
# include <errno.h>
# include <fcntl.h>
# include <stdio.h>
# include <string.h>
# include <unistd.h>
2014-02-06 15:34:21 +01:00
# include <android/dlext.h>
# include <sys/mman.h>
2014-05-02 15:57:42 +02:00
# include <sys/types.h>
2014-02-27 14:18:00 +01:00
# include <sys/wait.h>
2014-02-06 15:34:21 +01:00
2014-05-02 15:57:42 +02:00
# include <pagemap/pagemap.h>
2014-02-06 15:34:21 +01:00
# define ASSERT_DL_NOTNULL(ptr) \
2014-10-04 02:52:44 +02:00
ASSERT_TRUE ( ptr ! = nullptr ) < < " dlerror: " < < dlerror ( )
2014-02-06 15:34:21 +01:00
# define ASSERT_DL_ZERO(i) \
ASSERT_EQ ( 0 , i ) < < " dlerror: " < < dlerror ( )
2014-02-27 14:18:00 +01:00
# define ASSERT_NOERROR(i) \
ASSERT_NE ( - 1 , i ) < < " errno: " < < strerror ( errno )
2014-02-06 15:34:21 +01:00
typedef int ( * fn ) ( void ) ;
# define LIBNAME "libdlext_test.so"
2014-04-30 16:48:40 +02:00
# define LIBNAME_NORELRO "libdlext_test_norelro.so"
2014-02-06 15:34:21 +01:00
# define LIBSIZE 1024*1024 // how much address space to reserve for it
2014-07-01 23:10:16 +02:00
# if defined(__LP64__)
2014-10-04 02:52:44 +02:00
# define LIBPATH_PREFIX "%s / nativetest64 / libdlext_test_fd / "
2014-07-01 23:10:16 +02:00
# else
2014-10-04 02:52:44 +02:00
# define LIBPATH_PREFIX "%s / nativetest / libdlext_test_fd / "
2014-07-01 23:10:16 +02:00
# endif
2014-02-06 15:34:21 +01:00
2014-10-04 02:52:44 +02:00
# define LIBPATH LIBPATH_PREFIX "libdlext_test_fd.so"
2014-10-09 01:22:03 +02:00
# define LIBZIPPATH LIBPATH_PREFIX "libdlext_test_fd_zipaligned.zip"
2014-10-04 02:52:44 +02:00
# define LIBZIP_OFFSET 2*PAGE_SIZE
2014-02-06 15:34:21 +01:00
class DlExtTest : public : : testing : : Test {
protected :
virtual void SetUp ( ) {
2014-10-04 02:52:44 +02:00
handle_ = nullptr ;
2014-02-06 15:34:21 +01:00
// verify that we don't have the library loaded already
2014-10-04 02:52:44 +02:00
void * h = dlopen ( LIBNAME , RTLD_NOW | RTLD_NOLOAD ) ;
ASSERT_TRUE ( h = = nullptr ) ;
h = dlopen ( LIBNAME_NORELRO , RTLD_NOW | RTLD_NOLOAD ) ;
ASSERT_TRUE ( h = = nullptr ) ;
2014-02-06 15:34:21 +01:00
// call dlerror() to swallow the error, and check it was the one we wanted
2014-10-04 02:52:44 +02:00
ASSERT_STREQ ( " dlopen failed: library \" " LIBNAME_NORELRO " \" wasn't loaded and RTLD_NOLOAD prevented it " , dlerror ( ) ) ;
2014-02-06 15:34:21 +01:00
}
virtual void TearDown ( ) {
2014-10-04 02:52:44 +02:00
if ( handle_ ! = nullptr ) {
2014-02-06 15:34:21 +01:00
ASSERT_DL_ZERO ( dlclose ( handle_ ) ) ;
}
}
void * handle_ ;
} ;
TEST_F ( DlExtTest , ExtInfoNull ) {
2014-10-04 02:52:44 +02:00
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , nullptr ) ;
2014-02-06 15:34:21 +01:00
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
TEST_F ( DlExtTest , ExtInfoNoFlags ) {
android_dlextinfo extinfo ;
extinfo . flags = 0 ;
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
2014-07-01 23:10:16 +02:00
TEST_F ( DlExtTest , ExtInfoUseFd ) {
const char * android_data = getenv ( " ANDROID_DATA " ) ;
2014-10-04 02:52:44 +02:00
ASSERT_TRUE ( android_data ! = nullptr ) ;
2014-07-01 23:10:16 +02:00
char lib_path [ PATH_MAX ] ;
snprintf ( lib_path , sizeof ( lib_path ) , LIBPATH , android_data ) ;
android_dlextinfo extinfo ;
extinfo . flags = ANDROID_DLEXT_USE_LIBRARY_FD ;
extinfo . library_fd = TEMP_FAILURE_RETRY ( open ( lib_path , O_RDONLY | O_CLOEXEC ) ) ;
ASSERT_TRUE ( extinfo . library_fd ! = - 1 ) ;
handle_ = android_dlopen_ext ( lib_path , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
2014-10-04 02:52:44 +02:00
TEST_F ( DlExtTest , ExtInfoUseFdWithOffset ) {
const char * android_data = getenv ( " ANDROID_DATA " ) ;
ASSERT_TRUE ( android_data ! = nullptr ) ;
char lib_path [ PATH_MAX ] ;
snprintf ( lib_path , sizeof ( lib_path ) , LIBZIPPATH , android_data ) ;
android_dlextinfo extinfo ;
2014-10-21 21:09:18 +02:00
extinfo . flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET ;
2014-10-04 02:52:44 +02:00
extinfo . library_fd = TEMP_FAILURE_RETRY ( open ( lib_path , O_RDONLY | O_CLOEXEC ) ) ;
2014-10-21 21:09:18 +02:00
extinfo . library_fd_offset = LIBZIP_OFFSET ;
2014-10-04 02:52:44 +02:00
handle_ = android_dlopen_ext ( lib_path , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
TEST_F ( DlExtTest , ExtInfoUseFdWithInvalidOffset ) {
const char * android_data = getenv ( " ANDROID_DATA " ) ;
ASSERT_TRUE ( android_data ! = nullptr ) ;
char lib_path [ PATH_MAX ] ;
snprintf ( lib_path , sizeof ( lib_path ) , LIBZIPPATH , android_data ) ;
android_dlextinfo extinfo ;
2014-10-21 21:09:18 +02:00
extinfo . flags = ANDROID_DLEXT_USE_LIBRARY_FD | ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET ;
2014-10-04 02:52:44 +02:00
extinfo . library_fd = TEMP_FAILURE_RETRY ( open ( lib_path , O_RDONLY | O_CLOEXEC ) ) ;
2014-10-21 21:09:18 +02:00
extinfo . library_fd_offset = 17 ;
2014-10-04 02:52:44 +02:00
handle_ = android_dlopen_ext ( " libname_placeholder " , RTLD_NOW , & extinfo ) ;
ASSERT_TRUE ( handle_ = = nullptr ) ;
2014-10-21 21:09:18 +02:00
ASSERT_STREQ ( " dlopen failed: file offset for the library \" libname_placeholder \" is not page-aligned: 17 " , dlerror ( ) ) ;
extinfo . library_fd_offset = ( 5LL < < 58 ) + PAGE_SIZE ;
handle_ = android_dlopen_ext ( " libname_placeholder " , RTLD_NOW , & extinfo ) ;
ASSERT_TRUE ( handle_ = = nullptr ) ;
// TODO: Better error message when reading with offset > file_size
ASSERT_STREQ ( " dlopen failed: \" libname_placeholder \" has bad ELF magic " , dlerror ( ) ) ;
close ( extinfo . library_fd ) ;
2014-10-04 02:52:44 +02:00
}
TEST_F ( DlExtTest , ExtInfoUseOffsetWihtoutFd ) {
android_dlextinfo extinfo ;
2014-10-21 21:09:18 +02:00
extinfo . flags = ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET ;
extinfo . library_fd_offset = LIBZIP_OFFSET ;
2014-10-04 02:52:44 +02:00
handle_ = android_dlopen_ext ( " /some/lib/that/does_not_exist " , RTLD_NOW , & extinfo ) ;
ASSERT_TRUE ( handle_ = = nullptr ) ;
2014-10-21 21:09:18 +02:00
ASSERT_STREQ ( " dlopen failed: invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without ANDROID_DLEXT_USE_LIBRARY_FD): 0x20 " , dlerror ( ) ) ;
2014-10-04 02:52:44 +02:00
}
2014-02-06 15:34:21 +01:00
TEST_F ( DlExtTest , Reserved ) {
2014-10-04 02:52:44 +02:00
void * start = mmap ( nullptr , LIBSIZE , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS ,
2014-02-06 15:34:21 +01:00
- 1 , 0 ) ;
ASSERT_TRUE ( start ! = MAP_FAILED ) ;
android_dlextinfo extinfo ;
extinfo . flags = ANDROID_DLEXT_RESERVED_ADDRESS ;
extinfo . reserved_addr = start ;
extinfo . reserved_size = LIBSIZE ;
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
2014-08-27 22:45:37 +02:00
EXPECT_GE ( reinterpret_cast < void * > ( f ) , start ) ;
2014-02-06 15:34:21 +01:00
EXPECT_LT ( reinterpret_cast < void * > ( f ) ,
reinterpret_cast < char * > ( start ) + LIBSIZE ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
TEST_F ( DlExtTest , ReservedTooSmall ) {
2014-10-04 02:52:44 +02:00
void * start = mmap ( nullptr , PAGE_SIZE , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS ,
2014-02-06 15:34:21 +01:00
- 1 , 0 ) ;
ASSERT_TRUE ( start ! = MAP_FAILED ) ;
android_dlextinfo extinfo ;
extinfo . flags = ANDROID_DLEXT_RESERVED_ADDRESS ;
extinfo . reserved_addr = start ;
extinfo . reserved_size = PAGE_SIZE ;
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , & extinfo ) ;
2014-10-04 02:52:44 +02:00
EXPECT_EQ ( nullptr , handle_ ) ;
2014-02-06 15:34:21 +01:00
}
TEST_F ( DlExtTest , ReservedHint ) {
2014-10-04 02:52:44 +02:00
void * start = mmap ( nullptr , LIBSIZE , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS ,
2014-02-06 15:34:21 +01:00
- 1 , 0 ) ;
ASSERT_TRUE ( start ! = MAP_FAILED ) ;
android_dlextinfo extinfo ;
extinfo . flags = ANDROID_DLEXT_RESERVED_ADDRESS_HINT ;
extinfo . reserved_addr = start ;
extinfo . reserved_size = LIBSIZE ;
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
2014-08-27 22:45:37 +02:00
EXPECT_GE ( reinterpret_cast < void * > ( f ) , start ) ;
2014-02-06 15:34:21 +01:00
EXPECT_LT ( reinterpret_cast < void * > ( f ) ,
reinterpret_cast < char * > ( start ) + LIBSIZE ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
TEST_F ( DlExtTest , ReservedHintTooSmall ) {
2014-10-04 02:52:44 +02:00
void * start = mmap ( nullptr , PAGE_SIZE , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS ,
2014-02-06 15:34:21 +01:00
- 1 , 0 ) ;
ASSERT_TRUE ( start ! = MAP_FAILED ) ;
android_dlextinfo extinfo ;
extinfo . flags = ANDROID_DLEXT_RESERVED_ADDRESS_HINT ;
extinfo . reserved_addr = start ;
extinfo . reserved_size = PAGE_SIZE ;
handle_ = android_dlopen_ext ( LIBNAME , RTLD_NOW , & extinfo ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
2014-08-27 22:45:37 +02:00
EXPECT_TRUE ( reinterpret_cast < void * > ( f ) < start | |
( reinterpret_cast < void * > ( f ) > =
reinterpret_cast < char * > ( start ) + PAGE_SIZE ) ) ;
2014-02-06 15:34:21 +01:00
EXPECT_EQ ( 4 , f ( ) ) ;
}
2014-02-27 14:18:00 +01:00
2014-04-30 16:48:40 +02:00
class DlExtRelroSharingTest : public DlExtTest {
protected :
virtual void SetUp ( ) {
DlExtTest : : SetUp ( ) ;
2014-10-04 02:52:44 +02:00
void * start = mmap ( nullptr , LIBSIZE , PROT_NONE , MAP_PRIVATE | MAP_ANONYMOUS ,
2014-04-30 16:48:40 +02:00
- 1 , 0 ) ;
ASSERT_TRUE ( start ! = MAP_FAILED ) ;
extinfo_ . flags = ANDROID_DLEXT_RESERVED_ADDRESS ;
extinfo_ . reserved_addr = start ;
extinfo_ . reserved_size = LIBSIZE ;
extinfo_ . relro_fd = - 1 ;
const char * android_data = getenv ( " ANDROID_DATA " ) ;
2014-10-04 02:52:44 +02:00
ASSERT_TRUE ( android_data ! = nullptr ) ;
2014-04-30 16:48:40 +02:00
snprintf ( relro_file_ , sizeof ( relro_file_ ) , " %s/local/tmp/libdlext_test.relro " , android_data ) ;
}
2014-02-27 14:18:00 +01:00
2014-04-30 16:48:40 +02:00
virtual void TearDown ( ) {
DlExtTest : : TearDown ( ) ;
if ( extinfo_ . relro_fd ! = - 1 ) {
ASSERT_NOERROR ( close ( extinfo_ . relro_fd ) ) ;
2014-02-27 14:18:00 +01:00
}
}
2014-04-30 16:48:40 +02:00
void CreateRelroFile ( const char * lib ) {
int relro_fd = open ( relro_file_ , O_CREAT | O_RDWR | O_TRUNC , 0644 ) ;
ASSERT_NOERROR ( relro_fd ) ;
pid_t pid = fork ( ) ;
if ( pid = = 0 ) {
// child process
extinfo_ . flags | = ANDROID_DLEXT_WRITE_RELRO ;
extinfo_ . relro_fd = relro_fd ;
void * handle = android_dlopen_ext ( lib , RTLD_NOW , & extinfo_ ) ;
2014-10-04 02:52:44 +02:00
if ( handle = = nullptr ) {
2014-04-30 16:48:40 +02:00
fprintf ( stderr , " in child: %s \n " , dlerror ( ) ) ;
exit ( 1 ) ;
}
exit ( 0 ) ;
}
// continuing in parent
ASSERT_NOERROR ( close ( relro_fd ) ) ;
ASSERT_NOERROR ( pid ) ;
int status ;
ASSERT_EQ ( pid , waitpid ( pid , & status , 0 ) ) ;
ASSERT_TRUE ( WIFEXITED ( status ) ) ;
ASSERT_EQ ( 0 , WEXITSTATUS ( status ) ) ;
// reopen file for reading so it can be used
relro_fd = open ( relro_file_ , O_RDONLY ) ;
ASSERT_NOERROR ( relro_fd ) ;
extinfo_ . flags | = ANDROID_DLEXT_USE_RELRO ;
extinfo_ . relro_fd = relro_fd ;
}
void TryUsingRelro ( const char * lib ) {
handle_ = android_dlopen_ext ( lib , RTLD_NOW , & extinfo_ ) ;
ASSERT_DL_NOTNULL ( handle_ ) ;
fn f = reinterpret_cast < fn > ( dlsym ( handle_ , " getRandomNumber " ) ) ;
ASSERT_DL_NOTNULL ( f ) ;
EXPECT_EQ ( 4 , f ( ) ) ;
}
2014-05-02 15:57:42 +02:00
void SpawnChildrenAndMeasurePss ( const char * lib , bool share_relro , size_t * pss_out ) ;
2014-04-30 16:48:40 +02:00
android_dlextinfo extinfo_ ;
char relro_file_ [ PATH_MAX ] ;
} ;
TEST_F ( DlExtRelroSharingTest , ChildWritesGoodData ) {
ASSERT_NO_FATAL_FAILURE ( CreateRelroFile ( LIBNAME ) ) ;
ASSERT_NO_FATAL_FAILURE ( TryUsingRelro ( LIBNAME ) ) ;
}
2014-02-27 14:18:00 +01:00
2014-04-30 16:48:40 +02:00
TEST_F ( DlExtRelroSharingTest , ChildWritesNoRelro ) {
ASSERT_NO_FATAL_FAILURE ( CreateRelroFile ( LIBNAME_NORELRO ) ) ;
ASSERT_NO_FATAL_FAILURE ( TryUsingRelro ( LIBNAME_NORELRO ) ) ;
}
TEST_F ( DlExtRelroSharingTest , RelroFileEmpty ) {
int relro_fd = open ( relro_file_ , O_CREAT | O_RDWR | O_TRUNC , 0644 ) ;
2014-02-27 14:18:00 +01:00
ASSERT_NOERROR ( relro_fd ) ;
ASSERT_NOERROR ( close ( relro_fd ) ) ;
2014-04-30 16:48:40 +02:00
ASSERT_NO_FATAL_FAILURE ( TryUsingRelro ( LIBNAME ) ) ;
2014-02-27 14:18:00 +01:00
}
2014-05-02 15:57:42 +02:00
TEST_F ( DlExtRelroSharingTest , VerifyMemorySaving ) {
2014-09-03 20:30:21 +02:00
if ( geteuid ( ) ! = 0 ) {
GTEST_LOG_ ( INFO ) < < " This test must be run as root. \n " ;
return ;
}
2014-05-02 15:57:42 +02:00
ASSERT_NO_FATAL_FAILURE ( CreateRelroFile ( LIBNAME ) ) ;
int relro_fd = open ( relro_file_ , O_RDONLY ) ;
ASSERT_NOERROR ( relro_fd ) ;
extinfo_ . flags | = ANDROID_DLEXT_USE_RELRO ;
extinfo_ . relro_fd = relro_fd ;
int pipefd [ 2 ] ;
ASSERT_NOERROR ( pipe ( pipefd ) ) ;
size_t without_sharing , with_sharing ;
ASSERT_NO_FATAL_FAILURE ( SpawnChildrenAndMeasurePss ( LIBNAME , false , & without_sharing ) ) ;
ASSERT_NO_FATAL_FAILURE ( SpawnChildrenAndMeasurePss ( LIBNAME , true , & with_sharing ) ) ;
// We expect the sharing to save at least 10% of the total PSS. In practice
// it saves 40%+ for this test.
size_t expected_size = without_sharing - ( without_sharing / 10 ) ;
EXPECT_LT ( with_sharing , expected_size ) ;
}
void getPss ( pid_t pid , size_t * pss_out ) {
pm_kernel_t * kernel ;
ASSERT_EQ ( 0 , pm_kernel_create ( & kernel ) ) ;
pm_process_t * process ;
ASSERT_EQ ( 0 , pm_process_create ( kernel , pid , & process ) ) ;
pm_map_t * * maps ;
size_t num_maps ;
ASSERT_EQ ( 0 , pm_process_maps ( process , & maps , & num_maps ) ) ;
size_t total_pss = 0 ;
for ( size_t i = 0 ; i < num_maps ; i + + ) {
pm_memusage_t usage ;
ASSERT_EQ ( 0 , pm_map_usage ( maps [ i ] , & usage ) ) ;
total_pss + = usage . pss ;
}
* pss_out = total_pss ;
free ( maps ) ;
pm_process_destroy ( process ) ;
pm_kernel_destroy ( kernel ) ;
}
void DlExtRelroSharingTest : : SpawnChildrenAndMeasurePss ( const char * lib , bool share_relro ,
size_t * pss_out ) {
const int CHILDREN = 20 ;
// Create children
pid_t childpid [ CHILDREN ] ;
int childpipe [ CHILDREN ] ;
for ( int i = 0 ; i < CHILDREN ; + + i ) {
char read_buf ;
int child_done_pipe [ 2 ] , parent_done_pipe [ 2 ] ;
ASSERT_NOERROR ( pipe ( child_done_pipe ) ) ;
ASSERT_NOERROR ( pipe ( parent_done_pipe ) ) ;
pid_t child = fork ( ) ;
if ( child = = 0 ) {
// close the 'wrong' ends of the pipes in the child
close ( child_done_pipe [ 0 ] ) ;
close ( parent_done_pipe [ 1 ] ) ;
// open the library
void * handle ;
if ( share_relro ) {
handle = android_dlopen_ext ( lib , RTLD_NOW , & extinfo_ ) ;
} else {
handle = dlopen ( lib , RTLD_NOW ) ;
}
2014-10-04 02:52:44 +02:00
if ( handle = = nullptr ) {
2014-05-02 15:57:42 +02:00
fprintf ( stderr , " in child: %s \n " , dlerror ( ) ) ;
exit ( 1 ) ;
}
// close write end of child_done_pipe to signal the parent that we're done.
close ( child_done_pipe [ 1 ] ) ;
// wait for the parent to close parent_done_pipe, then exit
read ( parent_done_pipe [ 0 ] , & read_buf , 1 ) ;
exit ( 0 ) ;
}
ASSERT_NOERROR ( child ) ;
// close the 'wrong' ends of the pipes in the parent
close ( child_done_pipe [ 1 ] ) ;
close ( parent_done_pipe [ 0 ] ) ;
// wait for the child to be done
read ( child_done_pipe [ 0 ] , & read_buf , 1 ) ;
close ( child_done_pipe [ 0 ] ) ;
// save the child's pid and the parent_done_pipe
childpid [ i ] = child ;
childpipe [ i ] = parent_done_pipe [ 1 ] ;
}
// Sum the PSS of all the children
size_t total_pss = 0 ;
for ( int i = 0 ; i < CHILDREN ; + + i ) {
size_t child_pss ;
ASSERT_NO_FATAL_FAILURE ( getPss ( childpid [ i ] , & child_pss ) ) ;
total_pss + = child_pss ;
}
* pss_out = total_pss ;
// Close pipes and wait for children to exit
for ( int i = 0 ; i < CHILDREN ; + + i ) {
ASSERT_NOERROR ( close ( childpipe [ i ] ) ) ;
}
for ( int i = 0 ; i < CHILDREN ; + + i ) {
int status ;
ASSERT_EQ ( childpid [ i ] , waitpid ( childpid [ i ] , & status , 0 ) ) ;
ASSERT_TRUE ( WIFEXITED ( status ) ) ;
ASSERT_EQ ( 0 , WEXITSTATUS ( status ) ) ;
}
}