| // Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Implementation of bootstat_log(), part of the Chromium OS 'bootstat' |
| // facility. |
| |
| #include <assert.h> |
| #include <libgen.h> |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/fcntl.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <rootdev/rootdev.h> |
| |
| #include "bootstat.h" |
| #include "bootstat_test.h" |
| |
| static const char kBootstageMarkFile[] = "/sys/kernel/debug/bootstage/mark"; |
| |
| // |
| // Default path to directory where output statistics will be stored. |
| // |
| static const char kDefaultOutputDirectoryName[] = "/tmp"; |
| |
| // |
| // Paths to the statistics files we snapshot as part of the data to |
| // be logged. |
| // |
| static const char kDefaultUptimeStatisticsFileName[] = "/proc/uptime"; |
| |
| static const char* output_directory_name = kDefaultOutputDirectoryName; |
| static const char* uptime_statistics_file_name = |
| kDefaultUptimeStatisticsFileName; |
| |
| static const char* disk_statistics_file_name_for_test = NULL; |
| |
| // Stores the path representing the stats file for the root disk in |
| // |stats_path| of length |len|. |
| // Returns |stats_path| on success, NULL on failure. |
| static char* get_disk_statistics_file_name(char* stats_path, size_t len) { |
| char boot_path[PATH_MAX]; |
| int ret = rootdev(boot_path, sizeof(boot_path), |
| true, // Do full resolution. |
| false); // Do not remove partition number. |
| if (ret < 0) { |
| return NULL; |
| } |
| |
| // The general idea is to use the the root device's sysfs entry to |
| // get the path to the root disk's sysfs entry. |
| // Example: |
| // - rootdev() returns "/dev/sda3" |
| // - Use /sys/class/block/sda3/../ to get to root disk (sda) sysfs entry. |
| // This is because /sys/class/block/sda3 is a symlink that maps to: |
| // /sys/devices/pci.../.../ata./host./target.../.../block/sda/sda3 |
| const char* root_device_name = basename(boot_path); |
| if (!root_device_name) { |
| return NULL; |
| } |
| |
| ret = snprintf(stats_path, len, |
| "/sys/class/block/%s/../stat", root_device_name); |
| if (ret >= PATH_MAX || ret < 0) { |
| return NULL; |
| } |
| return stats_path; |
| } |
| |
| static void append_logdata(const char* input_path, |
| const char* output_name_prefix, |
| const char* event_name) |
| { |
| if (!input_path || !output_name_prefix || !event_name) |
| return; |
| const mode_t kFileCreationMode = |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; |
| char output_path[PATH_MAX]; |
| char buffer[256]; |
| ssize_t num_read; |
| int ifd, ofd; |
| int output_path_len; |
| |
| ifd = open(input_path, O_RDONLY); |
| if (ifd < 0) { |
| return; |
| } |
| |
| // |
| // For those not up on the more esoteric features of printf |
| // formats: the "%.*s" format is used to truncate the event name |
| // to the proper number of characters.. |
| // |
| // The assertion for output_path overflow should only be able to |
| // fail if output_directory_name is changed from its default, |
| // which can only happen in unit tests, and then only in the event |
| // of a serious test bug. |
| // |
| output_path_len = snprintf(output_path, sizeof(output_path), "%s/%s-%.*s", |
| output_directory_name, |
| output_name_prefix, |
| BOOTSTAT_MAX_EVENT_LEN - 1, event_name); |
| // output_path_len is unused when the assert() is disabled. Prevent the |
| // warning. |
| (void)output_path_len; |
| assert(output_path_len < sizeof(output_path)); |
| ofd = open(output_path, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, |
| kFileCreationMode); |
| if (ofd < 0) { |
| (void)close(ifd); |
| return; |
| } |
| |
| while ((num_read = read(ifd, buffer, sizeof(buffer))) > 0) { |
| ssize_t num_written = write(ofd, buffer, num_read); |
| if (num_written != num_read) |
| break; |
| } |
| (void)close(ofd); |
| (void)close(ifd); |
| } |
| |
| static void write_mark(const char *event_name) |
| { |
| ssize_t ret __attribute__((unused)); |
| int fd = open(kBootstageMarkFile, O_WRONLY); |
| |
| if (fd < 0) { |
| return; |
| } |
| |
| /* |
| * It's not necessary to check the return value, |
| * but the compiler will generate a warning if we don't. |
| */ |
| ret = write(fd, event_name, strlen(event_name)); |
| close(fd); |
| } |
| |
| void bootstat_log(const char* event_name) |
| { |
| const char* disk_statistics_file_name; |
| char stats_path[PATH_MAX]; |
| write_mark(event_name); |
| append_logdata(uptime_statistics_file_name, "uptime", event_name); |
| if (disk_statistics_file_name_for_test) { |
| disk_statistics_file_name = disk_statistics_file_name_for_test; |
| } else { |
| disk_statistics_file_name = get_disk_statistics_file_name( |
| stats_path, sizeof(stats_path)); |
| } |
| append_logdata(disk_statistics_file_name, "disk", event_name); |
| } |
| |
| void bootstat_set_output_directory_for_test(const char* dirname) |
| { |
| if (dirname != NULL) |
| output_directory_name = dirname; |
| else |
| output_directory_name = kDefaultOutputDirectoryName; |
| } |
| |
| void bootstat_set_uptime_file_name_for_test(const char* filename) |
| { |
| if (filename != NULL) |
| uptime_statistics_file_name = filename; |
| else |
| uptime_statistics_file_name = kDefaultUptimeStatisticsFileName; |
| } |
| |
| void bootstat_set_disk_file_name_for_test(const char* filename) |
| { |
| disk_statistics_file_name_for_test = filename; |
| } |