+void
+interface_add_user(struct interface_user *dep, struct interface *iface)
+{
+ if (!iface) {
+ list_add(&dep->list, &iface_all_users);
+ return;
+ }
+
+ dep->iface = iface;
+ list_add(&dep->list, &iface->users);
+ if (iface->state == IFS_UP)
+ dep->cb(dep, iface, IFEV_UP);
+}
+
+void
+interface_remove_user(struct interface_user *dep)
+{
+ list_del_init(&dep->list);
+ dep->iface = NULL;
+}
+
+static void
+interface_add_assignment_classes(struct interface *iface, struct blob_attr *list)
+{
+ struct blob_attr *cur;
+ int rem;
+
+ blobmsg_for_each_attr(cur, list, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ if (!blobmsg_check_attr(cur, NULL))
+ continue;
+
+ struct interface_assignment_class *c = malloc(sizeof(*c) + blobmsg_data_len(cur));
+ memcpy(c->name, blobmsg_data(cur), blobmsg_data_len(cur));
+ list_add(&c->head, &iface->assignment_classes);
+ }
+}
+
+static void
+interface_clear_assignment_classes(struct interface *iface)
+{
+ while (!list_empty(&iface->assignment_classes)) {
+ struct interface_assignment_class *c = list_first_entry(&iface->assignment_classes,
+ struct interface_assignment_class, head);
+ list_del(&c->head);
+ free(c);
+ }
+}
+
+static void
+interface_merge_assignment_data(struct interface *old, struct interface *new)
+{
+ bool changed = (old->assignment_hint != new->assignment_hint ||
+ old->assignment_length != new->assignment_length ||
+ list_empty(&old->assignment_classes) != list_empty(&new->assignment_classes));
+
+ struct interface_assignment_class *c;
+ list_for_each_entry(c, &new->assignment_classes, head) {
+ // Compare list entries one-by-one to see if there was a change
+ if (list_empty(&old->assignment_classes)) // The new list is longer
+ changed = true;
+
+ if (changed)
+ break;
+
+ struct interface_assignment_class *c_old = list_first_entry(&old->assignment_classes,
+ struct interface_assignment_class, head);
+
+ if (strcmp(c_old->name, c->name)) // An entry didn't match
+ break;
+
+ list_del(&c_old->head);
+ free(c_old);
+ }
+
+ // The old list was longer than the new one or the last entry didn't match
+ if (!list_empty(&old->assignment_classes)) {
+ interface_clear_assignment_classes(old);
+ changed = true;
+ }
+
+ list_splice_init(&new->assignment_classes, &old->assignment_classes);
+
+ if (changed) {
+ old->assignment_hint = new->assignment_hint;
+ old->assignment_length = new->assignment_length;
+ interface_refresh_assignments(true);
+ }
+}
+
+static void
+interface_alias_cb(struct interface_user *dep, struct interface *iface, enum interface_event ev)
+{
+ struct interface *alias = container_of(dep, struct interface, parent_iface);
+ struct device *dev = iface->l3_dev.dev;
+
+ switch (ev) {
+ case IFEV_UP:
+ if (!dev)
+ return;
+
+ interface_set_main_dev(alias, dev);
+ interface_set_available(alias, true);
+ break;
+ case IFEV_DOWN:
+ interface_set_available(alias, false);
+ interface_set_main_dev(alias, NULL);
+ break;
+ case IFEV_FREE:
+ interface_remove_user(dep);
+ break;
+ case IFEV_RELOAD:
+ case IFEV_UPDATE:
+ break;
+ }
+}
+
+static void
+interface_claim_device(struct interface *iface)
+{
+ struct interface *parent;
+ struct device *dev = NULL;
+
+ if (iface->parent_iface.iface)
+ interface_remove_user(&iface->parent_iface);
+
+ if (iface->parent_ifname) {
+ parent = vlist_find(&interfaces, iface->parent_ifname, parent, node);
+ iface->parent_iface.cb = interface_alias_cb;
+ interface_add_user(&iface->parent_iface, parent);
+ } else if (iface->ifname &&
+ !(iface->proto_handler->flags & PROTO_FLAG_NODEV)) {
+ dev = device_get(iface->ifname, true);
+ }
+
+ if (dev)
+ interface_set_main_dev(iface, dev);
+
+ if (iface->proto_handler->flags & PROTO_FLAG_INIT_AVAILABLE)
+ interface_set_available(iface, true);
+}
+
+static void
+interface_cleanup_state(struct interface *iface)
+{
+ interface_set_available(iface, false);
+
+ interface_flush_state(iface);
+ interface_clear_errors(iface);
+ interface_set_proto_state(iface, NULL);
+
+ if (iface->main_dev.dev)
+ interface_set_main_dev(iface, NULL);
+}
+
+static void
+interface_cleanup(struct interface *iface)
+{
+ struct interface_user *dep, *tmp;
+
+ if (iface->parent_iface.iface)
+ interface_remove_user(&iface->parent_iface);
+
+ list_for_each_entry_safe(dep, tmp, &iface->users, list)
+ interface_remove_user(dep);
+
+ interface_clear_assignment_classes(iface);
+ interface_ip_flush(&iface->config_ip);
+ interface_cleanup_state(iface);
+}
+
+static void
+interface_do_free(struct interface *iface)
+{
+ interface_event(iface, IFEV_FREE);
+ interface_cleanup(iface);
+ free(iface->config);
+ netifd_ubus_remove_interface(iface);
+ avl_delete(&interfaces.avl, &iface->node.avl);
+ free(iface);
+}
+
+static void
+interface_do_reload(struct interface *iface)
+{
+ interface_event(iface, IFEV_RELOAD);
+ interface_cleanup_state(iface);
+ proto_init_interface(iface, iface->config);
+ interface_claim_device(iface);
+}
+
+static void
+interface_handle_config_change(struct interface *iface)
+{
+ enum interface_config_state state = iface->config_state;
+
+ iface->config_state = IFC_NORMAL;
+ switch(state) {
+ case IFC_NORMAL:
+ break;
+ case IFC_RELOAD:
+ interface_do_reload(iface);
+ break;
+ case IFC_REMOVE:
+ interface_do_free(iface);
+ return;
+ }
+ if (iface->autostart && iface->available)
+ interface_set_up(iface);
+}
+