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 (!len) 245 return UBUS_STATUS_INVALID_ARGUMENT; 246 247 if (objpath[len - 1] != '*') { 248 obj = avl_find_element(&path, objpath, obj, path); 249 if (!obj) 250 return UBUS_STATUS_NOT_FOUND; 251 252 ubusd_send_obj(cl, ub, obj); 253 return 0; 254 } 255 256 objpath[--len] = 0; 257 258 obj = avl_find_ge_element(&path, objpath, obj, path); 259 if (!obj) 260 return UBUS_STATUS_NOT_FOUND; 261 262 while (!strncmp(objpath, obj->path.key, len)) { 263 found = true; 264 ubusd_send_obj(cl, ub, obj); 265 if (obj == avl_last_element(&path, obj, path)) 266 break; 267 obj = avl_next_element(obj, path); 268 } 269 270 if (!found) 271 return UBUS_STATUS_NOT_FOUND; 272 273 return 0; 274 } 275 276 static int ubusd_handle_lookup(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 277 { 278 int rc; 279 280 if (list_empty(&cl->tx_queue)) 281 rc = __ubusd_handle_lookup(cl, ub, attr, NULL); 282 else 283 rc = ubus_client_cmd_queue_add(cl, ub, NULL); 284 285 return rc; 286 } 287 288 int ubusd_cmd_lookup(struct ubus_client *cl, struct ubus_client_cmd *cmd) 289 { 290 struct ubus_msg_buf *ub = cmd->msg; 291 struct blob_attr **attr; 292 int ret; 293 294 attr = ubus_parse_msg(ub->data, blob_raw_len(ub->data)); 295 ret = __ubusd_handle_lookup(cl, ub, attr, cmd); 296 297 if (ret != -2) { 298 struct ubus_msg_buf *retmsg = cl->retmsg; 299 int *retmsg_data = blob_data(blob_data(retmsg->data)); 300 301 retmsg->hdr.seq = ub->hdr.seq; 302 retmsg->hdr.peer = ub->hdr.peer; 303 304 *retmsg_data = htonl(ret); 305 ubus_msg_send(cl, retmsg); 306 } 307 return ret; 308 } 309 310 static void 311 ubusd_forward_invoke(struct ubus_client *cl, struct ubus_object *obj, 312 const char *method, struct ubus_msg_buf *ub, 313 struct blob_attr *data) 314 { 315 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 316 blob_put_string(&b, UBUS_ATTR_METHOD, method); 317 if (cl->user) 318 blob_put_string(&b, UBUS_ATTR_USER, cl->user); 319 if (cl->group) 320 blob_put_string(&b, UBUS_ATTR_GROUP, cl->group); 321 if (data) 322 blob_put(&b, UBUS_ATTR_DATA, blob_data(data), blob_len(data)); 323 324 ubus_proto_send_msg_from_blob(obj->client, ub, UBUS_MSG_INVOKE); 325 } 326 327 static int ubusd_handle_invoke(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 328 { 329 struct ubus_object *obj = NULL; 330 struct ubus_id *id; 331 const char *method; 332 333 if (!attr[UBUS_ATTR_METHOD] || !attr[UBUS_ATTR_OBJID]) 334 return UBUS_STATUS_INVALID_ARGUMENT; 335 336 id = ubus_find_id(&objects, blob_get_u32(attr[UBUS_ATTR_OBJID])); 337 if (!id) 338 return UBUS_STATUS_NOT_FOUND; 339 340 obj = container_of(id, struct ubus_object, id); 341 342 method = blob_data(attr[UBUS_ATTR_METHOD]); 343 344 if (ubusd_acl_check(cl, obj->path.key, method, UBUS_ACL_ACCESS)) 345 return UBUS_STATUS_PERMISSION_DENIED; 346 347 if (!obj->client) 348 return obj->recv_msg(cl, ub, method, attr[UBUS_ATTR_DATA]); 349 350 ub->hdr.peer = cl->id.id; 351 blob_buf_init(&b, 0); 352 353 ubusd_forward_invoke(cl, obj, method, ub, attr[UBUS_ATTR_DATA]); 354 355 return -1; 356 } 357 358 static int ubusd_handle_notify(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 359 { 360 struct ubus_object *obj = NULL; 361 struct ubus_subscription *s; 362 struct ubus_id *id; 363 const char *method; 364 bool no_reply = false; 365 void *c; 366 367 if (!attr[UBUS_ATTR_METHOD] || !attr[UBUS_ATTR_OBJID]) 368 return UBUS_STATUS_INVALID_ARGUMENT; 369 370 if (attr[UBUS_ATTR_NO_REPLY]) 371 no_reply = blob_get_int8(attr[UBUS_ATTR_NO_REPLY]); 372 373 id = ubus_find_id(&objects, blob_get_u32(attr[UBUS_ATTR_OBJID])); 374 if (!id) 375 return UBUS_STATUS_NOT_FOUND; 376 377 obj = container_of(id, struct ubus_object, id); 378 if (obj->client != cl) 379 return UBUS_STATUS_PERMISSION_DENIED; 380 381 if (!no_reply) { 382 blob_buf_init(&b, 0); 383 blob_put_int32(&b, UBUS_ATTR_OBJID, id->id); 384 c = blob_nest_start(&b, UBUS_ATTR_SUBSCRIBERS); 385 list_for_each_entry(s, &obj->subscribers, list) { 386 blob_put_int32(&b, 0, s->subscriber->id.id); 387 } 388 blob_nest_end(&b, c); 389 blob_put_int32(&b, UBUS_ATTR_STATUS, 0); 390 ubus_proto_send_msg_from_blob(cl, ub, UBUS_MSG_STATUS); 391 } 392 393 ub->hdr.peer = cl->id.id; 394 method = blob_data(attr[UBUS_ATTR_METHOD]); 395 list_for_each_entry(s, &obj->subscribers, list) { 396 blob_buf_init(&b, 0); 397 if (no_reply) 398 blob_put_int8(&b, UBUS_ATTR_NO_REPLY, 1); 399 ubusd_forward_invoke(cl, s->subscriber, method, ub, attr[UBUS_ATTR_DATA]); 400 } 401 402 return -1; 403 } 404 405 static struct ubus_client *ubusd_get_client_by_id(uint32_t id) 406 { 407 struct ubus_id *clid; 408 409 clid = ubus_find_id(&clients, id); 410 if (!clid) 411 return NULL; 412 413 return container_of(clid, struct ubus_client, id); 414 } 415 416 static int ubusd_handle_response(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 417 { 418 struct ubus_object *obj; 419 420 if (!attr[UBUS_ATTR_OBJID] || 421 (ub->hdr.type == UBUS_MSG_STATUS && !attr[UBUS_ATTR_STATUS]) || 422 (ub->hdr.type == UBUS_MSG_DATA && !attr[UBUS_ATTR_DATA])) 423 goto out; 424 425 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 426 if (!obj) 427 goto out; 428 429 if (cl != obj->client) 430 goto out; 431 432 cl = ubusd_get_client_by_id(ub->hdr.peer); 433 if (!cl) 434 goto out; 435 436 ub->hdr.peer = blob_get_u32(attr[UBUS_ATTR_OBJID]); 437 ubus_msg_send(cl, ub); 438 out: 439 return -1; 440 } 441 442 static int ubusd_handle_add_watch(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 443 { 444 struct ubus_object *obj, *target; 445 446 if (!attr[UBUS_ATTR_OBJID] || !attr[UBUS_ATTR_TARGET]) 447 return UBUS_STATUS_INVALID_ARGUMENT; 448 449 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 450 if (!obj) 451 return UBUS_STATUS_NOT_FOUND; 452 453 if (cl != obj->client) 454 return UBUS_STATUS_INVALID_ARGUMENT; 455 456 target = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_TARGET])); 457 if (!target || !target->client) 458 return UBUS_STATUS_NOT_FOUND; 459 460 if (cl == target->client) 461 return UBUS_STATUS_INVALID_ARGUMENT; 462 463 if (!target->path.key) { 464 if (strcmp(target->client->user, cl->user) && strcmp(target->client->group, cl->group)) 465 return UBUS_STATUS_NOT_FOUND; 466 } else if (ubusd_acl_check(cl, target->path.key, NULL, UBUS_ACL_SUBSCRIBE)) { 467 return UBUS_STATUS_NOT_FOUND; 468 } 469 470 ubus_subscribe(obj, target); 471 return 0; 472 } 473 474 static int ubusd_handle_remove_watch(struct ubus_client *cl, struct ubus_msg_buf *ub, struct blob_attr **attr) 475 { 476 struct ubus_object *obj; 477 struct ubus_subscription *s; 478 uint32_t id; 479 480 if (!attr[UBUS_ATTR_OBJID] || !attr[UBUS_ATTR_TARGET]) 481 return UBUS_STATUS_INVALID_ARGUMENT; 482 483 obj = ubusd_find_object(blob_get_u32(attr[UBUS_ATTR_OBJID])); 484 if (!obj) 485 return UBUS_STATUS_NOT_FOUND; 486 487 if (cl != obj->client) 488 return UBUS_STATUS_INVALID_ARGUMENT; 489 490 id = blob_get_u32(attr[UBUS_ATTR_TARGET]); 491 list_for_each_entry(s, &obj->target_list, target_list) { 492 if (s->target->id.id != id) 493 continue; 494 495 ubus_unsubscribe(s); 496 return 0; 497 } 498 499 return UBUS_STATUS_NOT_FOUND; 500 } 501 502 static const ubus_cmd_cb handlers[__UBUS_MSG_LAST] = { 503 [UBUS_MSG_PING] = ubusd_send_pong, 504 [UBUS_MSG_ADD_OBJECT] = ubusd_handle_add_object, 505 [UBUS_MSG_REMOVE_OBJECT] = ubusd_handle_remove_object, 506 [UBUS_MSG_LOOKUP] = ubusd_handle_lookup, 507 [UBUS_MSG_INVOKE] = ubusd_handle_invoke, 508 [UBUS_MSG_STATUS] = ubusd_handle_response, 509 [UBUS_MSG_DATA] = ubusd_handle_response, 510 [UBUS_MSG_SUBSCRIBE] = ubusd_handle_add_watch, 511 [UBUS_MSG_UNSUBSCRIBE] = ubusd_handle_remove_watch, 512 [UBUS_MSG_NOTIFY] = ubusd_handle_notify, 513 }; 514 515 void ubusd_proto_receive_message(struct ubus_client *cl, struct ubus_msg_buf *ub) 516 { 517 ubus_cmd_cb cb = NULL; 518 int ret; 519 struct ubus_msg_buf *retmsg = cl->retmsg; 520 int *retmsg_data = blob_data(blob_data(retmsg->data)); 521 522 retmsg->hdr.seq = ub->hdr.seq; 523 retmsg->hdr.peer = ub->hdr.peer; 524 525 if (ub->hdr.type < __UBUS_MSG_LAST) 526 cb = handlers[ub->hdr.type]; 527 528 if (ub->hdr.type != UBUS_MSG_STATUS && ub->hdr.type != UBUS_MSG_INVOKE) 529 ubus_msg_close_fd(ub); 530 531 /* Note: no callback should free the `ub` buffer 532 that's always done right after the callback finishes */ 533 if (cb) 534 ret = cb(cl, ub, ubus_parse_msg(ub->data, blob_raw_len(ub->data))); 535 else 536 ret = UBUS_STATUS_INVALID_COMMAND; 537 538 /* Command has not been completed yet and got queued */ 539 if (ret == -2) 540 return; 541 542 ubus_msg_free(ub); 543 544 if (ret == -1) 545 return; 546 547 *retmsg_data = htonl(ret); 548 ubus_msg_send(cl, retmsg); 549 } 550 551 static int ubusd_proto_init_retmsg(struct ubus_client *cl) 552 { 553 struct blob_buf *b = &cl->b; 554 555 blob_buf_init(&cl->b, 0); 556 blob_put_int32(&cl->b, UBUS_ATTR_STATUS, 0); 557 558 /* we make the 'retmsg' buffer shared with the blob_buf b, to reduce mem duplication */ 559 cl->retmsg = ubus_msg_new(b->head, blob_raw_len(b->head), true); 560 if (!cl->retmsg) 561 return -1; 562 563 cl->retmsg->hdr.type = UBUS_MSG_STATUS; 564 return 0; 565 } 566 567 struct ubus_client *ubusd_proto_new_client(int fd, uloop_fd_handler cb) 568 { 569 struct ubus_client *cl; 570 571 cl = calloc(1, sizeof(*cl)); 572 if (!cl) 573 return NULL; 574 575 if (ubusd_acl_init_client(cl, fd)) 576 goto free; 577 578 INIT_LIST_HEAD(&cl->objects); 579 INIT_LIST_HEAD(&cl->cmd_queue); 580 INIT_LIST_HEAD(&cl->tx_queue); 581 cl->sock.fd = fd; 582 cl->sock.cb = cb; 583 cl->pending_msg_fd = -1; 584 585 if (!ubus_alloc_id(&clients, &cl->id, 0)) 586 goto free; 587 588 if (ubusd_proto_init_retmsg(cl)) 589 goto free; 590 591 if (!ubusd_send_hello(cl)) 592 goto delete; 593 594 return cl; 595 596 delete: 597 ubus_free_id(&clients, &cl->id); 598 free: 599 free(cl); 600 return NULL; 601 } 602 603 void ubusd_proto_free_client(struct ubus_client *cl) 604 { 605 struct ubus_object *obj, *tmp; 606 607 list_for_each_entry_safe(obj, tmp, &cl->objects, list) { 608 ubusd_free_object(obj); 609 } 610 611 ubus_msg_free(cl->retmsg); 612 blob_buf_free(&cl->b); 613 614 ubusd_acl_free_client(cl); 615 ubus_free_id(&clients, &cl->id); 616 } 617 618 void ubus_notify_subscription(struct ubus_object *obj) 619 { 620 bool active = !list_empty(&obj->subscribers); 621 struct ubus_msg_buf *ub; 622 623 blob_buf_init(&b, 0); 624 blob_put_int32(&b, UBUS_ATTR_OBJID, obj->id.id); 625 blob_put_int8(&b, UBUS_ATTR_ACTIVE, active); 626 627 ub = ubus_msg_from_blob(false); 628 if (!ub) 629 return; 630 631 ubus_msg_init(ub, UBUS_MSG_NOTIFY, ++obj->invoke_seq, 0); 632 ubus_msg_send(obj->client, ub); 633 ubus_msg_free(ub); 634 } 635 636 void ubus_notify_unsubscribe(struct ubus_subscription *s) 637 { 638 struct ubus_msg_buf *ub; 639 640 blob_buf_init(&b, 0); 641 blob_put_int32(&b, UBUS_ATTR_OBJID, s->subscriber->id.id); 642 blob_put_int32(&b, UBUS_ATTR_TARGET, s->target->id.id); 643 644 ub = ubus_msg_from_blob(false); 645 if (ub != NULL) { 646 ubus_msg_init(ub, UBUS_MSG_UNSUBSCRIBE, ++s->subscriber->invoke_seq, 0); 647 ubus_msg_send(s->subscriber->client, ub); 648 ubus_msg_free(ub); 649 } 650 651 ubus_unsubscribe(s); 652 } 653 654 static void __constructor ubusd_proto_init(void) 655 { 656 ubus_init_id_tree(&clients); 657 } 658
This page was automatically generated by LXR 0.3.1. • OpenWrt