Ejemplo n.º 1
0
 async def run_with_client(self, args: Namespace, client: Client) -> None:
     artifact: Optional[InstalledArtifact] = None
     compression = (Compression[args.compression]
                    if args.compression is not None else None)
     async for info in client.install(
             bundle=args.bundle_path,
             make_debuggable=args.make_debuggable,
             compression=compression,
             override_modification_time=args.override_mtime,
     ):
         artifact = info
         progress = info.progress
         if progress is None:
             continue
         self.logger.info(f"Progress: {progress}")
     if args.json:
         print(
             json.dumps({
                 "installedAppBundleId": none_throws(artifact).name,
                 "uuid": none_throws(artifact).uuid,
             }))
     else:
         print(
             f"Installed: {none_throws(artifact).name} {none_throws(artifact).uuid}"
         )
Ejemplo n.º 2
0
 async def record_video(self, stop: asyncio.Event, output_file: str) -> None:
     self.logger.info(f"Starting connection to backend")
     async with self.get_stub() as stub, stub.record.open() as stream:
         if none_throws(self.companion_info).is_local:
             self.logger.info(
                 f"Starting video recording to local file {output_file}"
             )
             await stream.send_message(
                 RecordRequest(start=RecordRequest.Start(file_path=output_file))
             )
         else:
             self.logger.info(f"Starting video recording with response data")
             await stream.send_message(
                 RecordRequest(start=RecordRequest.Start(file_path=None))
             )
         await stop.wait()
         self.logger.info("Stopping video recording")
         await stream.send_message(RecordRequest(stop=RecordRequest.Stop()))
         await stream.end()
         if none_throws(self.companion_info).is_local:
             self.logger.info("Video saved at output path")
             await stream.recv_message()
         else:
             self.logger.info(f"Decompressing gzip to {output_file}")
             await drain_gzip_decompress(
                 generate_video_bytes(stream), output_path=output_file
             )
             self.logger.info(f"Finished decompression to {output_file}")
Ejemplo n.º 3
0
async def daemon(
    client: CompanionClient, stream: Stream[InstallResponse, InstallRequest]
) -> None:
    destination_message = none_throws(await stream.recv_message())
    payload_message = none_throws(await stream.recv_message())
    file_path = payload_message.payload.file_path
    url = payload_message.payload.url
    data = payload_message.payload.data
    destination = destination_message.destination
    async with client.stub.install.open() as forward_stream:
        await forward_stream.send_message(destination_message)
        if client.is_local or len(url):
            await forward_stream.send_message(payload_message)
            await forward_stream.end()
            response = none_throws(await forward_stream.recv_message())
        elif file_path:
            response = await drain_to_stream(
                stream=forward_stream,
                generator=_generate_binary_chunks(
                    path=file_path, destination=destination, logger=client.logger
                ),
                logger=client.logger,
            )
        elif data:
            await forward_stream.send_message(payload_message)
            response = await drain_to_stream(
                stream=forward_stream, generator=stream, logger=client.logger
            )
        else:
            raise Exception(f"Unrecognised payload message")
        await stream.send_message(response)
