| /* |
| * 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/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; |
| 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)) { |
| 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", ×tamp->sec, ×tamp->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; |
| } |