1 /* 2 * Copyright (C) 2011-2014 Felix Fietkau <nbd@openwrt.org> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU Lesser General Public License version 2.1 6 * as published by the Free Software Foundation 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 */ 13 14 #include <arpa/inet.h> 15 #include <unistd.h> 16 17 #include "ubusd.h" 18 19 struct blob_buf b; 20 static struct avl_tree clients; 21 22 static struct blob_attr *attrbuf[UBUS_ATTR_MAX]; 23 24 typedef int (*ubus_cmd_cb)(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr); 25 26 static const struct blob_attr_info ubus_policy[UBUS_ATTR_MAX] = { 27 [UBUS_ATTR_SIGNATURE] = { .type = BLOB_ATTR_NESTED }, 28 [UBUS_ATTR_OBJTYPE] = { .type = BLOB_ATTR_INT32 }, 29 [UBUS_ATTR_OBJPATH] = { .type = BLOB_ATTR_STRING }, 30 [UBUS_ATTR_OBJID] = { .type = BLOB_ATTR_INT32 }, 31 [UBUS_ATTR_STATUS] = { .type = BLOB_ATTR_INT32 }, 32 [UBUS_ATTR_METHOD] = { .type = BLOB_ATTR_STRING }, 33 [UBUS_ATTR_USER] = { .type = BLOB_ATTR_STRING }, 34 [UBUS_ATTR_GROUP] = { .type = BLOB_ATTR_STRING }, 35 }; 36 37 struct blob_attr **ubus_parse_msg(struct blob_attr *msg, size_t len) 38 { 39 blob_parse_untrusted(msg, len, attrbuf, ubus_policy, UBUS_ATTR_MAX); 40 return attrbuf; 41 } 42 43 static void ubus_msg_close_fd(struct ubus_msg_buf *ub) 44 { 45 if (ub->fd < 0) 46 return; 47 48 close(ub->fd); 49 ub->fd = -1; 50 } 51 52 static void ubus_msg_init(struct ubus_msg_buf *ub, uint8_t type, uint16_t seq, uint32_t peer) 53 { 54 ub->hdr.version = 0; 55 ub->hdr.type = type; 56 ub->hdr.seq = seq; 57 ub->hdr.peer = peer; 58 } 59 60 static struct ubus_msg_buf *ubus_msg_from_blob(bool shared) 61 { 62 return ubus_msg_new(b.head, blob_raw_len(b.head), shared); 63 } 64 65 static struct ubus_msg_buf *ubus_reply_from_blob(struct ubus_msg_buf *ub, bool shared) 66 { 67 struct ubus_msg_buf *new; 68 69 new = ubus_msg_from_blob(shared); 70 if (!new) 71 return NULL; 72 73 ubus_msg_init(new, UBUS_MSG_DATA, ub->hdr.seq, ub->hdr.peer); 74 return new; 75 } 76 77 void 78 ubus_proto_send_msg_from_blob(struct ubus_client *cl, struct ubus_msg_buf *ub, 79 uint8_t type) 80 { 81 /* keep the fd to be passed if it is UBUS_MSG_INVOKE */ 82 int fd = ub->fd; 83 ub = ubus_reply_from_blob(ub, true); 84 if (!ub) 85 return; 86 87 ub->hdr.type = type; 88 ub->fd = fd; 89 90 ubus_msg_send(cl, ub); 91 ubus_msg_free(ub); 92 } 93 94 static bool ubusd_send_hello(struct ubus_client *cl) 95 { 96 struct ubus_msg_buf *ub; 97 98 blob_buf_init(&b, 0); 99 ub = ubus_msg_from_blob(true); 100 if (!ub) 101 return false; 102 103 ubus_msg_init(ub, UBUS_MSG_HELLO, 0, cl->id.id); 104 ubus_msg_send(cl, ub); 105 ubus_msg_free(ub); 106 return true; 107 } 108 109 static int ubusd_send_pong(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 110 { 111 ub->hdr.type = UBUS_MSG_DATA; 112 ubus_msg_send(cl, ub); 113 return 0; 114 } 115 116 static int ubusd_handle_remove_object(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 117 { 118 struct ubus_object *obj; 119 120 if (!attr[UBUS_ATTR_OBJID]) 121 return UBUS_STATUS_INVALID_ARGUMENT; 122 123 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 124 if (!obj) 125 return UBUS_STATUS_NOT_FOUND; 126 127 if (obj->client != cl) 128 return UBUS_STATUS_PERMISSION_DENIED; 129 130 blob_buf_init(&b, 0); 131 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 132 133 /* check if we're removing the object type as well */ 134 if (obj->type && obj->type->refcount == 1) 135 blob_put_int32(&b, UBUS_ATTR_OBJTYPE, obj->type->id.id); 136 137 ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA); 138 ubusd_free_object(obj); 139 140 return 0; 141 } 142 143 static int ubusd_handle_add_object(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 144 { 145 struct ubus_object *obj; 146 147 obj = ubusd_create_object(cl, attr); 148 if (!obj) 149 return UBUS_STATUS_INVALID_ARGUMENT; 150 151 blob_buf_init(&b, 0); 152 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 153 if (attr[UBUS_ATTR_SIGNATURE] && obj->type) 154 blob_put_int32(&b, UBUS_ATTR_OBJTYPE, obj->type->id.id); 155 156 ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA); 157 return 0; 158 } 159 160 static void ubusd_send_obj(struct ubus_client *cl, struct ubus_msg_buf *ub, struct ubus_object *obj) 161 { 162 struct ubus_method *m; 163 int all_cnt = 0, cnt = 0; 164 void *s; 165 166 if (!obj->type) 167 return; 168 169 blob_buf_init(&b, 0); 170 171 blob_put_string(&b, UBUS_ATTR_OBJPATH, obj->path.key); 172 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 173 blob_put_int32(&b, UBUS_ATTR_OBJTYPE, obj->type->id.id); 174 175 s = blob_nest_start(&b, UBUS_ATTR_SIGNATURE); 176 list_for_each_entry(m, &obj->type->methods, list) { 177 all_cnt++; 178 if (!ubusd_acl_check(cl, obj->path.key, blobmsg_name(m->data), UBUS_ACL_ACCESS)) { 179 blobmsg_add_blob(&b, m->data); 180 cnt++; 181 } 182 } 183 blob_nest_end(&b, s); 184 185 if (cnt || !all_cnt) 186 ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_DATA); 187 } 188 189 static int ubus_client_cmd_queue_add(struct ubus_client *cl, 190 struct ubus_msg_buf *msg, 191 struct ubus_object *obj) 192 { 193 struct ubus_client_cmd *cmd = malloc(sizeof(*cmd)); 194 195 if (cmd) { 196 cmd->msg = msg; 197 cmd->obj = obj; 198 list_add_tail(&cmd->list, &cl->cmd_queue); 199 return -2; 200 } 201 return UBUS_STATUS_UNKNOWN_ERROR; 202 } 203 204 static int __ubusd_handle_lookup(struct ubus_client *cl, 205 struct ubus_msg_buf *ub, 206 struct blob_attr **attr, 207 struct ubus_client_cmd *cmd) 208 { 209 struct ubus_object *obj = NULL; 210 char *objpath; 211 bool found = false; 212 int len; 213 214 if (!attr[UBUS_ATTR_OBJPATH]) { 215 if (cmd) 216 obj = cmd->obj; 217 218 /* Start from beginning or continue from the last object */ 219 if (obj == NULL) 220 obj = avl_first_element(&path, obj, path); 221 222 avl_for_element_range(obj, avl_last_element(&path, obj, path), obj, path) { 223 /* Keep sending objects until buffering starts */ 224 if (list_empty(&cl->tx_queue)) { 225 ubusd_send_obj(cl, ub, obj); 226 } else { 227 /* Queue command and continue on the next call */ 228 int ret; 229 230 if (cmd == NULL) { 231 ret = ubus_client_cmd_queue_add(cl, ub, obj); 232 } else { 233 cmd->obj = obj; 234 ret = -2; 235 } 236 return ret; 237 } 238 } 239 return 0; 240 } 241 242 objpath = blob_data(attr[UBUS_ATTR_OBJPATH]); 243 len = strlen(objpath); 244 if (objpath[len - 1] != '*') { 245 obj = avl_find_element(&path, objpath, obj, path); 246 if (!obj) 247 return UBUS_STATUS_NOT_FOUND; 248 249 ubusd_send_obj(cl, ub, obj); 250 return 0; 251 } 252 253 objpath[--len] = 0; 254 255 obj = avl_find_ge_element(&path, objpath, obj, path); 256 if (!obj) 257 return UBUS_STATUS_NOT_FOUND; 258 259 while (!strncmp(objpath, obj->path.key, len)) { 260 found = true; 261 ubusd_send_obj(cl, ub, obj); 262 if (obj == avl_last_element(&path, obj, path)) 263 break; 264 obj = avl_next_element(obj, path); 265 } 266 267 if (!found) 268 return UBUS_STATUS_NOT_FOUND; 269 270 return 0; 271 } 272 273 static int ubusd_handle_lookup(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 274 { 275 int rc; 276 277 if (list_empty(&cl->tx_queue)) 278 rc = __ubusd_handle_lookup(cl, ub, attr, NULL); 279 else 280 rc = ubus_client_cmd_queue_add(cl, ub, NULL); 281 282 return rc; 283 } 284 285 int ubusd_cmd_lookup(struct ubus_client *cl, struct ubus_client_cmd *cmd) 286 { 287 struct ubus_msg_buf *ub = cmd->msg; 288 struct blob_attr **attr; 289 int ret; 290 291 attr = ubus_parse_msg(ub->data, blob_raw_len(ub->data)); 292 ret = __ubusd_handle_lookup(cl, ub, attr, cmd); 293 294 if (ret != -2) { 295 struct ubus_msg_buf *retmsg = cl->retmsg; 296 int *retmsg_data = blob_data(blob_data(retmsg->data)); 297 298 retmsg->hdr.seq = ub->hdr.seq; 299 retmsg->hdr.peer = ub->hdr.peer; 300 301 *retmsg_data = htonl(ret); 302 ubus_msg_send(cl, retmsg); 303 } 304 return ret; 305 } 306 307 static void 308 ubusd_forward_invoke(struct ubus_client *cl, struct ubus_object *obj, 309 const char *method, struct ubus_msg_buf *ub, 310 struct blob_attr *data) 311 { 312 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 313 blob_put_string(&b, UBUS_ATTR_METHOD, method); 314 if (cl->user) 315 blob_put_string(&b, UBUS_ATTR_USER, cl->user); 316 if (cl->group) 317 blob_put_string(&b, UBUS_ATTR_GROUP, cl->group); 318 if (data) 319 blob_put(&b, UBUS_ATTR_DATA, blob_data(data), blob_len(data)); 320 321 ubus_proto_send_msg_from_blob(obj->client, ub, UBUS_MSG_INVOKE); 322 } 323 324 static int ubusd_handle_invoke(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 325 { 326 struct ubus_object *obj = NULL; 327 struct ubus_id *id; 328 const char *method; 329 330 if (!attr[UBUS_ATTR_METHOD] || !attr[UBUS_ATTR_OBJID]) 331 return UBUS_STATUS_INVALID_ARGUMENT; 332 333 id = ubus_find_id(&objects, blob_get_u32(attr[UBUS_ATTR_OBJID])); 334 if (!id) 335 return UBUS_STATUS_NOT_FOUND; 336 337 obj = container_of(id, struct ubus_object, id); 338 339 method = blob_data(attr[UBUS_ATTR_METHOD]); 340 341 if (ubusd_acl_check(cl, obj->path.key, method, UBUS_ACL_ACCESS)) 342 return UBUS_STATUS_PERMISSION_DENIED; 343 344 if (!obj->client) 345 return obj->recv_msg(cl, ub, method, attr[UBUS_ATTR_DATA]); 346 347 ub->hdr.peer = cl->id.id; 348 blob_buf_init(&b, 0); 349 350 ubusd_forward_invoke(cl, obj, method, ub, attr[UBUS_ATTR_DATA]); 351 352 return -1; 353 } 354 355 static int ubusd_handle_notify(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 356 { 357 struct ubus_object *obj = NULL; 358 struct ubus_subscription *s; 359 struct ubus_id *id; 360 const char *method; 361 bool no_reply = false; 362 void *c; 363 364 if (!attr[UBUS_ATTR_METHOD] || !attr[UBUS_ATTR_OBJID]) 365 return UBUS_STATUS_INVALID_ARGUMENT; 366 367 if (attr[UBUS_ATTR_NO_REPLY]) 368 no_reply = blob_get_int8(attr[UBUS_ATTR_NO_REPLY]); 369 370 id = ubus_find_id(&objects, blob_get_u32(attr[UBUS_ATTR_OBJID])); 371 if (!id) 372 return UBUS_STATUS_NOT_FOUND; 373 374 obj = container_of(id, struct ubus_object, id); 375 if (obj->client != cl) 376 return UBUS_STATUS_PERMISSION_DENIED; 377 378 if (!no_reply) { 379 blob_buf_init(&b, 0); 380 blob_put_int32(&b, UBUS_ATTR_OBJID, id->id); 381 c = blob_nest_start(&b, UBUS_ATTR_SUBSCRIBERS); 382 list_for_each_entry(s, &obj->subscribers, list) { 383 blob_put_int32(&b, 0, s->subscriber->id.id); 384 } 385 blob_nest_end(&b, c); 386 blob_put_int32(&b, UBUS_ATTR_STATUS, 0); 387 ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_STATUS); 388 } 389 390 ub->hdr.peer = cl->id.id; 391 method = blob_data(attr[UBUS_ATTR_METHOD]); 392 list_for_each_entry(s, &obj->subscribers, list) { 393 blob_buf_init(&b, 0); 394 if (no_reply) 395 blob_put_int8(&b, UBUS_ATTR_NO_REPLY, 1); 396 ubusd_forward_invoke(cl, s->subscriber, method, ub, attr[UBUS_ATTR_DATA]); 397 } 398 399 return -1; 400 } 401 402 static struct ubus_client *ubusd_get_client_by_id(uint32_t id) 403 { 404 struct ubus_id *clid; 405 406 clid = ubus_find_id(&clients, id); 407 if (!clid) 408 return NULL; 409 410 return container_of(clid, struct ubus_client, id); 411 } 412 413 static int ubusd_handle_response(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 414 { 415 struct ubus_object *obj; 416 417 if (!attr[UBUS_ATTR_OBJID] || 418 (ub->hdr.type == UBUS_MSG_STATUS && !attr[UBUS_ATTR_STATUS]) || 419 (ub->hdr.type == UBUS_MSG_DATA && !attr[UBUS_ATTR_DATA])) 420 goto out; 421 422 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 423 if (!obj) 424 goto out; 425 426 if (cl != obj->client) 427 goto out; 428 429 cl = ubusd_get_client_by_id(ub->hdr.peer); 430 if (!cl) 431 goto out; 432 433 ub->hdr.peer = blob_get_u32(attr[UBUS_ATTR_OBJID]); 434 ubus_msg_send(cl, ub); 435 out: 436 return -1; 437 } 438 439 static int ubusd_handle_add_watch(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 440 { 441 struct ubus_object *obj, *target; 442 443 if (!attr[UBUS_ATTR_OBJID] || !attr[UBUS_ATTR_TARGET]) 444 return UBUS_STATUS_INVALID_ARGUMENT; 445 446 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 447 if (!obj) 448 return UBUS_STATUS_NOT_FOUND; 449 450 if (cl != obj->client) 451 return UBUS_STATUS_INVALID_ARGUMENT; 452 453 target = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_TARGET])); 454 if (!target || !target->client) 455 return UBUS_STATUS_NOT_FOUND; 456 457 if (cl == target->client) 458 return UBUS_STATUS_INVALID_ARGUMENT; 459 460 if (!target->path.key) { 461 if (strcmp(target->client->user, cl->user) && strcmp(target->client->group, cl->group)) 462 return UBUS_STATUS_NOT_FOUND; 463 } else if (ubusd_acl_check(cl, target->path.key, NULL, UBUS_ACL_SUBSCRIBE)) { 464 return UBUS_STATUS_NOT_FOUND; 465 } 466 467 ubus_subscribe(obj, target); 468 return 0; 469 } 470 471 static int ubusd_handle_remove_watch(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 472 { 473 struct ubus_object *obj; 474 struct ubus_subscription *s; 475 uint32_t id; 476 477 if (!attr[UBUS_ATTR_OBJID] || !attr[UBUS_ATTR_TARGET]) 478 return UBUS_STATUS_INVALID_ARGUMENT; 479 480 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 481 if (!obj) 482 return UBUS_STATUS_NOT_FOUND; 483 484 if (cl != obj->client) 485 return UBUS_STATUS_INVALID_ARGUMENT; 486 487 id = blob_get_u32(attr[UBUS_ATTR_TARGET]); 488 list_for_each_entry(s, &obj->target_list, target_list) { 489 if (s->target->id.id != id) 490 continue; 491 492 ubus_unsubscribe(s); 493 return 0; 494 } 495 496 return UBUS_STATUS_NOT_FOUND; 497 } 498 499 static const ubus_cmd_cb handlers[__UBUS_MSG_LAST] = { 500 [UBUS_MSG_PING] = ubusd_send_pong, 501 [UBUS_MSG_ADD_OBJECT] = ubusd_handle_add_object, 502 [UBUS_MSG_REMOVE_OBJECT] = ubusd_handle_remove_object, 503 [UBUS_MSG_LOOKUP] = ubusd_handle_lookup, 504 [UBUS_MSG_INVOKE] = ubusd_handle_invoke, 505 [UBUS_MSG_STATUS] = ubusd_handle_response, 506 [UBUS_MSG_DATA] = ubusd_handle_response, 507 [UBUS_MSG_SUBSCRIBE] = ubusd_handle_add_watch, 508 [UBUS_MSG_UNSUBSCRIBE] = ubusd_handle_remove_watch, 509 [UBUS_MSG_NOTIFY] = ubusd_handle_notify, 510 }; 511 512 void ubusd_proto_receive_message(struct ubus_client *cl, struct ubus_msg_buf *ub) 513 { 514 ubus_cmd_cb cb = NULL; 515 int ret; 516 struct ubus_msg_buf *retmsg = cl->retmsg; 517 int *retmsg_data = blob_data(blob_data(retmsg->data)); 518 519 retmsg->hdr.seq = ub->hdr.seq; 520 retmsg->hdr.peer = ub->hdr.peer; 521 522 if (ub->hdr.type < __UBUS_MSG_LAST) 523 cb = handlers[ub->hdr.type]; 524 525 if (ub->hdr.type != UBUS_MSG_STATUS && ub->hdr.type != UBUS_MSG_INVOKE) 526 ubus_msg_close_fd(ub); 527 528 /* Note: no callback should free the `ub` buffer 529 that's always done right after the callback finishes */ 530 if (cb) 531 ret = cb(cl, ub, ubus_parse_msg(ub->data, blob_raw_len(ub->data))); 532 else 533 ret = UBUS_STATUS_INVALID_COMMAND; 534 535 /* Command has not been completed yet and got queued */ 536 if (ret == -2) 537 return; 538 539 ubus_msg_free(ub); 540 541 if (ret == -1) 542 return; 543 544 *retmsg_data = htonl(ret); 545 ubus_msg_send(cl, retmsg); 546 } 547 548 static int ubusd_proto_init_retmsg(struct ubus_client *cl) 549 { 550 struct blob_buf *b = &cl->b; 551 552 blob_buf_init(&cl->b, 0); 553 blob_put_int32(&cl->b, UBUS_ATTR_STATUS, 0); 554 555 /* we make the 'retmsg' buffer shared with the blob_buf b, to reduce mem duplication */ 556 cl->retmsg = ubus_msg_new(b->head, blob_raw_len(b->head), true); 557 if (!cl->retmsg) 558 return -1; 559 560 cl->retmsg->hdr.type = UBUS_MSG_STATUS; 561 return 0; 562 } 563 564 struct ubus_client *ubusd_proto_new_client(int fd, uloop_fd_handler cb) 565 { 566 struct ubus_client *cl; 567 568 cl = calloc(1, sizeof(*cl)); 569 if (!cl) 570 return NULL; 571 572 if (ubusd_acl_init_client(cl, fd)) 573 goto free; 574 575 INIT_LIST_HEAD(&cl->objects); 576 INIT_LIST_HEAD(&cl->cmd_queue); 577 INIT_LIST_HEAD(&cl->tx_queue); 578 cl->sock.fd = fd; 579 cl->sock.cb = cb; 580 cl->pending_msg_fd = -1; 581 582 if (!ubus_alloc_id(&clients, &cl->id, 0)) 583 goto free; 584 585 if (ubusd_proto_init_retmsg(cl)) 586 goto free; 587 588 if (!ubusd_send_hello(cl)) 589 goto delete; 590 591 return cl; 592 593 delete: 594 ubus_free_id(&clients, &cl->id); 595 free: 596 free(cl); 597 return NULL; 598 } 599 600 void ubusd_proto_free_client(struct ubus_client *cl) 601 { 602 struct ubus_object *obj, *tmp; 603 604 list_for_each_entry_safe(obj, tmp, &cl->objects, list) { 605 ubusd_free_object(obj); 606 } 607 608 ubus_msg_free(cl->retmsg); 609 blob_buf_free(&cl->b); 610 611 ubusd_acl_free_client(cl); 612 ubus_free_id(&clients, &cl->id); 613 } 614 615 void ubus_notify_subscription(struct ubus_object *obj) 616 { 617 bool active = !list_empty(&obj->subscribers); 618 struct ubus_msg_buf *ub; 619 620 blob_buf_init(&b, 0); 621 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 622 blob_put_int8(&b, UBUS_ATTR_ACTIVE, active); 623 624 ub = ubus_msg_from_blob(false); 625 if (!ub) 626 return; 627 628 ubus_msg_init(ub, UBUS_MSG_NOTIFY, ++obj->invoke_seq, 0); 629 ubus_msg_send(obj->client, ub); 630 ubus_msg_free(ub); 631 } 632 633 void ubus_notify_unsubscribe(struct ubus_subscription *s) 634 { 635 struct ubus_msg_buf *ub; 636 637 blob_buf_init(&b, 0); 638 blob_put_int32(&b, UBUS_ATTR_OBJID, s->subscriber->id.id); 639 blob_put_int32(&b, UBUS_ATTR_TARGET, s->target->id.id); 640 641 ub = ubus_msg_from_blob(false); 642 if (ub != NULL) { 643 ubus_msg_init(ub, UBUS_MSG_UNSUBSCRIBE, ++s->subscriber->invoke_seq, 0); 644 ubus_msg_send(s->subscriber->client, ub); 645 ubus_msg_free(ub); 646 } 647 648 ubus_unsubscribe(s); 649 } 650 651 static void __constructor ubusd_proto_init(void) 652 { 653 ubus_init_id_tree(&clients); 654 } 655
This page was automatically generated by LXR 0.3.1. • OpenWrt