* Copyright (C) 2011 David Goulet <david.goulet@polymtl.ca>
* Copyright (C) 2014 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License, version 2 only,
- * as published by the Free Software Foundation.
+ * SPDX-License-Identifier: GPL-2.0-only
*
- * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#define _GNU_SOURCE
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
-#include <config.h>
#include <ctype.h>
#include <dirent.h>
-#include <byteswap.h>
+#include <common/compat/endian.h>
#include <inttypes.h>
+#include <stdbool.h>
#include <version.h>
#include <lttng/lttng.h>
#include <common/common.h>
-
-#define DEFAULT_VIEWER "babeltrace"
+#include <common/spawn-viewer.h>
+#include <common/utils.h>
#define COPY_BUFLEN 4096
#define RB_CRASH_DUMP_ABI_LEN 32
0xF1 ^ 0xFF, 0x77 ^ 0xFF, 0xBF ^ 0xFF, 0x17 ^ 0xFF, \
}
+static const char *help_msg =
+#ifdef LTTNG_EMBED_HELP
+#include <lttng-crash.1.h>
+#else
+NULL
+#endif
+;
+
/*
* Non-static to ensure the compiler does not optimize away the xor.
*/
};
/* Variables */
-static char *progname,
- *opt_viewer_path = DEFAULT_VIEWER,
- *opt_output_path;
+static const char *progname;
+static char *opt_viewer_path = NULL;
+static char *opt_output_path = NULL;
static char *input_path;
{ NULL, 0, NULL, 0 },
};
-static void usage(FILE *ofp)
+static void usage(void)
{
- fprintf(ofp, "LTTng Crash Trace Viewer " VERSION " - " VERSION_NAME "%s\n\n",
- GIT_VERSION[0] == '\0' ? "" : " - " GIT_VERSION);
- fprintf(ofp, "usage: lttng-crash [OPTIONS] FILE\n");
- fprintf(ofp, "\n");
- fprintf(ofp, "Options:\n");
- fprintf(ofp, " -V, --version Show version.\n");
- fprintf(ofp, " -h, --help Show this help.\n");
- fprintf(ofp, " --list-options Simple listing of lttng-crash options.\n");
- fprintf(ofp, " -v, --verbose Increase verbosity.\n");
- fprintf(ofp, " -e, --viewer Specify viewer and/or options to use. This will\n"
- " completely override the default viewers so please\n"
- " make sure to specify the full command. The trace\n"
- " directory paths appended at the end to the\n"
- " arguments.\n");
- fprintf(ofp, " -x, --extract PATH Extract trace(s) to specified path. Don't view\n"
- " trace.\n");
- fprintf(ofp, "\n");
- fprintf(ofp, "Please see the lttng-crash(1) man page for full documentation.\n");
- fprintf(ofp, "See http://lttng.org for updates, bug reports and news.\n");
+ int ret = utils_show_help(1, "lttng-crash", help_msg);
+
+ if (ret) {
+ ERR("Cannot show --help for `lttng-crash`");
+ perror("exec");
+ exit(EXIT_FAILURE);
+ }
}
static void version(FILE *ofp)
{
fprintf(ofp, "%s (LTTng Crash Trace Viewer) " VERSION " - " VERSION_NAME
-"%s\n",
+ "%s%s\n",
progname,
- GIT_VERSION[0] == '\0' ? "" : " - " GIT_VERSION);
+ GIT_VERSION[0] == '\0' ? "" : " - " GIT_VERSION,
+ EXTRA_VERSION_NAME[0] == '\0' ? "" : " - " EXTRA_VERSION_NAME);
}
/*
int opt, ret = 0;
if (argc < 2) {
- usage(stderr);
+ usage();
exit(EXIT_FAILURE);
}
- while ((opt = getopt_long(argc, argv, "+Vhvex:", long_options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "+Vhve:x:", long_options, NULL)) != -1) {
switch (opt) {
case 'V':
version(stdout);
ret = 1;
goto end;
case 'h':
- usage(stdout);
+ usage();
ret = 1;
goto end;
case 'v':
}
break;
case 'e':
+ free(opt_viewer_path);
opt_viewer_path = strdup(optarg);
break;
case 'x':
+ free(opt_output_path);
opt_output_path = strdup(optarg);
break;
case OPT_DUMP_OPTIONS:
ret = 1;
goto end;
default:
- usage(stderr);
+ ERR("Unknown command-line option");
goto error;
}
}
/* No leftovers, or more than one input path, print usage and quit */
- if ((argc - optind) == 0 || (argc - optind) > 1) {
- usage(stderr);
+ if (argc - optind != 1) {
+ ERR("Command-line error: Specify exactly one input path");
goto error;
}
switch (size) {
case 1: return *(uint8_t *) ptr;
case 2: if (layout->reverse_byte_order) {
- return __bswap_16(*(uint16_t *) ptr);
+ return bswap_16(*(uint16_t *) ptr);
} else {
return *(uint16_t *) ptr;
}
case 4: if (layout->reverse_byte_order) {
- return __bswap_32(*(uint32_t *) ptr);
+ return bswap_32(*(uint32_t *) ptr);
} else {
return *(uint32_t *) ptr;
}
case 8: if (layout->reverse_byte_order) {
- return __bswap_64(*(uint64_t *) ptr);
+ return bswap_64(*(uint64_t *) ptr);
} else {
return *(uint64_t *) ptr;
}
}
static
-int get_crash_layout(struct lttng_crash_layout *layout, int fd)
+int get_crash_layout(struct lttng_crash_layout *layout, int fd,
+ const char *input_file)
{
char *map;
int ret = 0, unmapret;
const struct crash_abi_unknown *abi;
uint16_t endian;
enum lttng_crash_type layout_type;
+ struct stat stat;
+ ret = fstat(fd, &stat);
+ if (ret < 0) {
+ PERROR("Failed to fstat '%s'", input_file);
+ return -1;
+ }
+ if (stat.st_size < RB_CRASH_DUMP_ABI_LEN) {
+ ERR("File '%s' truncated: file length of %" PRIi64
+ " bytes does not meet the minimal expected "
+ "length of %d bytes",
+ input_file, (int64_t) stat.st_size,
+ RB_CRASH_DUMP_ABI_LEN);
+ return -1;
+ }
map = mmap(NULL, RB_CRASH_DUMP_ABI_LEN, PROT_READ, MAP_PRIVATE,
fd, 0);
if (map == MAP_FAILED) {
return -EINVAL;
}
- DBG("Copy crash subbuffer at offset %lu", offset);
+ DBG("Copy crash subbuffer at offset %" PRIu64, offset);
sbidx = subbuf_index(offset, buf_size, subbuf_size);
/*
subbuf_ptr + layout->offset.packet_size,
layout->length.packet_size);
if (layout->reverse_byte_order) {
- packet_size = __bswap_64(packet_size);
+ packet_size = bswap_64(packet_size);
}
packet_size /= CHAR_BIT;
} else {
*/
patch_size = committed * CHAR_BIT;
if (layout->reverse_byte_order) {
- patch_size = __bswap_64(patch_size);
+ patch_size = bswap_64(patch_size);
}
if (layout->length.content_size) {
memcpy(subbuf_ptr + layout->offset.content_size,
}
/* Query the crash ABI layout */
- ret = get_crash_layout(&layout, fd_src);
+ ret = get_crash_layout(&layout, fd_src, input_file);
if (ret) {
goto close_src;
}
DIR *dir;
int dir_fd, ret = 0, closeret;
struct dirent *entry;
+ size_t path_len;
int has_warning = 0;
/* Open directory */
PERROR("Cannot open '%s' path", input_path);
return -1;
}
+
+ path_len = strlen(input_path);
+
dir_fd = dirfd(dir);
if (dir_fd < 0) {
PERROR("dirfd");
}
while ((entry = readdir(dir))) {
+ struct stat st;
+ size_t name_len;
+ char filename[PATH_MAX];
+
if (!strcmp(entry->d_name, ".")
|| !strcmp(entry->d_name, "..")) {
continue;
}
- switch (entry->d_type) {
- case DT_DIR:
- {
+
+ name_len = strlen(entry->d_name);
+ if (path_len + name_len + 2 > sizeof(filename)) {
+ ERR("Failed to remove file: path name too long (%s/%s)",
+ input_path, entry->d_name);
+ continue;
+ }
+
+ if (snprintf(filename, sizeof(filename), "%s/%s",
+ input_path, entry->d_name) < 0) {
+ ERR("Failed to format path.");
+ continue;
+ }
+
+ if (stat(filename, &st)) {
+ PERROR("stat");
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
char output_subpath[PATH_MAX];
char input_subpath[PATH_MAX];
if (ret) {
has_warning = 1;
}
- break;
- }
- case DT_REG:
+ } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
if (!strcmp(entry->d_name, "metadata")) {
ret = extract_one_trace(output_path,
input_path);
has_warning = 1;
}
}
- /* Ignore other files */
- break;
- default:
+ } else {
has_warning = 1;
goto end;
}
}
static
-int delete_trace(const char *trace_path)
+int delete_dir_recursive(const char *path)
{
- DIR *trace_dir;
- int trace_dir_fd, ret = 0, closeret;
+ DIR *dir;
+ int dir_fd, ret = 0, closeret;
+ size_t path_len;
struct dirent *entry;
/* Open trace directory */
- trace_dir = opendir(trace_path);
- if (!trace_dir) {
- PERROR("Cannot open '%s' path", trace_path);
- return -1;
+ dir = opendir(path);
+ if (!dir) {
+ PERROR("Cannot open '%s' path", path);
+ ret = -errno;
+ goto end_no_closedir;
}
- trace_dir_fd = dirfd(trace_dir);
- if (trace_dir_fd < 0) {
+
+ path_len = strlen(path);
+
+ dir_fd = dirfd(dir);
+ if (dir_fd < 0) {
PERROR("dirfd");
- return -1;
+ ret = -errno;
+ goto end;
}
- while ((entry = readdir(trace_dir))) {
+ while ((entry = readdir(dir))) {
+ struct stat st;
+ size_t name_len;
+ char filename[PATH_MAX];
+
if (!strcmp(entry->d_name, ".")
|| !strcmp(entry->d_name, "..")) {
continue;
}
- switch (entry->d_type) {
- case DT_DIR:
- unlinkat(trace_dir_fd, entry->d_name, AT_REMOVEDIR);
- break;
- case DT_REG:
- unlinkat(trace_dir_fd, entry->d_name, 0);
- break;
- default:
+
+ name_len = strlen(entry->d_name);
+ if (path_len + name_len + 2 > sizeof(filename)) {
+ ERR("Failed to remove file: path name too long (%s/%s)",
+ path, entry->d_name);
+ continue;
+ }
+
+ if (snprintf(filename, sizeof(filename), "%s/%s",
+ path, entry->d_name) < 0) {
+ ERR("Failed to format path.");
+ continue;
+ }
+
+ if (stat(filename, &st)) {
+ PERROR("stat");
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ char *subpath = zmalloc(PATH_MAX);
+
+ if (!subpath) {
+ PERROR("zmalloc path");
+ ret = -1;
+ goto end;
+ }
+ strncpy(subpath, path, PATH_MAX);
+ subpath[PATH_MAX - 1] = '\0';
+ strncat(subpath, "/",
+ PATH_MAX - strlen(subpath) - 1);
+ strncat(subpath, entry->d_name,
+ PATH_MAX - strlen(subpath) - 1);
+
+ ret = delete_dir_recursive(subpath);
+ free(subpath);
+ if (ret) {
+ /* Error occurred, abort traversal. */
+ goto end;
+ }
+ } else if (S_ISREG(st.st_mode)) {
+ ret = unlinkat(dir_fd, entry->d_name, 0);
+ if (ret) {
+ PERROR("Unlinking '%s'", entry->d_name);
+ goto end;
+ }
+ } else {
ret = -EINVAL;
goto end;
}
}
end:
- closeret = closedir(trace_dir);
+ if (!ret) {
+ ret = rmdir(path);
+ if (ret) {
+ PERROR("rmdir '%s'", path);
+ }
+ }
+ closeret = closedir(dir);
if (closeret) {
PERROR("closedir");
}
- if (!ret) {
- ret = rmdir(trace_path);
- }
+end_no_closedir:
return ret;
}
static
-int view_trace(const char *viewer_path, const char *trace_path)
+int view_trace(const char *trace_path, char *viewer_path)
{
pid_t pid;
/* Child */
int ret;
- ret = execlp(viewer_path, viewer_path,
- trace_path, (char *) NULL);
+ ret = spawn_viewer(trace_path, viewer_path, false);
if (ret) {
- PERROR("execlp");
exit(EXIT_FAILURE);
}
- exit(EXIT_SUCCESS); /* Never reached */
+ /* Never reached */
+ exit(EXIT_SUCCESS);
}
return 0;
}
*/
int main(int argc, char *argv[])
{
- int ret, has_warning = 0;
+ int ret;
+ bool has_warning = false;
const char *output_path = NULL;
char tmppath[] = "/tmp/lttng-crash-XXXXXX";
ret = parse_args(argc, argv);
if (ret > 0) {
- exit(EXIT_SUCCESS);
+ goto end;
} else if (ret < 0) {
- exit(EXIT_FAILURE);
+ has_warning = true;
+ goto end;
}
if (opt_output_path) {
ret = mkdir(output_path, S_IRWXU | S_IRWXG);
if (ret) {
PERROR("mkdir");
- exit(EXIT_FAILURE);
+ has_warning = true;
+ goto end;
}
} else {
output_path = mkdtemp(tmppath);
if (!output_path) {
PERROR("mkdtemp");
- exit(EXIT_FAILURE);
+ has_warning = true;
+ goto end;
}
}
ret = extract_trace_recursive(output_path, input_path);
if (ret < 0) {
- exit(EXIT_FAILURE);
+ has_warning = true;
+ goto end;
} else if (ret > 0) {
- has_warning = 1;
+ /* extract_trace_recursive reported a warning. */
+ has_warning = true;
}
if (!opt_output_path) {
/* View trace */
- ret = view_trace(opt_viewer_path, output_path);
- if (ret)
- has_warning = 1;
-
+ ret = view_trace(output_path, opt_viewer_path);
+ if (ret) {
+ has_warning = true;
+ }
/* unlink temporary trace */
- ret = delete_trace(output_path);
- if (ret)
- has_warning = 1;
+ ret = delete_dir_recursive(output_path);
+ if (ret) {
+ has_warning = true;
+ }
}
- if (has_warning)
- exit(EXIT_FAILURE);
- exit(EXIT_SUCCESS);
+end:
+ exit(has_warning ? EXIT_FAILURE : EXIT_SUCCESS);
}