Ejemplo n.º 4
0
    async def pipe(
        self,
        input_stream: StreamReader,
        output_stream: StreamWriter,
        stop: asyncio.Event,
    ) -> None:
        """
        Pipe stdin and stdout to remote dap server
        """
        read_future: Optional[asyncio.Future[StreamReader]] = None
        write_future: Optional[asyncio.Future[StreamWriter]] = None
        stop_future = asyncio.ensure_future(stop.wait())
        while True:
            if read_future is None:
                read_future = asyncio.ensure_future(self._stream.recv_message())
            if write_future is None:
                write_future = asyncio.ensure_future(
                    read_next_dap_protocol_message(input_stream)
                )

            done, pending = await asyncio.wait(
                [read_future, write_future, stop_future],
                return_when=asyncio.FIRST_COMPLETED,
            )

            if stop_future in done:
                self.logger.debug("Received stop command! Closing stream...")
                read_future.cancel()
                self.logger.debug("Read future cancelled!")
                write_future.cancel()
                self.logger.debug("Write future cancelled!")
                break

            if write_future in done:
                data = none_throws(write_future.result())
                write_future = None
                await self._stream.send_message(
                    DapRequest(pipe=DapRequest.Pipe(data=data))
                )

            if read_future in done:
                self.logger.debug("Received a message from companion.")
                result = none_throws(read_future.result())
                read_future = None
                if result is None:
                    # Reached the end of the stream
                    break
                output_stream.write(result.stdout.data)
 async def notifierProcess(self) -> asyncio.subprocess.Process:
     cmd = [self.notifier_path, "--notify", "1"]
     with open(self._log_file_path(), "a") as log_file:
         process = await asyncio.create_subprocess_exec(
             *cmd, stdout=asyncio.subprocess.PIPE, stderr=log_file)
         await self._read_stream(none_throws(process.stdout))
         return process
Ejemplo n.º 6
0
    async def _install_to_destination(
        self, bundle: Bundle, destination: Destination
    ) -> InstalledArtifact:
        async with self.get_stub() as stub, stub.install.open() as stream:
            generator = None
            if isinstance(bundle, str):
                url = urllib.parse.urlparse(bundle)
                if url.scheme:
                    # send url
                    payload = Payload(url=bundle)
                    generator = generate_requests([InstallRequest(payload=payload)])

                else:
                    file_path = str(Path(bundle).resolve(strict=True))
                    if none_throws(self.companion_info).is_local:
                        # send file_path
                        generator = generate_requests(
                            [InstallRequest(payload=Payload(file_path=file_path))]
                        )
                    else:
                        # chunk file from file_path
                        generator = generate_binary_chunks(
                            path=file_path, destination=destination, logger=self.logger
                        )

            else:
                # chunk file from memory
                generator = generate_io_chunks(io=bundle, logger=self.logger)
                # stream to companion
            await stream.send_message(InstallRequest(destination=destination))
            response = await drain_to_stream(
                stream=stream, generator=generator, logger=self.logger
            )
            return InstalledArtifact(name=response.name, uuid=response.uuid)
Ejemplo n.º 7
0
 async def run_with_client(self, args: Namespace, client: Client) -> None:
     # Setup
     root_command = none_throws(self.root_command)
     prompt = "" if args.no_prompt else "idb> "
     # Prompt loop
     while True:
         sys.stdout.flush()
         sys.stderr.flush()
         new_args = shlex.split(input(prompt))
         # Special shell commands
         if new_args == ["exit"]:
             return
         # Run the specified command
         try:
             args = self.parser.parse_args(new_args)
             command = root_command.resolve_command_from_args(args)
             if not isinstance(command, ClientCommand):
                 print("shell commands must be client commands",
                       file=sys.stderr)
                 continue
             await command.run_with_client(args, client)
             print("SUCCESS=1")
         except IdbException as e:
             print(e.args[0], file=sys.stderr)
             print("SUCCESS=0")
Ejemplo n.º 8
0
 async def get_stub(self) -> AsyncContextManager[CompanionServiceStub]:
     await self.spawn_notifier()
     channel: Optional[Channel] = None
     try:
         try:
             self.companion_info = self.direct_companion_manager.get_companion_info(
                 target_udid=self.target_udid
             )
         except IdbException as e:
             # will try to spawn a companion if on mac.
             companion_info = await self.spawn_companion(
                 target_udid=none_throws(self.target_udid)
             )
             if companion_info:
                 self.companion_info = companion_info
             else:
                 raise e
         self.logger.info(f"using companion {self.companion_info}")
         channel = Channel(
             # pyre-fixme[16]: `Optional` has no attribute `host`.
             self.companion_info.host,
             # pyre-fixme[16]: `Optional` has no attribute `port`.
             self.companion_info.port,
             loop=asyncio.get_event_loop(),
         )
         yield CompanionServiceStub(channel=channel)
     finally:
         if channel:
             channel.close()
