blob: 53cfc30ffda2895d443a7974f3b5a87824340aa4 [file] [log] [blame]
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2016-2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <ell/ell.h>
#include "src/iwd.h"
#include "src/module.h"
#include "src/storage.h"
#include "src/common.h"
#include "src/network.h"
#include "src/dbus.h"
#include "src/knownnetworks.h"
#include "src/scan.h"
#include "src/util.h"
#include "src/watchlist.h"
static struct l_queue *known_networks;
static size_t num_known_hidden_networks;
static struct l_dir_watch *storage_dir_watch;
static struct watchlist known_network_watches;
static struct l_settings *known_freqs;
static void network_info_free(void *data)
{
struct network_info *network = data;
l_queue_destroy(network->known_frequencies, l_free);
network->ops->free(network);
}
static int connected_time_compare(const void *a, const void *b, void *user_data)
{
const struct network_info *ni_a = a;
const struct network_info *ni_b = b;
if (l_time_after(ni_a->connected_time, ni_b->connected_time))
return -1;
else if (l_time_before(ni_a->connected_time, ni_b->connected_time))
return 1;
return 0;
}
static const char *known_network_get_path(const struct network_info *network)
{
static char path[256];
unsigned int pos = 0, i;
L_WARN_ON((pos = snprintf(path, sizeof(path), "%s/",
IWD_BASE_PATH)) >= (int) sizeof(path));
for (i = 0; network->ssid[i] && pos < sizeof(path); i++)
pos += snprintf(path + pos, sizeof(path) - pos, "%02x",
network->ssid[i]);
snprintf(path + pos, sizeof(path) - pos, "_%s",
security_to_str(network->type));
path[sizeof(path) - 1] = '\0';
return path;
}
/*
* Finds the position n of this network_info in the list of known networks
* sorted by connected_time. E.g. an offset of 0 means the most recently
* used network. Only networks with seen_count > 0 are considered. E.g.
* only networks that appear in scan results on at least one wifi card.
*
* Returns -ENOENT if the entry couldn't be found.
*/
int known_network_offset(const struct network_info *target)
{
const struct l_queue_entry *entry;
const struct network_info *info;
int n = 0;
for (entry = l_queue_get_entries(known_networks); entry;
entry = entry->next) {
info = entry->data;
if (target == info)
return n;
if (info->seen_count)
n += 1;
}
return -ENOENT;
}
static void known_network_register_dbus(struct network_info *network)
{
const char *path = known_network_get_path(network);
if (!l_dbus_object_add_interface(dbus_get_bus(), path,
IWD_KNOWN_NETWORK_INTERFACE, network))
l_info("Unable to register %s interface",
IWD_KNOWN_NETWORK_INTERFACE);
if (!l_dbus_object_add_interface(dbus_get_bus(), path,
L_DBUS_INTERFACE_PROPERTIES, network))
l_info("Unable to register %s interface",
L_DBUS_INTERFACE_PROPERTIES);
}
static void known_network_set_autoconnect(struct network_info *network,
bool autoconnect)
{
if (network->is_autoconnectable == autoconnect)
return;
network->is_autoconnectable = autoconnect;
l_dbus_property_changed(dbus_get_bus(), known_network_get_path(network),
IWD_KNOWN_NETWORK_INTERFACE, "AutoConnect");
}
static int known_network_touch(struct network_info *info)
{
return storage_network_touch(info->type, info->ssid);
}
static struct l_settings *known_network_open(struct network_info *info)
{
return storage_network_open(info->type, info->ssid);
}
static void known_network_sync(struct network_info *info,
struct l_settings *settings)
{
storage_network_sync(info->type, info->ssid, settings);
}
static void known_network_remove(struct network_info *info)
{
storage_network_remove(info->type, info->ssid);
}
static void known_network_free(struct network_info *info)
{
l_free(info);
}
static const char *known_network_get_name(const struct network_info *info)
{
return info->ssid;
}
static const char *known_network_get_type(const struct network_info *info)
{
return security_to_str(info->type);
}
static char *known_network_get_file_path(const struct network_info *info)
{
return storage_get_network_file_path(info->type, info->ssid);
}
static struct network_info_ops known_network_ops = {
.open = known_network_open,
.touch = known_network_touch,
.sync = known_network_sync,
.remove = known_network_remove,
.free = known_network_free,
.get_path = known_network_get_path,
.get_name = known_network_get_name,
.get_type = known_network_get_type,
.get_file_path = known_network_get_file_path,
};
struct l_settings *network_info_open_settings(struct network_info *info)
{
return info->ops->open(info);
}
int network_info_touch(struct network_info *info)
{
return info->ops->touch(info);
}
const char *network_info_get_path(const struct network_info *info)
{
return info->ops->get_path(info);
}
const char *network_info_get_name(const struct network_info *info)
{
return info->ops->get_name(info);
}
const char *network_info_get_type(const struct network_info *info)
{
return info->ops->get_type(info);
}
const struct iovec *network_info_get_extra_ies(const struct network_info *info,
struct scan_bss *bss,
size_t *num_elems)
{
if (!info || !info->ops->get_extra_ies)
return NULL;
return info->ops->get_extra_ies(info, bss, num_elems);
}
const uint8_t *network_info_get_uuid(struct network_info *info)
{
char *file_path;
char *to_hash;
/*
* 16 bytes of randomness. Since we only care about a unique value there
* is no need to use any special pre-defined namespace.
*/
static const uint8_t nsid[16] = {
0xfd, 0x88, 0x6f, 0x1e, 0xdf, 0x02, 0xd7, 0x8b,
0xc4, 0x90, 0x30, 0x59, 0x73, 0x8a, 0x86, 0x0d
};
if (info->has_uuid)
return info->uuid;
file_path = info->ops->get_file_path(info);
/*
* This will generate a UUID based on file path and mtime. This
* is done so we can get a different UUID if the network has
* been forgotten.
*/
to_hash = l_strdup_printf("%s_%" PRIu64, file_path,
info->connected_time);
l_uuid_v5(nsid, to_hash, strlen(to_hash), info->uuid);
l_free(to_hash);
l_free(file_path);
info->has_uuid = true;
return info->uuid;
}
void network_info_set_uuid(struct network_info *info, const uint8_t *uuid)
{
memcpy(info->uuid, uuid, 16);
info->has_uuid = true;
}
struct scan_freq_set *network_info_get_roam_frequencies(
const struct network_info *info,
uint32_t current_freq,
uint8_t max)
{
struct scan_freq_set *freqs;
const struct l_queue_entry *entry;
freqs = scan_freq_set_new();
for (entry = l_queue_get_entries(info->known_frequencies); entry && max;
entry = entry->next) {
struct known_frequency *kn = entry->data;
if (kn->frequency == current_freq)
continue;
scan_freq_set_add(freqs, kn->frequency);
max--;
}
if (scan_freq_set_isempty(freqs)) {
scan_freq_set_free(freqs);
return NULL;
}
return freqs;
}
bool network_info_match_hessid(const struct network_info *info,
const uint8_t *hessid)
{
if (!info->ops->match_hessid)
return false;
return info->ops->match_hessid(info, hessid);
}
const uint8_t *network_info_match_roaming_consortium(
const struct network_info *info,
const uint8_t *rc,
size_t rc_len,
size_t *rc_len_out)
{
if (!info->ops->match_roaming_consortium)
return NULL;
return info->ops->match_roaming_consortium(info, rc, rc_len,
rc_len_out);
}
bool network_info_match_nai_realm(const struct network_info *info,
const char **nai_realms)
{
if (!info->ops->match_nai_realms)
return false;
return info->ops->match_nai_realms(info, nai_realms);
}
void known_network_set_connected_time(struct network_info *network,
uint64_t connected_time)
{
if (network->connected_time == connected_time)
return;
network->connected_time = connected_time;
l_dbus_property_changed(dbus_get_bus(),
known_network_get_path(network),
IWD_KNOWN_NETWORK_INTERFACE,
"LastConnectedTime");
l_queue_remove(known_networks, network);
l_queue_insert(known_networks, network, connected_time_compare, NULL);
}
void known_network_update(struct network_info *network,
struct l_settings *settings)
{
bool is_hidden;
bool is_autoconnectable;
if (!l_settings_get_bool(settings, "Settings", "Hidden", &is_hidden))
is_hidden = false;
if (network->is_hidden != is_hidden) {
if (network->is_hidden && !is_hidden)
num_known_hidden_networks--;
else if (!network->is_hidden && is_hidden)
num_known_hidden_networks++;
l_dbus_property_changed(dbus_get_bus(),
known_network_get_path(network),
IWD_KNOWN_NETWORK_INTERFACE,
"Hidden");
}
network->is_hidden = is_hidden;
if (!l_settings_get_bool(settings, "Settings", "AutoConnect",
&is_autoconnectable)) {
/* If no entry, default to AutoConnectable=True */
is_autoconnectable = true;
/* Try legacy property name just in case */
if (l_settings_get_bool(settings, "Settings", "Autoconnect",
&is_autoconnectable))
l_warn("Autoconnect setting is deprecated, use"
" AutoConnect instead");
}
known_network_set_autoconnect(network, is_autoconnectable);
}
bool known_networks_foreach(known_networks_foreach_func_t function,
void *user_data)
{
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(known_networks); entry;
entry = entry->next)
if (!function(entry->data, user_data))
break;
return !entry;
}
bool known_networks_has_hidden(void)
{
return num_known_hidden_networks ? true : false;
}
static bool network_info_match(const void *a, const void *b)
{
const struct network_info *ni_a = a;
const struct network_info *ni_b = b;
if (ni_a->type != ni_b->type)
return false;
if (strcmp(ni_a->ssid, ni_b->ssid))
return false;
return true;
}
struct network_info *known_networks_find(const char *ssid,
enum security security)
{
struct network_info query;
query.type = security;
strcpy(query.ssid, ssid);
return l_queue_find(known_networks, network_info_match, &query);
}
struct scan_freq_set *known_networks_get_recent_frequencies(
uint8_t num_networks_tosearch)
{
/*
* This search function assumes that the known networks are always
* sorted by the last connection time with the most recent ones being on
* top. Therefore, we just need to get the top NUM of networks from the
* list.
*/
const struct l_queue_entry *network_entry;
const struct l_queue_entry *freq_entry;
struct scan_freq_set *set;
if (!num_networks_tosearch)
return NULL;
set = scan_freq_set_new();
for (network_entry = l_queue_get_entries(known_networks);
network_entry && num_networks_tosearch;
network_entry = network_entry->next,
num_networks_tosearch--) {
const struct network_info *network = network_entry->data;
for (freq_entry = l_queue_get_entries(
network->known_frequencies);
freq_entry; freq_entry = freq_entry->next) {
const struct known_frequency *known_freq =
freq_entry->data;
scan_freq_set_add(set, known_freq->frequency);
}
}
return set;
}
static bool known_frequency_match(const void *a, const void *b)
{
const struct known_frequency *known_freq = a;
const uint32_t *frequency = b;
return known_freq->frequency == *frequency;
}
/*
* Adds a frequency to the 'known' set of frequencies that this network
* operates on. The list is sorted according to most-recently seen
*/
int known_network_add_frequency(struct network_info *info, uint32_t frequency)
{
struct known_frequency *known_freq;
if (!info->known_frequencies)
info->known_frequencies = l_queue_new();
known_freq = l_queue_remove_if(info->known_frequencies,
known_frequency_match, &frequency);
if (!known_freq) {
known_freq = l_new(struct known_frequency, 1);
known_freq->frequency = frequency;
}
l_queue_push_head(info->known_frequencies, known_freq);
return 0;
}
static struct l_dbus_message *known_network_forget(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct network_info *network = user_data;
struct l_dbus_message *reply;
/* Other actions taken care of by the filesystem watch callback */
network->ops->remove(network);
reply = l_dbus_message_new_method_return(message);
l_dbus_message_set_arguments(reply, "");
return reply;
}
static bool known_network_property_get_name(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network_info *network = user_data;
l_dbus_message_builder_append_basic(builder, 's',
network_info_get_name(network));
return true;
}
static bool known_network_property_get_type(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network_info *network = user_data;
l_dbus_message_builder_append_basic(builder, 's',
network_info_get_type(network));
return true;
}
static bool known_network_property_get_hidden(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network_info *network = user_data;
bool is_hidden = network->is_hidden;
l_dbus_message_builder_append_basic(builder, 'b', &is_hidden);
return true;
}
static bool known_network_property_get_autoconnect(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network_info *network = user_data;
bool autoconnect = network->is_autoconnectable;
l_dbus_message_builder_append_basic(builder, 'b', &autoconnect);
return true;
}
static struct l_dbus_message *known_network_property_set_autoconnect(
struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_iter *new_value,
l_dbus_property_complete_cb_t complete,
void *user_data)
{
struct network_info *network = user_data;
struct l_settings *settings;
bool autoconnect;
if (!l_dbus_message_iter_get_variant(new_value, "b", &autoconnect))
return dbus_error_invalid_args(message);
if (network->is_autoconnectable == autoconnect)
return l_dbus_message_new_method_return(message);
settings = network->ops->open(network);
if (!settings)
return dbus_error_failed(message);
l_settings_set_bool(settings, "Settings", "AutoConnect", autoconnect);
network->ops->sync(network, settings);
l_settings_free(settings);
return l_dbus_message_new_method_return(message);
}
static bool known_network_property_get_last_connected(struct l_dbus *dbus,
struct l_dbus_message *message,
struct l_dbus_message_builder *builder,
void *user_data)
{
struct network_info *network = user_data;
char datestr[64];
struct tm tm;
time_t seconds = l_time_to_secs(network->connected_time);
if (network->connected_time == 0)
return false;
gmtime_r(&seconds, &tm);
if (!strftime(datestr, sizeof(datestr), "%FT%TZ", &tm))
return false;
l_dbus_message_builder_append_basic(builder, 's', datestr);
return true;
}
static void setup_known_network_interface(struct l_dbus_interface *interface)
{
l_dbus_interface_method(interface, "Forget", 0,
known_network_forget, "", "");
l_dbus_interface_property(interface, "Name", 0, "s",
known_network_property_get_name, NULL);
l_dbus_interface_property(interface, "Type", 0, "s",
known_network_property_get_type, NULL);
l_dbus_interface_property(interface, "Hidden", 0, "b",
known_network_property_get_hidden,
NULL);
l_dbus_interface_property(interface, "AutoConnect", 0, "b",
known_network_property_get_autoconnect,
known_network_property_set_autoconnect);
l_dbus_interface_property(interface, "LastConnectedTime", 0, "s",
known_network_property_get_last_connected,
NULL);
}
void known_networks_remove(struct network_info *network)
{
if (network->is_hidden)
num_known_hidden_networks--;
l_queue_remove(known_networks, network);
l_dbus_unregister_object(dbus_get_bus(),
known_network_get_path(network));
WATCHLIST_NOTIFY(&known_network_watches,
known_networks_watch_func_t,
KNOWN_NETWORKS_EVENT_REMOVED, network);
if (known_freqs && network->has_uuid) {
char uuid[37];
l_uuid_to_string(network->uuid, uuid, sizeof(uuid));
l_settings_remove_group(known_freqs, uuid);
storage_known_frequencies_sync(known_freqs);
}
network_info_free(network);
}
void known_networks_add(struct network_info *network)
{
l_queue_insert(known_networks, network, connected_time_compare, NULL);
known_network_register_dbus(network);
WATCHLIST_NOTIFY(&known_network_watches,
known_networks_watch_func_t,
KNOWN_NETWORKS_EVENT_ADDED, network);
}
static void known_network_new(const char *ssid, enum security security,
struct l_settings *settings,
uint64_t connected_time)
{
bool is_hidden;
bool is_autoconnectable;
struct network_info *network;
network = l_new(struct network_info, 1);
strcpy(network->ssid, ssid);
network->type = security;
network->connected_time = connected_time;
network->ops = &known_network_ops;
if (!l_settings_get_bool(settings, "Settings", "Hidden",
&is_hidden))
is_hidden = false;
if (!l_settings_get_bool(settings, "Settings", "AutoConnect",
&is_autoconnectable)) {
is_autoconnectable = true;
/* Try legacy property name just in case */
if (l_settings_get_bool(settings, "Settings", "Autoconnect",
&is_autoconnectable))
l_warn("Autoconnect setting is deprecated, use"
" AutoConnect instead");
}
if (is_hidden)
num_known_hidden_networks++;
network->is_hidden = is_hidden;
network->is_autoconnectable = is_autoconnectable;
known_networks_add(network);
}
static void known_networks_watch_cb(const char *filename,
enum l_dir_watch_event event,
void *user_data)
{
const char *ssid;
L_AUTO_FREE_VAR(char *, full_path) = NULL;
enum security security;
struct network_info *network_before;
struct l_settings *settings;
uint64_t connected_time;
/*
* Ignore notifications for the actual directory, we can't do
* anything about some of them anyway. Only react to
* notifications for files in the storage directory.
*/
if (!filename)
return;
ssid = storage_network_ssid_from_path(filename, &security);
if (!ssid)
return;
network_before = known_networks_find(ssid, security);
full_path = storage_get_network_file_path(security, ssid);
switch (event) {
case L_DIR_WATCH_EVENT_CREATED:
case L_DIR_WATCH_EVENT_REMOVED:
case L_DIR_WATCH_EVENT_MODIFIED:
/*
* For now treat all the operations the same. E.g. they may
* result in the removal of the network (file moved out, not
* readable or invalid) or the creation of a new network (file
* created, permissions granted, syntax fixed, etc.)
* so we always need to re-read the file.
*/
settings = storage_network_open(security, ssid);
if (settings) {
connected_time = l_path_get_mtime(full_path);
if (network_before) {
known_network_set_connected_time(network_before,
connected_time);
known_network_update(network_before, settings);
} else
known_network_new(ssid, security, settings,
connected_time);
} else if (network_before)
known_networks_remove(network_before);
l_settings_free(settings);
break;
case L_DIR_WATCH_EVENT_ACCESSED:
break;
case L_DIR_WATCH_EVENT_ATTRIB:
if (network_before) {
connected_time = l_path_get_mtime(full_path);
known_network_set_connected_time(network_before,
connected_time);
}
break;
}
}
static void known_networks_watch_destroy(void *user_data)
{
storage_dir_watch = NULL;
}
static struct l_queue *known_frequencies_from_string(char *freq_set_str)
{
struct l_queue *known_frequencies;
struct known_frequency *known_freq;
uint16_t t;
if (!freq_set_str)
return NULL;
if (*freq_set_str == '\0')
return NULL;
known_frequencies = l_queue_new();
while (*freq_set_str != '\0') {
errno = 0;
t = strtoul(freq_set_str, &freq_set_str, 10);
if (unlikely(errno == ERANGE || !t || t > 6000))
goto error;
known_freq = l_new(struct known_frequency, 1);
known_freq->frequency = t;
l_queue_push_tail(known_frequencies, known_freq);
}
if (l_queue_isempty(known_frequencies))
goto error;
return known_frequencies;
error:
l_queue_destroy(known_frequencies, l_free);
return NULL;
}
static void known_frequency_to_string(void *data, void *user_data)
{
struct known_frequency *known_freq = data;
struct l_string *str = user_data;
l_string_append_printf(str, " %u", known_freq->frequency);
}
static char *known_frequencies_to_string(struct l_queue *known_frequencies)
{
struct l_string *str;
str = l_string_new(100);
l_queue_foreach(known_frequencies, known_frequency_to_string, str);
return l_string_unwrap(str);
}
struct hotspot_search {
struct network_info *info;
const char *path;
};
static bool match_hotspot_path(const struct network_info *info, void *user_data)
{
struct hotspot_search *search = user_data;
char *path;
if (!info->is_hotspot)
return true;
path = info->ops->get_file_path(info);
if (!strcmp(path, search->path)) {
l_free(path);
search->info = (struct network_info *)info;
return false;
}
l_free(path);
return true;
}
static struct network_info *find_network_info_from_path(const char *path)
{
enum security security;
struct hotspot_search search;
const char *ssid = storage_network_ssid_from_path(path, &security);
if (ssid)
return known_networks_find(ssid, security);
search.info = NULL;
search.path = path;
/* Try hotspot */
known_networks_foreach(match_hotspot_path, &search);
return search.info;
}
static int known_network_frequencies_load(void)
{
char **groups;
struct l_queue *known_frequencies;
uint32_t i;
uint8_t uuid[16];
known_freqs = storage_known_frequencies_load();
if (!known_freqs) {
l_debug("No known frequency file found.");
return 0;
}
groups = l_settings_get_groups(known_freqs);
for (i = 0; groups[i]; i++) {
struct network_info *info;
char *freq_list;
const char *path = l_settings_get_value(known_freqs, groups[i],
"name");
if (!path)
continue;
info = find_network_info_from_path(path);
if (!info)
continue;
freq_list = l_settings_get_string(known_freqs, groups[i],
"list");
if (!freq_list)
continue;
known_frequencies = known_frequencies_from_string(freq_list);
if (!known_frequencies)
goto next;
if (!l_uuid_from_string(groups[i], uuid))
goto next;
network_info_set_uuid(info, uuid);
info->known_frequencies = known_frequencies;
next:
l_free(freq_list);
}
l_strv_free(groups);
return 0;
}
/*
* Syncs a single network_info frequency to the global frequency file
*/
void known_network_frequency_sync(struct network_info *info)
{
char *freq_list_str;
char *file_path;
char group[37];
if (!info->known_frequencies)
return;
if (!known_freqs)
known_freqs = l_settings_new();
freq_list_str = known_frequencies_to_string(info->known_frequencies);
file_path = info->ops->get_file_path(info);
l_uuid_to_string(network_info_get_uuid(info), group, sizeof(group));
l_settings_set_value(known_freqs, group, "name", file_path);
l_settings_set_value(known_freqs, group, "list", freq_list_str);
l_free(file_path);
l_free(freq_list_str);
storage_known_frequencies_sync(known_freqs);
}
uint32_t known_networks_watch_add(known_networks_watch_func_t func,
void *user_data,
known_networks_destroy_func_t destroy)
{
return watchlist_add(&known_network_watches, func, user_data, destroy);
}
void known_networks_watch_remove(uint32_t id)
{
watchlist_remove(&known_network_watches, id);
}
static int known_networks_init(void)
{
struct l_dbus *dbus = dbus_get_bus();
DIR *dir;
struct dirent *dirent;
L_AUTO_FREE_VAR(char *, storage_dir) = storage_get_path(NULL);
if (!l_dbus_register_interface(dbus, IWD_KNOWN_NETWORK_INTERFACE,
setup_known_network_interface,
NULL, false)) {
l_info("Unable to register %s interface",
IWD_KNOWN_NETWORK_INTERFACE);
return -EPERM;
}
dir = opendir(storage_dir);
if (!dir) {
l_info("Unable to open %s: %s", storage_dir, strerror(errno));
l_dbus_unregister_interface(dbus, IWD_KNOWN_NETWORK_INTERFACE);
return -ENOENT;
}
known_networks = l_queue_new();
while ((dirent = readdir(dir))) {
const char *ssid;
enum security security;
struct l_settings *settings;
uint64_t connected_time;
L_AUTO_FREE_VAR(char *, full_path) = NULL;
if (dirent->d_type != DT_REG && dirent->d_type != DT_LNK)
continue;
ssid = storage_network_ssid_from_path(dirent->d_name,
&security);
if (!ssid)
continue;
settings = storage_network_open(security, ssid);
full_path = storage_get_network_file_path(security, ssid);
if (settings) {
connected_time = l_path_get_mtime(full_path);
known_network_new(ssid, security, settings,
connected_time);
}
l_settings_free(settings);
}
closedir(dir);
storage_dir_watch = l_dir_watch_new(storage_dir,
known_networks_watch_cb, NULL,
known_networks_watch_destroy);
watchlist_init(&known_network_watches, NULL);
return 0;
}
static void known_networks_exit(void)
{
struct l_dbus *dbus = dbus_get_bus();
l_dir_watch_destroy(storage_dir_watch);
l_queue_destroy(known_networks, network_info_free);
known_networks = NULL;
l_dbus_unregister_interface(dbus, IWD_KNOWN_NETWORK_INTERFACE);
watchlist_destroy(&known_network_watches);
}
IWD_MODULE(known_networks, known_networks_init, known_networks_exit)
static void known_frequencies_exit(void)
{
l_settings_free(known_freqs);
}
/*
* Since the known frequency file should only be read in after all known
* networks are loaded (including hotspots) we need to create another module
* here which depends on hotspot.
*/
IWD_MODULE(known_frequencies, known_network_frequencies_load, known_frequencies_exit)
IWD_MODULE_DEPENDS(known_frequencies, hotspot)