blob: e58f5ecb8cf09a6701837fcb7739a02708827c0e [file] [log] [blame]
/*
* hostapd / Interface steering
* Copyright (c) 2015 Google, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include "common.h"
#include "common/defs.h"
#include "common/hw_features_common.h"
#include "common/ieee802_11_defs.h"
#include "hostapd.h"
#include "steering.h"
static int get_timestamp_filename(const u8 *mac,
logged_request_type type,
steering_path_type path_type, char *buf,
size_t len) {
char *path = (path_type == STEERING_PATH ?
steering_timestamp_path : request_logging_path);
if (path == NULL) {
return 0;
}
if (os_snprintf(buf, len, "%s/" COMPACT_MACSTR ".%d", path,
MAC2STR(mac), type) < 0) {
wpa_printf(MSG_ERROR, "os_snprintf couldn't format filename: %s",
strerror(errno));
return 0;
}
return 1;
}
int write_timestamp_file(const u8 *mac,
const struct hostapd_data *hapd,
logged_request_type type,
const struct os_reltime *timestamp) {
FILE *f;
char filename[1024], tmp_filename[1024];
int success = 0;
if (!get_timestamp_filename(mac, type, LOGGING_PATH, filename,
sizeof(filename))) {
return 0;
}
/* Create a temporary filename to prevent multiple interfaces on the same band
* from touching each others' writes.
*/
if (os_snprintf(tmp_filename, sizeof(tmp_filename), "%s%s", filename,
os_strrchr(hapd->iface->config_fname, '.')) < 0) {
wpa_printf(MSG_ERROR, "os_snprintf couldn't format temp filename: %s",
strerror(errno));
return 0;
}
if ((f = fopen(tmp_filename, "w")) == NULL) {
wpa_printf(MSG_ERROR, "fopen(%s) for write: %s", tmp_filename,
strerror(errno));
return 0;
}
if (timestamp) {
if (fprintf(f, "%d %d", timestamp->sec, timestamp->usec) < 0) {
wpa_printf(MSG_ERROR, "fprintf to %s: %s", tmp_filename, strerror(errno));
} else {
success = 1;
}
}
if (fclose(f) == EOF) {
wpa_printf(MSG_ERROR, "fclose(%s): %s", tmp_filename, strerror(errno));
return 0;
}
if (rename(tmp_filename, filename) != 0) {
wpa_printf(MSG_ERROR, "rename(%s, %s): %s", tmp_filename, filename,
strerror(errno));
return 0;
}
return success;
}
int maybe_write_timestamp_file(const u8 *mac,
const struct hostapd_data *hapd,
logged_request_type type) {
struct os_reltime now, prev_logged_timestamp, new_timestamp;
int bandsteering_delay_seconds;
if (!request_logging_path) {
return 0;
}
os_get_reltime(&now);
if (garbage_collect_timestamp_files() == -1) {
wpa_printf(MSG_ERROR,
"Garbage collecting steering timestamp files failed: %s",
strerror(errno));
return 0;
}
if (!read_timestamp_file(mac, type, LOGGING_PATH, &prev_logged_timestamp) ||
os_reltime_expired(&now, &prev_logged_timestamp,
BANDSTEERING_EXPIRATION_SECONDS)) {
bandsteering_delay_seconds = BANDSTEERING_DELAY_SECONDS;
if (experiment("WifiShortBandsteeringDelay")) {
bandsteering_delay_seconds = 1;
}
new_timestamp.sec = now.sec + bandsteering_delay_seconds;
new_timestamp.usec = now.usec;
if (!write_timestamp_file(mac, hapd, type, &new_timestamp)) {
wpa_printf(MSG_ERROR, "Failed to write timestamp file.");
return 0;
} else {
wpa_printf(MSG_INFO, "Set steering timestamp for " MACSTR " (type=%d)",
MAC2STR(mac), type);
return 1;
}
}
}
int read_timestamp_file(const u8 *mac,
logged_request_type type,
steering_path_type path_type,
struct os_reltime *timestamp) {
FILE *f;
char filename[1024];
int success = 1;
struct stat st;
os_time_t sec = 0, usec = 0;
if (!get_timestamp_filename(mac, type, path_type, filename,
sizeof(filename))) {
return 0;
}
if (stat(filename, &st) == -1) {
return 0;
}
f = fopen(filename, "r");
if (f == NULL) {
wpa_printf(MSG_ERROR, "open(%s) for read: %s", filename, strerror(errno));
return 0;
}
if (timestamp) {
if (fscanf(f, "%d %d", &timestamp->sec, &timestamp->usec) != 2) {
wpa_printf(MSG_ERROR, "fscanf from %s: %s", filename, strerror(errno));
success = 0;
}
}
if (fclose(f) == EOF) {
wpa_printf(MSG_ERROR, "fclose(%s): %s", filename, strerror(errno));
return 0;
}
return success;
}
int delete_timestamp_file(const u8 *mac,
logged_request_type type,
steering_path_type path_type) {
char filename[1024];
struct stat st;
if (!get_timestamp_filename(mac, type, path_type, filename,
sizeof(filename))) {
return 0;
}
if (stat(filename, &st) == -1) {
return 1;
}
if (unlink(filename) == -1) {
wpa_printf(MSG_ERROR, "unlink(%s): %s", filename, strerror(errno));
return 0;
}
return 1;
}
int file_ctime_lt(const struct dirent **a, const struct dirent **b) {
struct stat astat, bstat;
/* If we can't stat both of the files, give up and say they're equivalent. */
if (stat((*a)->d_name, &astat) == -1 || stat((*b)->d_name, &bstat) == -1) {
return 0;
}
return astat.st_ctime - bstat.st_ctime;
}
/* Only garbage collect LOG_PROBE files. */
int should_garbage_collect(const struct dirent *name) {
char *extension = os_strrchr(name->d_name, '.');
char buf[4];
os_snprintf(buf, sizeof(buf), ".%d", LOG_PROBE);
return os_strncmp(extension, buf, sizeof(buf)) == 0;
}
int garbage_collect_timestamp_files(const char *path) {
int num_timestamp_files = 0, num_timestamp_files_deleted = 0, i = 0;
struct dirent **namelist;
char original_cwd[1024];
char *filename;
int error = 0;
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
wpa_printf(MSG_ERROR, "getcwd(): %s", strerror(errno));
return -1;
}
if (chdir(request_logging_path) == -1) {
if (errno != ENOENT) {
wpa_printf(MSG_ERROR, "chdir(%s): %s",
request_logging_path, strerror(errno));
return -1;
} else {
return 0;
}
}
num_timestamp_files = scandir(request_logging_path, &namelist,
should_garbage_collect, file_ctime_lt);
for (i = 0; i < num_timestamp_files; ++i) {
if (MAX_STEERING_TIMESTAMP_FILES <
/* The -2 is because scandir includes "." and "..". */
(num_timestamp_files - 2) - num_timestamp_files_deleted) {
filename = namelist[i]->d_name;
if (filename[0] != '.' && !error) {
if (unlink(filename) == -1) {
wpa_printf(MSG_ERROR, "unlink(%s): %s", filename, strerror(errno));
error = 1;
} else {
++num_timestamp_files_deleted;
}
}
}
os_free(namelist[i]);
}
os_free(namelist);
if (chdir(original_cwd) == -1) {
wpa_printf(MSG_ERROR, "chdir(%s): %s", original_cwd, strerror(errno));
return -1;
}
return error ? -1 : num_timestamp_files_deleted;
}