Ejemplo n.º 9
0
 async def spawn_domain_sock_server(
         self, config: CompanionServerConfig,
         path: str) -> asyncio.subprocess.Process:
     (process, log_file_path) = await self._spawn_server(
         config=config,
         port_env_variables={},
         bind_arguments=["--grpc-domain-sock", path],
     )
     stdout = none_throws(process.stdout)
     try:
         extracted_path = await _extract_domain_sock_from_spawned_companion(
             stream=stdout, log_file_path=log_file_path)
     except Exception as e:
         raise CompanionSpawnerException(
             f"Failed to spawn companion, couldn't read domain socket path "
             f"stderr: {get_last_n_lines(log_file_path, 30)}") from e
     if not extracted_path:
         raise CompanionSpawnerException(
             f"Failed to spawn companion, no extracted domain socket path "
             f"stderr: {get_last_n_lines(log_file_path, 30)}")
     if extracted_path != path:
         raise CompanionSpawnerException(
             "Failed to spawn companion, extracted domain socket path "
             f"is not correct (expected {path} got {extracted_path})"
             f"stderr: {get_last_n_lines(log_file_path, 30)}")
     return process
Ejemplo n.º 10
0
async def daemon(client: CompanionClient,
                 stream: Stream[RecordResponse, RecordRequest]) -> None:
    client.logger.info(f"Starting connection to backend")
    request = await stream.recv_message()
    output_file = none_throws(request).start.file_path
    async with client.stub.record.open() as forward_stream:
        if client.is_local:
            client.logger.info(
                f"Starting video recording to local file {output_file}")
            await forward_stream.send_message(
                RecordRequest(start=Start(file_path=output_file)))
        else:
            client.logger.info(f"Starting video recording with response data")
            await forward_stream.send_message(
                RecordRequest(start=Start(file_path=None)))
        client.logger.info("Request sent")
        await stream.recv_message()
        client.logger.info("Stopping video recording")
        await forward_stream.send_message(RecordRequest(stop=Stop()))
        await forward_stream.stream.end()
        if client.is_local:
            client.logger.info("Responding with file path")
            response = await forward_stream.recv_message()
            await stream.send_message(response)
        else:
            client.logger.info(f"Decompressing gzip to {output_file}")
            await drain_gzip_decompress(_generate_bytes(forward_stream),
                                        output_path=output_file)
            client.logger.info(f"Finished decompression to {output_file}")
            await stream.send_message(
                RecordResponse(payload=Payload(file_path=output_file)))
Ejemplo n.º 11
0
 async def get_stub_for_udid(self, udid: Optional[str]) -> CompanionClient:
     is_companion_available = (
         self.is_companion_available_for_target_udid(udid)
         if udid else False)
     if udid and udid in self._stub_map and udid in self._udid_companion_map:
         return CompanionClient(
             stub=self._stub_map[udid],
             is_local=self._udid_companion_map[udid].is_local,
             udid=udid,
             logger=self._logger,
             is_companion_available=is_companion_available,
         )
     else:
         async with self.create_companion_for_target_with_udid(
                 target_udid=udid) as companion:
             stub = self.get_stub_for_address(companion.host,
                                              none_throws(companion.port))
             self._stub_map[companion.udid] = stub
             return CompanionClient(
                 stub=stub,
                 is_local=companion.is_local,
                 udid=udid,
                 logger=self._logger,
                 is_companion_available=is_companion_available,
             )
