-
Jakob Olsson authoredJakob Olsson authored
dongle.c 14.96 KiB
#include <getopt.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <limits.h>
#include <netdb.h>
#include <ifaddrs.h>
#include "dongle.h"
static void uloop_add_get_devices(struct uloop_timeout *t);
static int get_devices_from_path(char *input_path);
static int get_devices(void);
static int poll_devices(struct uloop_timeout *t);
static int tag_missing_devices(void);
static int add_device(struct device *new_dev);
static int delete_all_devices(void);
static int delete_device_by_name(char *name);
static int delete_device(struct device *dev);
static void free_device(struct device *dev);
static void free_usb(struct USB *usb);
static struct device *search_list(char *name);
static char *get_usb_stat(char *path, char *file);
static char *get_device_name(char *dir_name);
static char *get_device_ip(char *device_name);
static int list_to_blob(struct blob_buf *bb);
static int print_list(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
static int clear(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
static int test(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
static int remove_device(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg);
static void init_ubus(void);
static int publish_object(struct ubus_context *ctx);
static struct ubus_context *global_ctx;
struct device *global_dev;
int debug;
static struct uloop_timeout timeout = {
.cb = poll_devices
};
static struct option long_options[] = {
{"debug", required_argument, NULL, 'd'},
{0, 0, 0, 0}
};
static const struct blobmsg_policy dev_policy[__DEV_MAX] = {
[DEV] = {.name = "dev", .type = BLOBMSG_TYPE_STRING},
};
enum {
USB_PATH,
__USB_MAX
};
static const struct blobmsg_policy usb_policy[__USB_MAX] = {
[USB_PATH] = {.name = "path", .type = BLOBMSG_TYPE_STRING},
};
LIST_HEAD(devices);
LIST_HEAD(stack);
LIST_HEAD(visited);
static int parse_args(int argc, char **argv)
{
char ch;
while ((ch = getopt_long(argc, argv, ":d:", long_options, NULL)) != -1) {
switch (ch) {
case 'd':
debug = atoi(optarg);
if (debug > 1 || debug < 0) {
printf("%s: option '-%c' is invalid.\n", argv[0], optopt);
goto fail;
}
goto done;
break;
case ':':
fprintf(stderr, "%s: option '-%c' requires an argument\n", argv[0], optopt);
goto fail;
break;
case '?':
fprintf(stderr, "%s: option '-%c' is invalid: ignored\n", argv[0], optopt);
goto fail;
break;
default:
goto fail;
break;
}
}
done:
return 0;
fail:
return -1;
}
static int poll_devices(struct uloop_timeout *t)
{
get_devices();
tag_missing_devices();
uloop_add_get_devices(t);
return 0;
}
static int tag_missing_devices(void)
{
struct device *dev, *tmp;
list_for_each_entry_safe(dev, tmp, &devices, list) {
if (dev->present) {
dev->missing = 0;
dev->present = false;
continue;
}
dev->missing++;
if (dev->missing == 1) {
/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
list_del(&dev->list);
delete_device(dev);
}
}
return 0;
}
static int update_device(struct device *dev)
{
struct device *prev_dev;
int rv;
prev_dev = search_list(dev->usb.if_name);
if (!prev_dev)
goto not_present;
if (prev_dev->ip)
free(prev_dev->ip);
if (dev->ip)
prev_dev->ip = strdup(dev->ip);
else
prev_dev->ip = NULL;
if (!prev_dev->ubus_obj && prev_dev->ip) {
prev_dev->ubus_obj = dongle_create_dynamic_object(prev_dev);
if (!prev_dev->ubus_obj)
goto fail;
rv = publish_ubus_object(global_ctx, prev_dev->ubus_obj);
if (rv)
goto fail;
global_dev = prev_dev;
} else if (prev_dev->ubus_obj && !prev_dev->ip) {
unpublish_ubus_object(global_ctx, prev_dev->ubus_obj);
dongle_destroy_dynamic_object(&(prev_dev->ubus_obj));
}
prev_dev->present = true;
return 0;
not_present:
return -1;
fail:
return -2;
}
static int add_device(struct device *dev)
{
int rv;
rv = update_device(dev);
if (rv == 0)
goto already_present;
dev->present = true;
if (list_empty(&devices))
INIT_LIST_HEAD(&devices);
list_add_tail(&dev->list, &devices);
if (dev->ip) {
dev->ubus_obj = dongle_create_dynamic_object(dev);
if (!dev->ubus_obj)
goto fail;
rv = publish_ubus_object(global_ctx, dev->ubus_obj);
if (rv)
goto fail_publish;
global_dev = dev;
}
return 0;
already_present:
return -1;
fail_publish:
dongle_destroy_dynamic_object(&(dev->ubus_obj));
fail:
return -2;
}
static int delete_all_devices(void)
{
struct device *dev, *tmp;
list_for_each_entry_safe(dev, tmp, &devices, list) {
/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
list_del(&dev->list);
delete_device(dev);
}
return 0;
}
static int delete_device_by_name(char *name)
{
struct device *dev, *tmp;
list_for_each_entry_safe(dev, tmp, &devices, list) {
if (strncmp(dev->usb.if_name, name, 128) != 0)
continue;
/* TEMPORARY FIX -- TODO: HOW TO CHECK IF IT CONTAINS ELEMENT */
list_del(&dev->list);
delete_device(dev);
return 0;
}
return -1;
}
static int delete_device(struct device *dev)
{
if (dev->ubus_obj) {
unpublish_ubus_object(global_ctx, dev->ubus_obj);
dongle_destroy_dynamic_object(&(dev->ubus_obj));
}
/*if (dev->list) // how to check if list contains this element?
list_del(&dev->list);*/
free_device(dev);
free(dev);
return 0;
}
static void free_device(struct device *dev)
{
if (!dev)
return;
free(dev->ip);
free_usb(&(dev->usb));
//free(&(dev->usb));
}
static void free_usb(struct USB *usb)
{
if (!usb)
return;
free(usb->product);
free(usb->product_id);
free(usb->vendor_id);
free(usb->if_name);
}
static struct device *search_list(char *name)
{
struct device *dev;
list_for_each_entry(dev, &devices, list) {
if (strncmp(name, dev->usb.if_name, 128) == 0)
return dev;
}
return NULL;
}
static char *get_usb_stat(char *path, char *file)
{
char stat_path[PATH_MAX], contents[1024];
FILE *f;
snprintf(stat_path, PATH_MAX, "%s%s", path, file);
f = fopen(stat_path, "r");
if (!f) {
perror("fopen");
goto fail_fopen;
}
fgets(contents, 1024, f);
remove_newline(contents);
fclose(f);
return strdup(contents);
fail_fopen:
return NULL;
}
static char *get_device_name(char *dir_name)
{
char path[PATH_MAX], *name = NULL;
struct directory *dr, *sub_dr;
struct dirent *de;
struct stat st;
DIR *dir;
dr = (struct directory *)malloc(sizeof(*dr));
if (!dr) {
perror("malloc");
goto fail_dr;
}
dr->path = strdup(dir_name);
if (!dr->path)
goto fail_path;
push(dr, &stack);
while (!list_empty(&stack) && !name) {
dr = pop(&stack);
dir = opendir(dr->path);
if (!dir) {
free(dr->path);
free(dr);
continue;
}
push(dr, &visited);
while ((de = readdir(dir)) != NULL) {
if (de->d_name[0] == '.')
continue;
if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
perror("fstatat");
continue;
}
if (!S_ISDIR(st.st_mode))
continue;
if (!strstr(dr->path, "net")) {
snprintf(path, PATH_MAX, "%s%s/", dr->path, de->d_name);
if (search(path, &visited))
continue;
sub_dr = (struct directory *)malloc(sizeof(*sub_dr));
if (!sub_dr) {
perror("malloc");
continue;
}
sub_dr->path = strdup(path);
if (!sub_dr->path) {
free(sub_dr);
continue;
}
push(sub_dr, &stack);
continue;
}
if (!strstr(de->d_name, "eth") && !strstr(de->d_name, "usb"))
break;
name = strdup(de->d_name);
// how to manage failure on this strdup
break;
}
closedir(dir);
}
clear_list(&visited);
clear_list(&stack);
return name;
fail_path:
free(dr);
fail_dr:
return NULL;
}
static int get_devices_from_path(char *input_path)
{
char *path, *name;
struct dirent *de;
DIR *dir;
struct stat st;
struct device *dev;
int rv, path_len;
dir = opendir(input_path);
if (!dir) {
perror("opendir");
goto fail_dr;
}
path_len = strlen(input_path) + strlen("/product") + 1;
path = malloc(path_len);
if (!path) {
perror("malloc");
goto fail_path;
}
while ((de = readdir(dir)) != NULL) {
if (de->d_name[0] == '.')
continue;
if (strstr(de->d_name, ":") || strstr(de->d_name, "usb"))
continue;
snprintf(path, path_len, "%s/product", input_path);
memset(&st, 0, sizeof(st));
rv = stat(path, &st);
if (rv < 0) {
//perror("stat");
continue;
}
snprintf(path, path_len, "%s/", input_path);
name = get_device_name(path);
if (!name)
continue;
dev = (struct device *)calloc(1, sizeof(*dev));
if (!dev) {
perror("calloc");
/*goto fail_dev;*/
free(name);
continue;
}
dev->usb.product = get_usb_stat(path, "product");
dev->usb.product_id = get_usb_stat(path, "idProduct");
dev->usb.vendor_id = get_usb_stat(path, "idVendor");
dev->usb.if_name = name;
dev->ip = get_device_ip(dev->usb.if_name);
rv = add_device(dev);
if (rv < 0)
delete_device(dev);
break;
}
closedir(dir);
return 0;
fail_path:
closedir(dir);
fail_dr:
return -1;
}
static int get_devices(void)
{
char devices_path[PATH_MAX], mount_path[PATH_MAX];
struct dirent *de;
DIR *dir;
int rv;
strncpy(devices_path, "/sys/bus/usb/devices/", PATH_MAX);
dir = opendir(devices_path);
if (!dir) {
perror("opendir");
goto fail_dr;
}
while ((de = readdir(dir)) != NULL) {
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
continue;
if (strstr(de->d_name, ":") || strstr(de->d_name, "usb"))
continue;
// found the usb folder
snprintf(mount_path, PATH_MAX, "%s%s/", devices_path, de->d_name);
rv = get_devices_from_path(mount_path);
if (rv < 0)
break;
}
closedir(dir);
return 0;
fail_dr:
return -1;
}
static char *get_device_ip(char *device_name)
{
char *iface, *destination, *gateway, *flags, *route, *ipv4_addr = NULL;
int host_flag;
struct in_addr addr;
FILE *fp;
fp = fopen("/proc/net/route", "r");
if (!fp) {
perror("fopen");
goto fail_fopen;
}
route = (char *)calloc(1, 1024);
if (!route) {
perror("calloc");
goto fail_route;
}
while ((fgets(route, 1024, fp)) != NULL) {
remove_newline(route);
iface = strtok(route, "\t");
destination = strtok(NULL, "\t");
gateway = strtok(NULL, "\t");
flags = strtok(NULL, "\t");
if (strncmp(iface, device_name, 10) != 0)
continue;
host_flag = atoi(flags) & 4;
if (!host_flag)
continue;
ipv4_addr = (char *)calloc(1, IPV4_MAX);
if (!ipv4_addr)
break;
addr.s_addr = strtoul(destination, NULL, IPV4_MAX);
inet_ntop(AF_INET, &(addr.s_addr), ipv4_addr, IPV4_MAX);
}
free(route);
fail_route:
fclose(fp);
fail_fopen:
return ipv4_addr;
}
static int list_to_blob(struct blob_buf *bb)
{
struct device *net_dev;
void *arr, *table;
int rv;
memset(bb, 0, sizeof(*bb));
rv = blob_buf_init(bb, 0);
if (rv != 0)
goto fail;
arr = blobmsg_open_array(bb, "network_devices");
if (!arr)
goto fail_arr;
list_for_each_entry(net_dev, &devices, list) {
table = blobmsg_open_table(bb, "");
if (!table)
goto fail_table;
rv = blobmsg_add_string(bb, "product", net_dev->usb.product);
if (rv < 0)
goto fail_string;
rv = blobmsg_add_string(bb, "interface_name", net_dev->usb.if_name);
if (rv < 0)
goto fail_string;
rv = blobmsg_add_string(bb, "idproduct", net_dev->usb.product_id);
if (rv < 0)
goto fail_string;
rv = blobmsg_add_string(bb, "idvendor", net_dev->usb.vendor_id);
if (rv < 0)
goto fail_string;
if (net_dev->ip) {
rv = blobmsg_add_string(bb, "ip", net_dev->ip);
if (rv < 0)
goto fail_string;
}
blobmsg_close_table(bb, table);
}
blobmsg_close_array(bb, arr);
return 0;
fail_string:
fail_table:
fail_arr:
fail:
return UBUS_STATUS_UNKNOWN_ERROR;
}
static int print_list(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_buf bb;
int rv;
rv = list_to_blob(&bb);
if (rv != 0)
goto fail;
ubus_send_reply(ctx, req, bb.head);
fail:
blob_buf_free(&bb);
return rv;
}
static int clear(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
printf("%d\n", delete_all_devices());
return 0;
}
static int test(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
printf("%s\n", get_device_ip("usb0"));
return 0;
}
static int alert(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__USB_MAX];
char *usb_path;
blobmsg_parse(usb_policy, __USB_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[USB_PATH])
goto fail;
usb_path = (char *)blobmsg_data(tb[USB_PATH]);
get_devices_from_path(usb_path);
return 0;
fail:
return UBUS_STATUS_INVALID_ARGUMENT;
}
static int remove_device(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__DEV_MAX];
char *dev;
blobmsg_parse(dev_policy, __DEV_MAX, tb, blob_data(msg), blob_len(msg));
if (!tb[DEV])
goto fail;
dev = (char *)blobmsg_data(tb[DEV]);
printf("%d\n", delete_device_by_name(dev));
return 0;
fail:
return UBUS_STATUS_INVALID_ARGUMENT;
}
static int update(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
get_devices();
tag_missing_devices();
return 0;
}
static struct ubus_method dongle_object_methods[] = {
UBUS_METHOD_NOARG("test", test),
UBUS_METHOD_NOARG("list", print_list),
UBUS_METHOD_NOARG("clear", clear),
UBUS_METHOD("remove_device", remove_device, dev_policy),
UBUS_METHOD("alert", alert, usb_policy),
UBUS_METHOD_NOARG("update", update)};
static struct ubus_object_type dongle_object_type = UBUS_OBJECT_TYPE("dongle", dongle_object_methods);
static struct ubus_object dongle_object = {
.name = "dongle",
.type = &dongle_object_type,
.methods = dongle_object_methods,
.n_methods = ARRAY_SIZE(dongle_object_methods),
};
static void uloop_add_get_devices(struct uloop_timeout *t)
{
uloop_timeout_set(t, 60000);
uloop_timeout_add(t);
}
static void init_ubus(void)
{
global_ctx = ubus_connect(NULL);
if (!global_ctx) {
perror("ubus");
exit(1);
}
ubus_add_uloop(global_ctx);
}
static int publish_object(struct ubus_context *ctx)
{
int rv;
rv = ubus_add_object(ctx, &dongle_object);
if (rv) {
debug_print("failed to add dongle.pin to ubus!\n");
return -1;
}
return 0;
}
int main(int argc, char **argv)
{
int rv;
rv = parse_args(argc, argv);
if (rv < 0)
goto fail;
uloop_init();
init_ubus();
uloop_timeout_set(&timeout, 0);
uloop_timeout_add(&timeout);
rv = publish_object(global_ctx);
if (rv < 0)
goto fail;
uloop_run();
return 0;
fail:
return -1;
}