blob: 84c5f0eb55ee149c490b628463e043280f730a68 [file] [log] [blame]
// 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;
}