Ejemplo n.º 12
0
 async def spawn_tcp_server(
     self,
     config: CompanionServerConfig,
     port: Optional[int],
     tls_cert_path: Optional[str] = None,
 ) -> Tuple[asyncio.subprocess.Process, int]:
     bind_arguments = [
         "--grpc-port",
         str(port) if port is not None else "0"
     ]
     if tls_cert_path is not None:
         bind_arguments.extend(["--tls-cert-path", tls_cert_path])
     (process, log_file_path) = await self._spawn_server(
         config=config,
         bind_arguments=bind_arguments,
     )
     stdout = none_throws(process.stdout)
     try:
         extracted_port = await _extract_port_from_spawned_companion(stdout)
     except Exception as e:
         raise CompanionSpawnerException(
             f"Failed to spawn companion, couldn't read port output "
             f"stderr: {get_last_n_lines(log_file_path, 30)}") from e
     if extracted_port == 0:
         raise CompanionSpawnerException(
             f"Failed to spawn companion, port zero is invalid "
             f"stderr: {get_last_n_lines(log_file_path, 30)}")
     if port is not None and extracted_port != port:
         raise CompanionSpawnerException(
             "Failed to spawn companion, invalid port "
             f"(expected {port} got {extracted_port})"
             f"stderr: {get_last_n_lines(log_file_path, 30)}")
     return (process, extracted_port)
Ejemplo n.º 13
0
async def generate_gzip(path: str) -> AsyncIterator[bytes]:
    async with _create_gzip_compress_command(path=path) as process:
        reader = none_throws(process.stdout)
        while not reader.at_eof():
            data = await reader.read(READ_CHUNK_SIZE)
            if not data:
                return
            yield data
Ejemplo n.º 14
0
 async def get_stub(self) -> AsyncContextManager[GrpcStubClient]:
     await self.spawn_notifier()
     try:
         self.companion_info = self.direct_companion_manager.get_companion_info(
             target_udid=self.target_udid)
     except IdbException as e:
         # will try to spawn a companion if on mac.
         companion_info = await self.spawn_companion(
             target_udid=none_throws(self.target_udid))
         if companion_info:
             self.companion_info = companion_info
         else:
             raise e
     async with GrpcStubClient.build(companion_info=none_throws(
             self.companion_info),
                                     logger=self.logger) as stub:
         yield stub
Ejemplo n.º 15
0
async def drain_gzip_decompress(stream: AsyncIterator[bytes], output_path: str) -> None:
    async with _create_gzip_decompress_command(extract_path=output_path) as process:
        writer = none_throws(process.stdin)
        async for data in stream:
            writer.write(data)
            await writer.drain()
        writer.write_eof()
        await writer.drain()
Ejemplo n.º 16
0
async def do_spawn_companion(
    path: str,
    udid: str,
    log_file_path: str,
    device_set_path: Optional[str],
    port: Optional[int],
    cwd: Optional[str],
    tmp_path: Optional[str],
    reparent: bool,
    tls_cert_path: Optional[str] = None,
) -> Tuple[asyncio.subprocess.Process, int]:
    arguments: List[str] = [
        path,
        "--udid",
        udid,
        "--grpc-port",
        str(port) if port is not None else "0",
    ]
    if tls_cert_path is not None:
        arguments.extend(["--tls-cert-path", tls_cert_path])
    if device_set_path is not None:
        arguments.extend(["--device-set-path", device_set_path])

    env = dict(os.environ)
    if tmp_path:
        env["TMPDIR"] = tmp_path

    with open(log_file_path, "a") as log_file:
        process = await asyncio.create_subprocess_exec(
            *arguments,
            stdout=asyncio.subprocess.PIPE,
            stdin=asyncio.subprocess.PIPE if reparent else None,
            stderr=log_file,
            cwd=cwd,
            env=env,
            preexec_fn=os.setpgrp if reparent else None,
        )
        logging.debug(f"started companion at process id {process.pid}")
        stdout = none_throws(process.stdout)
        try:
            extracted_port = await _extract_port_from_spawned_companion(stdout)
        except Exception as e:
            raise CompanionSpawnerException(
                f"Failed to spawn companion, couldn't read port "
                f"stderr: {get_last_n_lines(log_file_path, 30)}"
            ) from e
        if extracted_port == 0:
            raise CompanionSpawnerException(
                f"Failed to spawn companion, port is zero"
                f"stderr: {get_last_n_lines(log_file_path, 30)}"
            )
        if port is not None and extracted_port != port:
            raise CompanionSpawnerException(
                "Failed to spawn companion, port is not correct "
                f"(expected {port} got {extracted_port})"
                f"stderr: {get_last_n_lines(log_file_path, 30)}"
            )
        return (process, extracted_port)
