1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #define TRACE_TAG TRACE_SERVICES 18 19 #include "sysdeps.h" 20 21 #include <errno.h> 22 #include <stddef.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 27 #ifndef _WIN32 28 #include <netdb.h> 29 #include <netinet/in.h> 30 #include <sys/ioctl.h> 31 #include <unistd.h> 32 #endif 33 34 #include <base/file.h> 35 #include <base/stringprintf.h> 36 #include <base/strings.h> 37 38 #if !ADB_HOST 39 #include "cutils/android_reboot.h" 40 #include "cutils/properties.h" 41 #endif 42 43 #include "adb.h" 44 #include "adb_io.h" 45 #include "file_sync_service.h" 46 #include "remount_service.h" 47 #include "transport.h" 48 49 struct stinfo { 50 void (*func)(int fd, void *cookie); 51 int fd; 52 void *cookie; 53 }; 54 55 56 void *service_bootstrap_func(void *x) 57 { 58 stinfo* sti = reinterpret_cast<stinfo*>(x); 59 sti->func(sti->fd, sti->cookie); 60 free(sti); 61 return 0; 62 } 63 64 #if !ADB_HOST 65 66 void restart_root_service(int fd, void *cookie) { 67 if (getuid() == 0) { 68 WriteFdExactly(fd, "adbd is already running as root\n"); 69 adb_close(fd); 70 } else { 71 char value[PROPERTY_VALUE_MAX]; 72 property_get("ro.debuggable", value, ""); 73 if (strcmp(value, "1") != 0) { 74 WriteFdExactly(fd, "adbd cannot run as root in production builds\n"); 75 adb_close(fd); 76 return; 77 } 78 79 property_set("service.adb.root", "1"); 80 WriteFdExactly(fd, "restarting adbd as root\n"); 81 adb_close(fd); 82 } 83 } 84 85 void restart_unroot_service(int fd, void *cookie) { 86 if (getuid() != 0) { 87 WriteFdExactly(fd, "adbd not running as root\n"); 88 adb_close(fd); 89 } else { 90 property_set("service.adb.root", "0"); 91 WriteFdExactly(fd, "restarting adbd as non root\n"); 92 adb_close(fd); 93 } 94 } 95 96 void restart_tcp_service(int fd, void *cookie) { 97 int port = (int) (uintptr_t) cookie; 98 if (port <= 0) { 99 WriteFdFmt(fd, "invalid port %d\n", port); 100 adb_close(fd); 101 return; 102 } 103 104 char value[PROPERTY_VALUE_MAX]; 105 snprintf(value, sizeof(value), "%d", port); 106 property_set("service.adb.tcp.port", value); 107 WriteFdFmt(fd, "restarting in TCP mode port: %d\n", port); 108 adb_close(fd); 109 } 110 111 void restart_usb_service(int fd, void *cookie) { 112 property_set("service.adb.tcp.port", "0"); 113 WriteFdExactly(fd, "restarting in USB mode\n"); 114 adb_close(fd); 115 } 116 117 static bool reboot_service_impl(int fd, const char* arg) { 118 const char* reboot_arg = arg; 119 bool auto_reboot = false; 120 121 if (strcmp(reboot_arg, "sideload-auto-reboot") == 0) { 122 auto_reboot = true; 123 reboot_arg = "sideload"; 124 } 125 126 // It reboots into sideload mode by setting "--sideload" or "--sideload_auto_reboot" 127 // in the command file. 128 if (strcmp(reboot_arg, "sideload") == 0) { 129 if (getuid() != 0) { 130 WriteFdExactly(fd, "'adb root' is required for 'adb reboot sideload'.\n"); 131 return false; 132 } 133 134 const char* const recovery_dir = "/cache/recovery"; 135 const char* const command_file = "/cache/recovery/command"; 136 // Ensure /cache/recovery exists. 137 if (adb_mkdir(recovery_dir, 0770) == -1 && errno != EEXIST) { 138 D("Failed to create directory '%s': %s\n", recovery_dir, strerror(errno)); 139 return false; 140 } 141 142 bool write_status = android::base::WriteStringToFile( 143 auto_reboot ? "--sideload_auto_reboot" : "--sideload", command_file); 144 if (!write_status) { 145 return false; 146 } 147 148 reboot_arg = "recovery"; 149 } 150 151 sync(); 152 153 char property_val[PROPERTY_VALUE_MAX]; 154 int ret = snprintf(property_val, sizeof(property_val), "reboot,%s", reboot_arg); 155 if (ret >= static_cast<int>(sizeof(property_val))) { 156 WriteFdFmt(fd, "reboot string too long: %d\n", ret); 157 return false; 158 } 159 160 ret = property_set(ANDROID_RB_PROPERTY, property_val); 161 if (ret < 0) { 162 WriteFdFmt(fd, "reboot failed: %d\n", ret); 163 return false; 164 } 165 166 return true; 167 } 168 169 void reboot_service(int fd, void* arg) 170 { 171 if (reboot_service_impl(fd, static_cast<const char*>(arg))) { 172 // Don't return early. Give the reboot command time to take effect 173 // to avoid messing up scripts which do "adb reboot && adb wait-for-device" 174 while (true) { 175 pause(); 176 } 177 } 178 179 free(arg); 180 adb_close(fd); 181 } 182 183 void reverse_service(int fd, void* arg) 184 { 185 const char* command = reinterpret_cast<const char*>(arg); 186 187 if (handle_forward_request(command, kTransportAny, NULL, fd) < 0) { 188 SendFail(fd, "not a reverse forwarding command"); 189 } 190 free(arg); 191 adb_close(fd); 192 } 193 194 #endif 195 196 static int create_service_thread(void (*func)(int, void *), void *cookie) 197 { 198 int s[2]; 199 if (adb_socketpair(s)) { 200 printf("cannot create service socket pair\n"); 201 return -1; 202 } 203 D("socketpair: (%d,%d)", s[0], s[1]); 204 205 stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo))); 206 if (sti == nullptr) { 207 fatal("cannot allocate stinfo"); 208 } 209 sti->func = func; 210 sti->cookie = cookie; 211 sti->fd = s[1]; 212 213 adb_thread_t t; 214 if (adb_thread_create(&t, service_bootstrap_func, sti)) { 215 free(sti); 216 adb_close(s[0]); 217 adb_close(s[1]); 218 printf("cannot create service thread\n"); 219 return -1; 220 } 221 222 D("service thread started, %d:%d\n",s[0], s[1]); 223 return s[0]; 224 } 225 226 #if !ADB_HOST 227 228 static void init_subproc_child() 229 { 230 setsid(); 231 232 // Set OOM score adjustment to prevent killing 233 int fd = adb_open("/proc/self/oom_score_adj", O_WRONLY | O_CLOEXEC); 234 if (fd >= 0) { 235 adb_write(fd, "0", 1); 236 adb_close(fd); 237 } else { 238 D("adb: unable to update oom_score_adj\n"); 239 } 240 } 241 242 static int create_subproc_pty(const char *cmd, const char *arg0, const char *arg1, pid_t *pid) 243 { 244 D("create_subproc_pty(cmd=%s, arg0=%s, arg1=%s)\n", cmd, arg0, arg1); 245 #if defined(_WIN32) 246 fprintf(stderr, "error: create_subproc_pty not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1); 247 return -1; 248 #else 249 int ptm; 250 251 ptm = unix_open("/dev/ptmx", O_RDWR | O_CLOEXEC); // | O_NOCTTY); 252 if(ptm < 0){ 253 printf("[ cannot open /dev/ptmx - %s ]\n",strerror(errno)); 254 return -1; 255 } 256 257 char devname[64]; 258 if(grantpt(ptm) || unlockpt(ptm) || ptsname_r(ptm, devname, sizeof(devname)) != 0) { 259 printf("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); 260 adb_close(ptm); 261 return -1; 262 } 263 264 *pid = fork(); 265 if(*pid < 0) { 266 printf("- fork failed: %s -\n", strerror(errno)); 267 adb_close(ptm); 268 return -1; 269 } 270 271 if (*pid == 0) { 272 init_subproc_child(); 273 274 int pts = unix_open(devname, O_RDWR | O_CLOEXEC); 275 if (pts < 0) { 276 fprintf(stderr, "child failed to open pseudo-term slave: %s\n", devname); 277 exit(-1); 278 } 279 280 dup2(pts, STDIN_FILENO); 281 dup2(pts, STDOUT_FILENO); 282 dup2(pts, STDERR_FILENO); 283 284 adb_close(pts); 285 adb_close(ptm); 286 287 execl(cmd, cmd, arg0, arg1, NULL); 288 fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", 289 cmd, strerror(errno), errno); 290 exit(-1); 291 } else { 292 return ptm; 293 } 294 #endif /* !defined(_WIN32) */ 295 } 296 297 static int create_subproc_raw(const char *cmd, const char *arg0, const char *arg1, pid_t *pid) 298 { 299 D("create_subproc_raw(cmd=%s, arg0=%s, arg1=%s)\n", cmd, arg0, arg1); 300 #if defined(_WIN32) 301 fprintf(stderr, "error: create_subproc_raw not implemented on Win32 (%s %s %s)\n", cmd, arg0, arg1); 302 return -1; 303 #else 304 305 // 0 is parent socket, 1 is child socket 306 int sv[2]; 307 if (adb_socketpair(sv) < 0) { 308 printf("[ cannot create socket pair - %s ]\n", strerror(errno)); 309 return -1; 310 } 311 D("socketpair: (%d,%d)", sv[0], sv[1]); 312 313 *pid = fork(); 314 if (*pid < 0) { 315 printf("- fork failed: %s -\n", strerror(errno)); 316 adb_close(sv[0]); 317 adb_close(sv[1]); 318 return -1; 319 } 320 321 if (*pid == 0) { 322 adb_close(sv[0]); 323 init_subproc_child(); 324 325 dup2(sv[1], STDIN_FILENO); 326 dup2(sv[1], STDOUT_FILENO); 327 dup2(sv[1], STDERR_FILENO); 328 329 adb_close(sv[1]); 330 331 execl(cmd, cmd, arg0, arg1, NULL); 332 fprintf(stderr, "- exec '%s' failed: %s (%d) -\n", 333 cmd, strerror(errno), errno); 334 exit(-1); 335 } else { 336 adb_close(sv[1]); 337 return sv[0]; 338 } 339 #endif /* !defined(_WIN32) */ 340 } 341 #endif /* !ABD_HOST */ 342 343 #if ADB_HOST 344 #define SHELL_COMMAND "/bin/sh" 345 #else 346 #define SHELL_COMMAND "/system/bin/sh" 347 #endif 348 349 #if !ADB_HOST 350 static void subproc_waiter_service(int fd, void *cookie) 351 { 352 pid_t pid = (pid_t) (uintptr_t) cookie; 353 354 D("entered. fd=%d of pid=%d\n", fd, pid); 355 while (true) { 356 int status; 357 pid_t p = waitpid(pid, &status, 0); 358 if (p == pid) { 359 D("fd=%d, post waitpid(pid=%d) status=%04x\n", fd, p, status); 360 if (WIFSIGNALED(status)) { 361 D("*** Killed by signal %d\n", WTERMSIG(status)); 362 break; 363 } else if (!WIFEXITED(status)) { 364 D("*** Didn't exit!!. status %d\n", status); 365 break; 366 } else if (WEXITSTATUS(status) >= 0) { 367 D("*** Exit code %d\n", WEXITSTATUS(status)); 368 break; 369 } 370 } 371 } 372 D("shell exited fd=%d of pid=%d err=%d\n", fd, pid, errno); 373 if (SHELL_EXIT_NOTIFY_FD >=0) { 374 int res; 375 res = WriteFdExactly(SHELL_EXIT_NOTIFY_FD, &fd, sizeof(fd)) ? 0 : -1; 376 D("notified shell exit via fd=%d for pid=%d res=%d errno=%d\n", 377 SHELL_EXIT_NOTIFY_FD, pid, res, errno); 378 } 379 } 380 381 static int create_subproc_thread(const char *name, const subproc_mode mode) 382 { 383 adb_thread_t t; 384 int ret_fd; 385 pid_t pid = -1; 386 387 const char *arg0, *arg1; 388 if (name == 0 || *name == 0) { 389 arg0 = "-"; arg1 = 0; 390 } else { 391 arg0 = "-c"; arg1 = name; 392 } 393 394 switch (mode) { 395 case SUBPROC_PTY: 396 ret_fd = create_subproc_pty(SHELL_COMMAND, arg0, arg1, &pid); 397 break; 398 case SUBPROC_RAW: 399 ret_fd = create_subproc_raw(SHELL_COMMAND, arg0, arg1, &pid); 400 break; 401 default: 402 fprintf(stderr, "invalid subproc_mode %d\n", mode); 403 return -1; 404 } 405 D("create_subproc ret_fd=%d pid=%d\n", ret_fd, pid); 406 407 stinfo* sti = reinterpret_cast<stinfo*>(malloc(sizeof(stinfo))); 408 if(sti == 0) fatal("cannot allocate stinfo"); 409 sti->func = subproc_waiter_service; 410 sti->cookie = (void*) (uintptr_t) pid; 411 sti->fd = ret_fd; 412 413 if (adb_thread_create(&t, service_bootstrap_func, sti)) { 414 free(sti); 415 adb_close(ret_fd); 416 fprintf(stderr, "cannot create service thread\n"); 417 return -1; 418 } 419 420 D("service thread started, fd=%d pid=%d\n", ret_fd, pid); 421 return ret_fd; 422 } 423 #endif 424 425 int service_to_fd(const char *name) 426 { 427 int ret = -1; 428 429 if(!strncmp(name, "tcp:", 4)) { 430 int port = atoi(name + 4); 431 name = strchr(name + 4, ':'); 432 if(name == 0) { 433 ret = socket_loopback_client(port, SOCK_STREAM); 434 if (ret >= 0) 435 disable_tcp_nagle(ret); 436 } else { 437 #if ADB_HOST 438 ret = socket_network_client(name + 1, port, SOCK_STREAM); 439 #else 440 return -1; 441 #endif 442 } 443 #ifndef HAVE_WINSOCK /* winsock doesn't implement unix domain sockets */ 444 } else if(!strncmp(name, "local:", 6)) { 445 ret = socket_local_client(name + 6, 446 ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); 447 } else if(!strncmp(name, "localreserved:", 14)) { 448 ret = socket_local_client(name + 14, 449 ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM); 450 } else if(!strncmp(name, "localabstract:", 14)) { 451 ret = socket_local_client(name + 14, 452 ANDROID_SOCKET_NAMESPACE_ABSTRACT, SOCK_STREAM); 453 } else if(!strncmp(name, "localfilesystem:", 16)) { 454 ret = socket_local_client(name + 16, 455 ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM); 456 #endif 457 #if !ADB_HOST 458 } else if(!strncmp("dev:", name, 4)) { 459 ret = unix_open(name + 4, O_RDWR | O_CLOEXEC); 460 } else if(!strncmp(name, "framebuffer:", 12)) { 461 ret = create_service_thread(framebuffer_service, 0); 462 } else if (!strncmp(name, "jdwp:", 5)) { 463 ret = create_jdwp_connection_fd(atoi(name+5)); 464 } else if(!HOST && !strncmp(name, "shell:", 6)) { 465 ret = create_subproc_thread(name + 6, SUBPROC_PTY); 466 } else if(!HOST && !strncmp(name, "exec:", 5)) { 467 ret = create_subproc_thread(name + 5, SUBPROC_RAW); 468 } else if(!strncmp(name, "sync:", 5)) { 469 ret = create_service_thread(file_sync_service, NULL); 470 } else if(!strncmp(name, "remount:", 8)) { 471 ret = create_service_thread(remount_service, NULL); 472 } else if(!strncmp(name, "reboot:", 7)) { 473 void* arg = strdup(name + 7); 474 if (arg == NULL) return -1; 475 ret = create_service_thread(reboot_service, arg); 476 } else if(!strncmp(name, "root:", 5)) { 477 ret = create_service_thread(restart_root_service, NULL); 478 } else if(!strncmp(name, "unroot:", 7)) { 479 ret = create_service_thread(restart_unroot_service, NULL); 480 } else if(!strncmp(name, "backup:", 7)) { 481 ret = create_subproc_thread(android::base::StringPrintf("/system/bin/bu backup %s", 482 (name + 7)).c_str(), SUBPROC_RAW); 483 } else if(!strncmp(name, "restore:", 8)) { 484 ret = create_subproc_thread("/system/bin/bu restore", SUBPROC_RAW); 485 } else if(!strncmp(name, "tcpip:", 6)) { 486 int port; 487 if (sscanf(name + 6, "%d", &port) != 1) { 488 port = 0; 489 } 490 ret = create_service_thread(restart_tcp_service, (void *) (uintptr_t) port); 491 } else if(!strncmp(name, "usb:", 4)) { 492 ret = create_service_thread(restart_usb_service, NULL); 493 } else if (!strncmp(name, "reverse:", 8)) { 494 char* cookie = strdup(name + 8); 495 if (cookie == NULL) { 496 ret = -1; 497 } else { 498 ret = create_service_thread(reverse_service, cookie); 499 if (ret < 0) { 500 free(cookie); 501 } 502 } 503 } else if(!strncmp(name, "disable-verity:", 15)) { 504 ret = create_service_thread(set_verity_enabled_state_service, (void*)0); 505 } else if(!strncmp(name, "enable-verity:", 15)) { 506 ret = create_service_thread(set_verity_enabled_state_service, (void*)1); 507 #endif 508 } 509 if (ret >= 0) { 510 close_on_exec(ret); 511 } 512 return ret; 513 } 514 515 #if ADB_HOST 516 struct state_info { 517 transport_type transport; 518 char* serial; 519 int state; 520 }; 521 522 static void wait_for_state(int fd, void* cookie) 523 { 524 state_info* sinfo = reinterpret_cast<state_info*>(cookie); 525 526 D("wait_for_state %d\n", sinfo->state); 527 528 std::string error_msg = "unknown error"; 529 atransport* t = acquire_one_transport(sinfo->state, sinfo->transport, sinfo->serial, &error_msg); 530 if (t != 0) { 531 SendOkay(fd); 532 } else { 533 SendFail(fd, error_msg); 534 } 535 536 if (sinfo->serial) 537 free(sinfo->serial); 538 free(sinfo); 539 adb_close(fd); 540 D("wait_for_state is done\n"); 541 } 542 543 static void connect_device(const std::string& host, std::string* response) { 544 if (host.empty()) { 545 *response = "empty host name"; 546 return; 547 } 548 549 std::vector<std::string> pieces = android::base::Split(host, ":"); 550 const std::string& hostname = pieces[0]; 551 552 int port = DEFAULT_ADB_LOCAL_TRANSPORT_PORT; 553 if (pieces.size() > 1) { 554 if (sscanf(pieces[1].c_str(), "%d", &port) != 1) { 555 *response = android::base::StringPrintf("bad port number %s", pieces[1].c_str()); 556 return; 557 } 558 } 559 560 // This may look like we're putting 'host' back together, 561 // but we're actually inserting the default port if necessary. 562 std::string serial = android::base::StringPrintf("%s:%d", hostname.c_str(), port); 563 564 int fd = socket_network_client_timeout(hostname.c_str(), port, SOCK_STREAM, 10); 565 if (fd < 0) { 566 *response = android::base::StringPrintf("unable to connect to %s:%d", 567 hostname.c_str(), port); 568 return; 569 } 570 571 D("client: connected on remote on fd %d\n", fd); 572 close_on_exec(fd); 573 disable_tcp_nagle(fd); 574 575 int ret = register_socket_transport(fd, serial.c_str(), port, 0); 576 if (ret < 0) { 577 adb_close(fd); 578 *response = android::base::StringPrintf("already connected to %s", serial.c_str()); 579 } else { 580 *response = android::base::StringPrintf("connected to %s", serial.c_str()); 581 } 582 } 583 584 void connect_emulator(const std::string& port_spec, std::string* response) { 585 std::vector<std::string> pieces = android::base::Split(port_spec, ","); 586 if (pieces.size() != 2) { 587 *response = android::base::StringPrintf("unable to parse '%s' as <console port>,<adb port>", 588 port_spec.c_str()); 589 return; 590 } 591 592 int console_port = strtol(pieces[0].c_str(), NULL, 0); 593 int adb_port = strtol(pieces[1].c_str(), NULL, 0); 594 if (console_port <= 0 || adb_port <= 0) { 595 *response = android::base::StringPrintf("Invalid port numbers: %s", port_spec.c_str()); 596 return; 597 } 598 599 // Check if the emulator is already known. 600 // Note: There's a small but harmless race condition here: An emulator not 601 // present just yet could be registered by another invocation right 602 // after doing this check here. However, local_connect protects 603 // against double-registration too. From here, a better error message 604 // can be produced. In the case of the race condition, the very specific 605 // error message won't be shown, but the data doesn't get corrupted. 606 atransport* known_emulator = find_emulator_transport_by_adb_port(adb_port); 607 if (known_emulator != nullptr) { 608 *response = android::base::StringPrintf("Emulator already registered on port %d", adb_port); 609 return; 610 } 611 612 // Check if more emulators can be registered. Similar unproblematic 613 // race condition as above. 614 int candidate_slot = get_available_local_transport_index(); 615 if (candidate_slot < 0) { 616 *response = "Cannot accept more emulators"; 617 return; 618 } 619 620 // Preconditions met, try to connect to the emulator. 621 if (!local_connect_arbitrary_ports(console_port, adb_port)) { 622 *response = android::base::StringPrintf("Connected to emulator on ports %d,%d", 623 console_port, adb_port); 624 } else { 625 *response = android::base::StringPrintf("Could not connect to emulator on ports %d,%d", 626 console_port, adb_port); 627 } 628 } 629 630 static void connect_service(int fd, void* cookie) 631 { 632 char *host = reinterpret_cast<char*>(cookie); 633 634 std::string response; 635 if (!strncmp(host, "emu:", 4)) { 636 connect_emulator(host + 4, &response); 637 } else { 638 connect_device(host, &response); 639 } 640 641 // Send response for emulator and device 642 SendProtocolString(fd, response); 643 adb_close(fd); 644 } 645 #endif 646 647 #if ADB_HOST 648 asocket* host_service_to_socket(const char* name, const char *serial) 649 { 650 if (!strcmp(name,"track-devices")) { 651 return create_device_tracker(); 652 } else if (!strncmp(name, "wait-for-", strlen("wait-for-"))) { 653 auto sinfo = reinterpret_cast<state_info*>(malloc(sizeof(state_info))); 654 if (sinfo == nullptr) { 655 fprintf(stderr, "couldn't allocate state_info: %s", strerror(errno)); 656 return NULL; 657 } 658 659 if (serial) 660 sinfo->serial = strdup(serial); 661 else 662 sinfo->serial = NULL; 663 664 name += strlen("wait-for-"); 665 666 if (!strncmp(name, "local", strlen("local"))) { 667 sinfo->transport = kTransportLocal; 668 sinfo->state = CS_DEVICE; 669 } else if (!strncmp(name, "usb", strlen("usb"))) { 670 sinfo->transport = kTransportUsb; 671 sinfo->state = CS_DEVICE; 672 } else if (!strncmp(name, "any", strlen("any"))) { 673 sinfo->transport = kTransportAny; 674 sinfo->state = CS_DEVICE; 675 } else { 676 free(sinfo); 677 return NULL; 678 } 679 680 int fd = create_service_thread(wait_for_state, sinfo); 681 return create_local_socket(fd); 682 } else if (!strncmp(name, "connect:", 8)) { 683 const char *host = name + 8; 684 int fd = create_service_thread(connect_service, (void *)host); 685 return create_local_socket(fd); 686 } 687 return NULL; 688 } 689 #endif /* ADB_HOST */ 690