Skip to content
Snippets Groups Projects
dongle.c 15 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jakob Olsson's avatar
    Jakob Olsson committed
    #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>
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    #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;
    
    static struct uloop_timeout timeout = {
    
    	.cb = poll_devices
    
    static struct option long_options[] = {
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	{"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},
    };
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    enum {
    	USB_PATH,
    	__USB_MAX
    };
    
    
    static const struct blobmsg_policy usb_policy[__USB_MAX] = {
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	[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)
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	char ch;
    
    
    	while ((ch = getopt_long(argc, argv, ":d:", long_options, NULL)) != -1) {
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		switch (ch) {
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		case 'd':
    			debug = atoi(optarg);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    			if (debug > 1 || debug < 0) {
    
    				printf("%s: option '-%c' is invalid.\n", argv[0], optopt);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    			}
    			goto done;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		case ':':
    
    			fprintf(stderr, "%s: option '-%c' requires an argument\n", argv[0], optopt);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    			goto fail;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		case '?':
    
    			fprintf(stderr, "%s: option '-%c' is invalid: ignored\n", argv[0], optopt);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    			goto fail;
    
    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);
    
    static int update_device(struct device *dev)
    
    	struct device *prev_dev;
    
    	int rv;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    	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)
    
    		rv = publish_ubus_object(global_ctx, dev->ubus_obj);
    
    		if (rv)
    
    		global_dev = dev;
    
    	return 0;
    already_present:
    	return -1;
    
    	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);
    
    static void free_device(struct device *dev)
    
    {
    	if (!dev)
    		return;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    	free(dev->ip);
    
    	free_usb(&(dev->usb));
    
    	//free(&(dev->usb));
    
    static void free_usb(struct USB *usb)
    
    {
    	if (!usb)
    		return;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    	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");
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		goto fail_fopen;
    
    	}
    
    	fgets(contents, 1024, f);
    	remove_newline(contents);
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	fclose(f);
    
    	return strdup(contents);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    fail_fopen:
    
    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);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    		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;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	struct dirent *de;
    	DIR *dir;
    	struct stat st;
    	struct device *dev;
    
    	int rv, path_len;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	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;
    	}
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	while ((de = readdir(dir)) != NULL) {
    
    		if (de->d_name[0] == '.')
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    			continue;
    		if (strstr(de->d_name, ":") || strstr(de->d_name, "usb"))
    			continue;
    
    		snprintf(path, path_len, "%s/product", input_path);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		memset(&st, 0, sizeof(st));
    
    		rv = stat(path, &st);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		if (rv < 0) {
    			//perror("stat");
    			continue;
    		}
    
    		snprintf(path, path_len, "%s/", input_path);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		name = get_device_name(path);
    		if (!name)
    			continue;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		dev = (struct device *)calloc(1, sizeof(*dev));
    
    		if (!dev) {
    			perror("calloc");
    			/*goto fail_dev;*/
    			free(name);
    
    
    		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");
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		dev->usb.if_name = name;
    		dev->ip = get_device_ip(dev->usb.if_name);
    
    
    		rv = add_device(dev);
    		if (rv < 0)
    			delete_device(dev);
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	}
    	closedir(dir);
    	return 0;
    
    fail_path:
    	closedir(dir);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    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;
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    	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;
    
    	struct in_addr addr;
    	FILE *fp;
    
    	fp = fopen("/proc/net/route", "r");
    	if (!fp) {
    		perror("fopen");
    
    	route = (char *)calloc(1, 1024);
    
    	if (!route) {
    
    		perror("calloc");
    
    		goto fail_route;
    	}
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    
    	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)
    
    
    		host_flag = atoi(flags) & 4;
    
    
    		ipv4_addr = (char *)calloc(1, IPV4_MAX);
    
    		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);
    
    	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;
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    		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);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	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)
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    {
    	struct blob_attr *tb[__USB_MAX];
    	char *usb_path;
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	blobmsg_parse(usb_policy, __USB_MAX, tb, blob_data(msg), blob_len(msg));
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    
    	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),
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	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) {
    
    	ubus_add_uloop(global_ctx);
    
    static int publish_object(struct ubus_context *ctx)
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    {
    	int rv;
    
    
    	rv = ubus_add_object(ctx, &dongle_object);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	if (rv) {
    		debug_print("failed to add dongle.pin to ubus!\n");
    		return -1;
    	}
    
    	return 0;
    }
    
    
    	int rv;
    
    	rv = parse_args(argc, argv);
    	if (rv < 0)
    		goto fail;
    
    
    	uloop_timeout_set(&timeout, 0);
    	uloop_timeout_add(&timeout);
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    	rv = publish_object(global_ctx);
    
    	if (rv < 0)
    		goto fail;