2012-10-19 14:29:46 +04:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2012 Zbigniew Jędrzejewski - Szmek
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/>.
* * */
2012-11-12 23:16:07 +04:00
# include <locale.h>
2012-10-19 14:29:46 +04:00
# include <stdio.h>
# include <string.h>
# include <getopt.h>
2012-10-27 03:19:47 +04:00
# include <fcntl.h>
# include <unistd.h>
2012-10-19 14:29:46 +04:00
# include <systemd/sd-journal.h>
# include "build.h"
# include "set.h"
# include "util.h"
# include "log.h"
# include "path-util.h"
# include "pager.h"
2013-03-18 07:36:25 +04:00
# include "macro.h"
2013-04-16 07:25:57 +04:00
# include "journal-internal.h"
2012-10-19 14:29:46 +04:00
static enum {
ACTION_NONE ,
ACTION_LIST ,
ACTION_DUMP ,
2012-10-27 03:19:47 +04:00
ACTION_GDB ,
2012-10-19 14:29:46 +04:00
} arg_action = ACTION_LIST ;
2012-10-26 22:23:28 +04:00
static FILE * output = NULL ;
2012-10-30 14:15:24 +04:00
static char * field = NULL ;
2012-10-19 14:29:46 +04:00
2012-10-26 22:23:28 +04:00
static int arg_no_pager = false ;
2012-10-30 12:45:19 +04:00
static int arg_no_legend = false ;
2012-10-19 14:29:46 +04:00
static Set * new_matches ( void ) {
Set * set ;
char * tmp ;
int r ;
set = set_new ( trivial_hash_func , trivial_compare_func ) ;
if ( ! set ) {
log_oom ( ) ;
return NULL ;
}
tmp = strdup ( " MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1 " ) ;
if ( ! tmp ) {
log_oom ( ) ;
2012-10-26 16:11:37 +04:00
set_free ( set ) ;
2012-10-19 14:29:46 +04:00
return NULL ;
}
2013-04-23 07:12:15 +04:00
r = set_consume ( set , tmp ) ;
2012-10-19 14:29:46 +04:00
if ( r < 0 ) {
log_error ( " failed to add to set: %s " , strerror ( - r ) ) ;
2012-10-26 16:11:37 +04:00
set_free ( set ) ;
2012-10-19 14:29:46 +04:00
return NULL ;
}
return set ;
}
static int help ( void ) {
printf ( " %s [OPTIONS...] [MATCHES...] \n \n "
" List or retrieve coredumps from the journal. \n \n "
" Flags: \n "
" -o --output=FILE Write output to FILE \n "
" --no-pager Do not pipe output into a pager \n "
2013-05-17 17:38:13 +04:00
" --no-legend Do not print the column headers. \n \n "
2012-10-19 14:29:46 +04:00
" Commands: \n "
" -h --help Show this help \n "
" --version Print version string \n "
2012-11-09 23:14:53 +04:00
" -F --field=FIELD List all values a certain field takes \n "
2012-11-08 19:13:46 +04:00
" gdb Start gdb for the first matching coredump \n "
2012-10-19 14:29:46 +04:00
" list List available coredumps \n "
" dump PID Print coredump to stdout \n "
" dump PATH Print coredump to stdout \n "
, program_invocation_short_name ) ;
return 0 ;
}
static int add_match ( Set * set , const char * match ) {
int r = - ENOMEM ;
unsigned pid ;
const char * prefix ;
char * pattern = NULL ;
2013-04-18 11:11:22 +04:00
_cleanup_free_ char * p = NULL ;
2012-10-19 14:29:46 +04:00
if ( strchr ( match , ' = ' ) )
prefix = " " ;
else if ( strchr ( match , ' / ' ) ) {
p = path_make_absolute_cwd ( match ) ;
if ( ! p )
goto fail ;
match = p ;
prefix = " COREDUMP_EXE= " ;
}
else if ( safe_atou ( match , & pid ) = = 0 )
prefix = " COREDUMP_PID= " ;
else
prefix = " COREDUMP_COMM= " ;
pattern = strjoin ( prefix , match , NULL ) ;
if ( ! pattern )
goto fail ;
2013-04-23 07:12:15 +04:00
log_debug ( " Adding pattern: %s " , pattern ) ;
2014-01-14 18:49:10 +04:00
r = set_consume ( set , pattern ) ;
2012-10-19 14:29:46 +04:00
if ( r < 0 ) {
2013-04-23 07:12:15 +04:00
log_error ( " Failed to add pattern '%s': %s " ,
2012-10-19 14:29:46 +04:00
pattern , strerror ( - r ) ) ;
goto fail ;
}
return 0 ;
fail :
2013-04-23 07:12:15 +04:00
log_error ( " Failed to add match: %s " , strerror ( - r ) ) ;
2012-10-19 14:29:46 +04:00
return r ;
}
2013-03-18 07:36:25 +04:00
static int parse_argv ( int argc , char * argv [ ] , Set * matches ) {
2012-10-19 14:29:46 +04:00
enum {
ARG_VERSION = 0x100 ,
ARG_NO_PAGER ,
2012-10-30 12:45:19 +04:00
ARG_NO_LEGEND ,
2012-10-19 14:29:46 +04:00
} ;
int r , c ;
static const struct option options [ ] = {
2012-10-30 12:35:53 +04:00
{ " help " , no_argument , NULL , ' h ' } ,
{ " version " , no_argument , NULL , ARG_VERSION } ,
{ " no-pager " , no_argument , NULL , ARG_NO_PAGER } ,
2012-10-30 12:45:19 +04:00
{ " no-legend " , no_argument , NULL , ARG_NO_LEGEND } ,
2012-10-30 12:35:53 +04:00
{ " output " , required_argument , NULL , ' o ' } ,
2012-10-30 14:15:24 +04:00
{ " field " , required_argument , NULL , ' F ' } ,
2013-11-06 21:28:39 +04:00
{ }
2012-10-19 14:29:46 +04:00
} ;
assert ( argc > = 0 ) ;
assert ( argv ) ;
2012-10-30 14:15:24 +04:00
while ( ( c = getopt_long ( argc , argv , " ho:F: " , options , NULL ) ) > = 0 )
2012-10-19 14:29:46 +04:00
switch ( c ) {
2013-11-06 21:28:39 +04:00
2012-10-19 14:29:46 +04:00
case ' h ' :
arg_action = ACTION_NONE ;
2013-11-06 21:28:39 +04:00
return help ( ) ;
2012-10-19 14:29:46 +04:00
case ARG_VERSION :
2013-11-06 21:28:39 +04:00
arg_action = ACTION_NONE ;
2012-10-19 14:29:46 +04:00
puts ( PACKAGE_STRING ) ;
puts ( SYSTEMD_FEATURES ) ;
return 0 ;
case ARG_NO_PAGER :
arg_no_pager = true ;
break ;
2012-10-30 12:45:19 +04:00
case ARG_NO_LEGEND :
arg_no_legend = true ;
break ;
2012-10-19 14:29:46 +04:00
case ' o ' :
if ( output ) {
log_error ( " cannot set output more than once " ) ;
return - EINVAL ;
}
output = fopen ( optarg , " we " ) ;
if ( ! output ) {
log_error ( " writing to '%s': %m " , optarg ) ;
return - errno ;
}
break ;
2012-10-30 12:35:53 +04:00
2012-10-30 14:15:24 +04:00
case ' F ' :
if ( field ) {
log_error ( " cannot use --field/-F more than once " ) ;
return - EINVAL ;
}
field = optarg ;
break ;
2012-10-30 12:35:53 +04:00
case ' ? ' :
return - EINVAL ;
2012-10-19 14:29:46 +04:00
default :
2013-11-06 21:28:39 +04:00
assert_not_reached ( " Unhandled option " ) ;
2012-10-19 14:29:46 +04:00
}
if ( optind < argc ) {
const char * cmd = argv [ optind + + ] ;
2013-12-04 01:27:45 +04:00
if ( streq ( cmd , " list " ) )
2012-10-19 14:29:46 +04:00
arg_action = ACTION_LIST ;
else if ( streq ( cmd , " dump " ) )
arg_action = ACTION_DUMP ;
2012-10-27 03:19:47 +04:00
else if ( streq ( cmd , " gdb " ) )
arg_action = ACTION_GDB ;
2012-10-19 14:29:46 +04:00
else {
log_error ( " Unknown action '%s' " , cmd ) ;
return - EINVAL ;
}
}
2012-10-30 14:15:24 +04:00
if ( field & & arg_action ! = ACTION_LIST ) {
log_error ( " Option --field/-F only makes sense with list " ) ;
return - EINVAL ;
}
2012-10-19 14:29:46 +04:00
while ( optind < argc ) {
r = add_match ( matches , argv [ optind ] ) ;
if ( r ! = 0 )
return r ;
optind + + ;
}
return 0 ;
}
2012-10-26 22:25:10 +04:00
static int retrieve ( const void * data ,
size_t len ,
const char * name ,
const char * * var ) {
2012-10-19 14:29:46 +04:00
2012-10-30 14:15:24 +04:00
size_t ident ;
2012-10-26 22:25:10 +04:00
2012-10-30 14:15:24 +04:00
ident = strlen ( name ) + 1 ; /* name + "=" */
2012-10-26 22:25:10 +04:00
2012-10-30 14:15:24 +04:00
if ( len < ident )
2012-10-26 22:25:10 +04:00
return 0 ;
2012-10-19 14:29:46 +04:00
2012-10-30 14:15:24 +04:00
if ( memcmp ( data , name , ident - 1 ) ! = 0 )
2012-10-26 22:25:10 +04:00
return 0 ;
2012-10-30 14:15:24 +04:00
if ( ( ( const char * ) data ) [ ident - 1 ] ! = ' = ' )
2012-10-26 22:25:10 +04:00
return 0 ;
2012-10-19 14:29:46 +04:00
2012-10-30 14:15:24 +04:00
* var = strndup ( ( const char * ) data + ident , len - ident ) ;
2012-12-19 17:38:53 +04:00
if ( ! * var )
2012-10-19 14:29:46 +04:00
return log_oom ( ) ;
return 0 ;
}
2012-10-30 14:15:24 +04:00
static void print_field ( FILE * file , sd_journal * j ) {
2013-04-18 11:11:22 +04:00
_cleanup_free_ const char * value = NULL ;
2012-10-30 14:15:24 +04:00
const void * d ;
size_t l ;
assert ( field ) ;
SD_JOURNAL_FOREACH_DATA ( j , d , l )
retrieve ( d , l , field , & value ) ;
if ( value )
fprintf ( file , " %s \n " , value ) ;
}
2012-10-30 12:45:19 +04:00
static int print_entry ( FILE * file , sd_journal * j , int had_legend ) {
2013-04-18 11:11:22 +04:00
_cleanup_free_ const char
2012-10-19 14:29:46 +04:00
* pid = NULL , * uid = NULL , * gid = NULL ,
* sgnl = NULL , * exe = NULL ;
2012-10-26 22:25:10 +04:00
const void * d ;
size_t l ;
2012-10-26 22:34:39 +04:00
usec_t t ;
char buf [ FORMAT_TIMESTAMP_MAX ] ;
int r ;
2012-10-26 22:25:10 +04:00
SD_JOURNAL_FOREACH_DATA ( j , d , l ) {
retrieve ( d , l , " COREDUMP_PID " , & pid ) ;
retrieve ( d , l , " COREDUMP_PID " , & pid ) ;
retrieve ( d , l , " COREDUMP_UID " , & uid ) ;
retrieve ( d , l , " COREDUMP_GID " , & gid ) ;
retrieve ( d , l , " COREDUMP_SIGNAL " , & sgnl ) ;
retrieve ( d , l , " COREDUMP_EXE " , & exe ) ;
if ( ! exe )
retrieve ( d , l , " COREDUMP_COMM " , & exe ) ;
if ( ! exe )
retrieve ( d , l , " COREDUMP_CMDLINE " , & exe ) ;
}
2012-10-19 14:29:46 +04:00
if ( ! pid & & ! uid & & ! gid & & ! sgnl & & ! exe ) {
2012-10-26 22:34:39 +04:00
log_warning ( " Empty coredump log entry " ) ;
return - EINVAL ;
}
r = sd_journal_get_realtime_usec ( j , & t ) ;
if ( r < 0 ) {
log_error ( " Failed to get realtime timestamp: %s " , strerror ( - r ) ) ;
return r ;
2012-10-19 14:29:46 +04:00
}
2012-10-26 22:34:39 +04:00
format_timestamp ( buf , sizeof ( buf ) , t ) ;
2012-10-30 12:45:19 +04:00
if ( ! had_legend & & ! arg_no_legend )
2012-10-26 22:34:39 +04:00
fprintf ( file , " %-*s %*s %*s %*s %*s %s \n " ,
FORMAT_TIMESTAMP_MAX - 1 , " TIME " ,
2012-10-19 14:29:46 +04:00
6 , " PID " ,
5 , " UID " ,
5 , " GID " ,
2012-10-26 22:34:39 +04:00
3 , " SIG " ,
" EXE " ) ;
2012-10-19 14:29:46 +04:00
2012-10-26 22:34:39 +04:00
fprintf ( file , " %*s %*s %*s %*s %*s %s \n " ,
FORMAT_TIMESTAMP_MAX - 1 , buf ,
2012-10-19 14:29:46 +04:00
6 , pid ,
5 , uid ,
5 , gid ,
3 , sgnl ,
exe ) ;
2012-10-26 22:34:39 +04:00
return 0 ;
2012-10-19 14:29:46 +04:00
}
static int dump_list ( sd_journal * j ) {
int found = 0 ;
assert ( j ) ;
2012-11-21 03:28:00 +04:00
/* The coredumps are likely to compressed, and for just
2013-08-02 15:58:26 +04:00
* listing them we don ' t need to decompress them , so let ' s
2012-11-21 03:28:00 +04:00
* pick a fairly low data threshold here */
sd_journal_set_data_threshold ( j , 4096 ) ;
2012-10-30 14:15:24 +04:00
SD_JOURNAL_FOREACH ( j ) {
if ( field )
print_field ( stdout , j ) ;
else
print_entry ( stdout , j , found + + ) ;
}
2012-10-19 14:29:46 +04:00
2012-10-30 14:15:24 +04:00
if ( ! field & & ! found ) {
2012-10-26 22:34:39 +04:00
log_notice ( " No coredumps found " ) ;
2012-10-19 14:29:46 +04:00
return - ESRCH ;
}
return 0 ;
}
2012-10-27 03:19:47 +04:00
static int focus ( sd_journal * j ) {
2012-10-19 14:29:46 +04:00
int r ;
r = sd_journal_seek_tail ( j ) ;
if ( r = = 0 )
r = sd_journal_previous ( j ) ;
if ( r < 0 ) {
log_error ( " Failed to search journal: %s " , strerror ( - r ) ) ;
return r ;
}
2012-10-26 22:25:10 +04:00
if ( r = = 0 ) {
log_error ( " No match found " ) ;
return - ESRCH ;
}
2012-10-27 03:19:47 +04:00
return r ;
}
2012-10-19 14:29:46 +04:00
2012-10-27 03:19:47 +04:00
static int dump_core ( sd_journal * j ) {
const void * data ;
size_t len , ret ;
int r ;
assert ( j ) ;
2012-11-21 03:28:00 +04:00
/* We want full data, nothing truncated. */
sd_journal_set_data_threshold ( j , 0 ) ;
2012-10-27 03:19:47 +04:00
r = focus ( j ) ;
if ( r < 0 )
2012-10-19 14:29:46 +04:00
return r ;
print_entry ( output ? stdout : stderr , j , false ) ;
if ( on_tty ( ) & & ! output ) {
log_error ( " Refusing to dump core to tty " ) ;
return - ENOTTY ;
}
2012-10-27 03:19:47 +04:00
r = sd_journal_get_data ( j , " COREDUMP " , ( const void * * ) & data , & len ) ;
if ( r < 0 ) {
log_error ( " Failed to retrieve COREDUMP field: %s " , strerror ( - r ) ) ;
return r ;
}
2012-10-19 14:29:46 +04:00
assert ( len > = 9 ) ;
2012-10-27 03:19:47 +04:00
data = ( const uint8_t * ) data + 9 ;
len - = 9 ;
2012-10-19 14:29:46 +04:00
2012-10-27 03:19:47 +04:00
ret = fwrite ( data , len , 1 , output ? output : stdout ) ;
2012-10-19 14:29:46 +04:00
if ( ret ! = 1 ) {
log_error ( " dumping coredump: %m (%zu) " , ret ) ;
return - errno ;
}
r = sd_journal_previous ( j ) ;
if ( r > = 0 )
2013-12-24 19:39:37 +04:00
log_warning ( " More than one entry matches, ignoring rest. " ) ;
2012-10-19 14:29:46 +04:00
return 0 ;
}
2012-10-27 03:19:47 +04:00
static int run_gdb ( sd_journal * j ) {
char path [ ] = " /var/tmp/coredump-XXXXXX " ;
const void * data ;
size_t len ;
ssize_t sz ;
pid_t pid ;
_cleanup_free_ char * exe = NULL ;
int r ;
_cleanup_close_ int fd = - 1 ;
siginfo_t st ;
assert ( j ) ;
2012-11-21 03:28:00 +04:00
sd_journal_set_data_threshold ( j , 0 ) ;
2012-10-27 03:19:47 +04:00
r = focus ( j ) ;
if ( r < 0 )
return r ;
print_entry ( stdout , j , false ) ;
r = sd_journal_get_data ( j , " COREDUMP_EXE " , ( const void * * ) & data , & len ) ;
if ( r < 0 ) {
log_error ( " Failed to retrieve COREDUMP_EXE field: %s " , strerror ( - r ) ) ;
return r ;
}
assert ( len > = 13 ) ;
data = ( const uint8_t * ) data + 13 ;
len - = 13 ;
exe = strndup ( data , len ) ;
if ( ! exe )
return log_oom ( ) ;
if ( endswith ( exe , " (deleted) " ) ) {
log_error ( " Binary already deleted. " ) ;
return - ENOENT ;
}
r = sd_journal_get_data ( j , " COREDUMP " , ( const void * * ) & data , & len ) ;
if ( r < 0 ) {
log_error ( " Failed to retrieve COREDUMP field: %s " , strerror ( - r ) ) ;
return r ;
}
assert ( len > = 9 ) ;
data = ( const uint8_t * ) data + 9 ;
len - = 9 ;
fd = mkostemp ( path , O_WRONLY ) ;
if ( fd < 0 ) {
log_error ( " Failed to create temporary file: %m " ) ;
return - errno ;
}
sz = write ( fd , data , len ) ;
if ( sz < 0 ) {
2013-11-26 12:38:02 +04:00
log_error ( " Failed to write temporary file: %m " ) ;
2012-10-27 03:19:47 +04:00
r = - errno ;
goto finish ;
}
if ( sz ! = ( ssize_t ) len ) {
log_error ( " Short write to temporary file. " ) ;
r = - EIO ;
goto finish ;
}
close_nointr_nofail ( fd ) ;
fd = - 1 ;
pid = fork ( ) ;
if ( pid < 0 ) {
log_error ( " Failed to fork(): %m " ) ;
r = - errno ;
goto finish ;
}
if ( pid = = 0 ) {
execlp ( " gdb " , " gdb " , exe , path , NULL ) ;
log_error ( " Failed to invoke gdb: %m " ) ;
_exit ( 1 ) ;
}
r = wait_for_terminate ( pid , & st ) ;
if ( r < 0 ) {
log_error ( " Failed to wait for gdb: %m " ) ;
goto finish ;
}
r = st . si_code = = CLD_EXITED ? st . si_status : 255 ;
finish :
unlink ( path ) ;
return r ;
}
2012-10-19 14:29:46 +04:00
int main ( int argc , char * argv [ ] ) {
2013-04-18 11:11:22 +04:00
_cleanup_journal_close_ sd_journal * j = NULL ;
2012-10-19 14:29:46 +04:00
const char * match ;
Iterator it ;
int r = 0 ;
2013-04-18 11:11:22 +04:00
_cleanup_set_free_free_ Set * matches = NULL ;
2012-10-19 14:29:46 +04:00
2012-11-12 23:16:07 +04:00
setlocale ( LC_ALL , " " ) ;
2012-10-19 14:29:46 +04:00
log_parse_environment ( ) ;
log_open ( ) ;
matches = new_matches ( ) ;
2012-10-30 12:44:32 +04:00
if ( ! matches ) {
r = - ENOMEM ;
2012-10-19 14:29:46 +04:00
goto end ;
2012-10-30 12:44:32 +04:00
}
2012-10-19 14:29:46 +04:00
2013-03-18 07:36:25 +04:00
r = parse_argv ( argc , argv , matches ) ;
2012-10-30 12:44:32 +04:00
if ( r < 0 )
2012-10-19 14:29:46 +04:00
goto end ;
if ( arg_action = = ACTION_NONE )
goto end ;
r = sd_journal_open ( & j , SD_JOURNAL_LOCAL_ONLY ) ;
if ( r < 0 ) {
log_error ( " Failed to open journal: %s " , strerror ( - r ) ) ;
goto end ;
}
SET_FOREACH ( match , matches , it ) {
r = sd_journal_add_match ( j , match , strlen ( match ) ) ;
if ( r ! = 0 ) {
log_error ( " Failed to add match '%s': %s " ,
match , strerror ( - r ) ) ;
goto end ;
}
}
2013-08-02 15:58:26 +04:00
if ( _unlikely_ ( log_get_max_level ( ) > = LOG_PRI ( LOG_DEBUG ) ) ) {
_cleanup_free_ char * filter ;
filter = journal_make_match_string ( j ) ;
log_debug ( " Journal filter: %s " , filter ) ;
}
2012-10-19 14:29:46 +04:00
switch ( arg_action ) {
2012-10-27 03:19:47 +04:00
2012-10-19 14:29:46 +04:00
case ACTION_LIST :
if ( ! arg_no_pager )
2013-03-07 23:44:35 +04:00
pager_open ( false ) ;
2012-10-19 14:29:46 +04:00
r = dump_list ( j ) ;
break ;
2012-10-27 03:19:47 +04:00
2012-10-19 14:29:46 +04:00
case ACTION_DUMP :
r = dump_core ( j ) ;
break ;
2012-10-27 03:19:47 +04:00
case ACTION_GDB :
r = run_gdb ( j ) ;
break ;
default :
2012-10-19 14:29:46 +04:00
assert_not_reached ( " Shouldn't be here " ) ;
}
end :
pager_close ( ) ;
if ( output )
fclose ( output ) ;
2012-10-27 03:19:47 +04:00
return r > = 0 ? r : EXIT_FAILURE ;
2012-10-19 14:29:46 +04:00
}