Ejemplo n.º 17
0
 async def boot(self) -> None:
     if self.target_udid:
         cmd: List[str] = ["idb_companion", "--boot", none_throws(self.target_udid)]
         process = await asyncio.create_subprocess_exec(
             *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
         )
         await process.communicate()
     else:
         raise IdbException("boot needs --udid to work")
Ejemplo n.º 18
0
 async def run_with_client(self, args: Namespace, client: Client) -> None:
     artifact: Optional[InstalledArtifact] = None
     async for info in client.install(args.bundle_path):
         artifact = info
         progress = info.progress
         if progress is None:
             continue
         self.logger.info(f"Progress: {progress}")
     if args.json:
         print(
             json.dumps({
                 "installedAppBundleId": none_throws(artifact).name,
                 "uuid": none_throws(artifact).uuid,
             }))
     else:
         print(
             f"Installed: {none_throws(artifact).name} {none_throws(artifact).uuid}"
         )
Ejemplo n.º 19
0
 async def tail_targets(
     self,
     only: Optional[OnlyFilter] = None
 ) -> AsyncGenerator[List[TargetDescription], None]:
     arguments = ["--notify", "stdout"] + _only_arg_from_filter(only=only)
     async with self._start_companion_command(
             arguments=arguments) as process:
         async for line in none_throws(process.stdout):
             yield target_descriptions_from_json(data=line.decode().strip())
Ejemplo n.º 20
0
 async def pull(self, bundle_id: str, src_path: str, dest_path: str) -> None:
     async with self.get_stub() as stub, stub.pull.open() as stream:
         request = request = PullRequest(
             bundle_id=bundle_id,
             src_path=src_path,
             # not sending the destination to remote companion
             # so it streams the file back
             dst_path=dest_path
             if none_throws(self.companion_info).is_local
             else None,
         )
         await stream.send_message(request)
         await stream.end()
         if none_throws(self.companion_info).is_local:
             await stream.recv_message()
         else:
             await drain_untar(generate_bytes(stream), output_path=dest_path)
         self.logger.info(f"pulled file to {dest_path}")
 async def start(self) -> None:
     logging.debug(f"Started tailing notifier")
     if self.process:
         logging.warning(
             f"Trying to start companion tailer when already running")
         return
     self.process = await self.notifierProcess()
     if self.process:
         self._reading_forever_fut = asyncio.ensure_future(
             self._read_stream(stream=none_throws(self.process.stdout)))
Ejemplo n.º 22
0
 async def _spawn_daemon(self) -> None:
     cmd: List[str] = [sys.argv[0], "daemon"]
     if platform.system() == "Darwin":
         logging.debug("Mac Detected. passing notifier path to daemon")
         cmd.extend(["--notifier-path", "idb_companion"])
     with open(self._log_file_path(), "w") as log_file:
         process = await asyncio.create_subprocess_exec(
             *cmd, stdout=asyncio.subprocess.PIPE, stderr=log_file)
         logging.debug(f"daemon process id {process.pid}")
         save_pid(pid=process.pid)
         await self._read_daemon_output(none_throws(process.stdout))
Ejemplo n.º 23
0
async def drain_to_stream(stream: Stream[_TSend, _TRecv],
                          generator: AsyncIterator[_TSend],
                          logger: Logger) -> _TRecv:
    while True:
        async for message in generator:
            await stream.send_message(message)
        await stream.end()
        logger.debug(
            f"Streamed all chunks to companion, waiting for completion")
        response = none_throws(await stream.recv_message())
        logger.debug(f"Companion completed")
        return response
Ejemplo n.º 24
0
async def instruments_drain_until_running(stream: Stream[
    InstrumentsRunRequest, InstrumentsRunResponse], logger: Logger) -> None:
    while True:
        response = none_throws(await stream.recv_message())
        log_output = response.log_output
        if len(log_output):
            logger.info(log_output.decode().strip())
            continue
        state = response.state
        if state == InstrumentsRunResponse.RUNNING_INSTRUMENTS:
            logger.info("State changed to RUNNING_INSTRUMENTS")
            return
Ejemplo n.º 25
0
async def daemon(context: DaemonContext, client: CompanionClient,
                 request: BootRequest) -> BootResponse:
    if client.is_companion_available:
        await none_throws(context.boot_manager
                          ).boot(udid=none_throws(client.udid))
        return BootResponse()
    else:
        # TODO T41660845
        raise GRPCError(
            status=Status(Status.UNIMPLEMENTED),
            message="boot with chained daemons hasn't been implemented yet",
        )
Ejemplo n.º 26
0
async def drain_untar(generator: AsyncIterator[bytes], output_path: str) -> None:
    try:
        os.mkdir(output_path)
    except FileExistsError:
        pass
    async with _create_untar_command(output_path=output_path) as process:
        writer = none_throws(process.stdin)
        async for data in generator:
            writer.write(data)
            await writer.drain()
        writer.write_eof()
        await writer.drain()
        await process.wait()
Ejemplo n.º 27
0
    async def spawn_notifier(self) -> None:
        if self._is_notifier_running():
            return

        self.check_okay_to_spawn()
        cmd = [self.companion_path, "--notify", IDB_LOCAL_TARGETS_FILE]
        with open(self._log_file_path("notifier"), "a") as log_file:
            process = await asyncio.create_subprocess_exec(
                *cmd, stdout=asyncio.subprocess.PIPE, stderr=log_file)
            self.pid_saver.save_notifier_pid(pid=process.pid)
            await asyncio.ensure_future(
                self._read_notifier_output(stream=none_throws(process.stdout)))
            logging.debug(f"started notifier at process id {process.pid}")
Ejemplo n.º 28
0
 async def __aexit__(
     self,
     exc_type: Optional[Type[Exception]],
     exception: Optional[Exception],
     traceback: Optional[TracebackType],
 ) -> bool:
     name = none_throws(self.name)
     duration = int((time.time() - none_throws(self.start)) * 1000)
     if exception:
         logger.debug(f"{name} failed")
         await plugin.failed_invocation(
             name=name,
             duration=duration,
             exception=exception,
             metadata=self.metadata,
         )
     else:
         logger.debug(f"{name} succeeded")
         await plugin.after_invocation(
             name=name, duration=duration, metadata=self.metadata
         )
     return False
Ejemplo n.º 29
0
async def instruments_drain_until_stop(
    stream: Stream[InstrumentsRunRequest, InstrumentsRunResponse],
    stop: asyncio.Future,
    logger: Logger,
) -> None:
    while True:
        read = asyncio.ensure_future(stream.recv_message())
        done, pending = await asyncio.wait([stop, read],
                                           return_when=asyncio.FIRST_COMPLETED)
        if stop in done:
            return
        response = none_throws(read.result())
        output = response.log_output
        if len(output):
            logger.info(output.decode())
Ejemplo n.º 30
0
 async def add_media(self, file_paths: List[str]) -> None:
     async with self.get_stub() as stub, stub.add_media.open() as stream:
         if none_throws(self.companion_info).is_local:
             for file_path in file_paths:
                 await stream.send_message(
                     AddMediaRequest(payload=Payload(file_path=file_path)))
             await stream.end()
             await stream.recv_message()
         else:
             generator = stream_map(
                 generate_tar(paths=file_paths, place_in_subfolders=True),
                 lambda chunk: AddMediaRequest(payload=Payload(data=chunk)),
             )
             await drain_to_stream(stream=stream,
                                   generator=generator,
                                   logger=self.logger)