adb: implement compression for file sync.

This improves performance when syncing by up to 2x (remote cuttlefish
goes from 11.9 MB/s to 21.3 MB/s, blueline over USB 2.0 from 36 MB/s
to 70 MB/s).

This results in a slight drop in push speeds over USB 3.0 (125 -> 115
MB/s on blueline), presumably because we're compressing and extracting
on only a single thread, but the gains over lower bandwidth transports
make this worth it to submit this now and parallelize later.

Bug: https://issuetracker.google.com/150827486
Test: ADB_COMPRESSION={0, 1} test_device.py (with new/old adbd)
Change-Id: Ic2a0c974f1b6efecda115f87d336e3caac810035
This commit is contained in:
Josh Gao 2020-03-04 19:34:08 -08:00
parent 5e4b94d44d
commit 939fc19aee
12 changed files with 832 additions and 134 deletions

View File

@ -317,6 +317,7 @@ cc_binary_host {
"libandroidfw",
"libapp_processes_protos_full",
"libbase",
"libbrotli",
"libcutils",
"libcrypto_utils",
"libcrypto",
@ -469,6 +470,7 @@ cc_library {
static_libs: [
"libadbconnection_server",
"libadbd_core",
"libbrotli",
"libdiagnose_usb",
],
@ -567,6 +569,7 @@ cc_library {
},
static_libs: [
"libbrotli",
"libcutils_sockets",
"libdiagnose_usb",
"libmdnssd",
@ -606,6 +609,7 @@ cc_binary {
"libapp_processes_protos_lite",
"libasyncio",
"libbase",
"libbrotli",
"libcap",
"libcrypto_utils",
"libcutils_sockets",

144
adb/brotli_utils.h Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <span>
#include <brotli/decode.h>
#include <brotli/encode.h>
#include "types.h"
enum class BrotliDecodeResult {
Error,
Done,
NeedInput,
MoreOutput,
};
struct BrotliDecoder {
explicit BrotliDecoder(std::span<char> output_buffer)
: output_buffer_(output_buffer),
decoder_(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr),
BrotliDecoderDestroyInstance) {}
void Append(Block&& block) { input_buffer_.append(std::move(block)); }
BrotliDecodeResult Decode(std::span<char>* output) {
size_t available_in = input_buffer_.front_size();
const uint8_t* next_in = reinterpret_cast<const uint8_t*>(input_buffer_.front_data());
size_t available_out = output_buffer_.size();
uint8_t* next_out = reinterpret_cast<uint8_t*>(output_buffer_.data());
BrotliDecoderResult r = BrotliDecoderDecompressStream(
decoder_.get(), &available_in, &next_in, &available_out, &next_out, nullptr);
size_t bytes_consumed = input_buffer_.front_size() - available_in;
input_buffer_.drop_front(bytes_consumed);
size_t bytes_emitted = output_buffer_.size() - available_out;
*output = std::span<char>(output_buffer_.data(), bytes_emitted);
switch (r) {
case BROTLI_DECODER_RESULT_SUCCESS:
return BrotliDecodeResult::Done;
case BROTLI_DECODER_RESULT_ERROR:
return BrotliDecodeResult::Error;
case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
// Brotli guarantees as one of its invariants that if it returns NEEDS_MORE_INPUT,
// it will consume the entire input buffer passed in, so we don't have to worry
// about bytes left over in the front block with more input remaining.
return BrotliDecodeResult::NeedInput;
case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
return BrotliDecodeResult::MoreOutput;
}
}
private:
IOVector input_buffer_;
std::span<char> output_buffer_;
std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState*)> decoder_;
};
enum class BrotliEncodeResult {
Error,
Done,
NeedInput,
MoreOutput,
};
template <size_t OutputBlockSize>
struct BrotliEncoder {
explicit BrotliEncoder()
: output_block_(OutputBlockSize),
output_bytes_left_(OutputBlockSize),
encoder_(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
BrotliEncoderDestroyInstance) {
BrotliEncoderSetParameter(encoder_.get(), BROTLI_PARAM_QUALITY, 1);
}
void Append(Block input) { input_buffer_.append(std::move(input)); }
void Finish() { finished_ = true; }
BrotliEncodeResult Encode(Block* output) {
output->clear();
while (true) {
size_t available_in = input_buffer_.front_size();
const uint8_t* next_in = reinterpret_cast<const uint8_t*>(input_buffer_.front_data());
size_t available_out = output_bytes_left_;
uint8_t* next_out = reinterpret_cast<uint8_t*>(output_block_.data() +
(OutputBlockSize - output_bytes_left_));
BrotliEncoderOperation op = BROTLI_OPERATION_PROCESS;
if (finished_) {
op = BROTLI_OPERATION_FINISH;
}
if (!BrotliEncoderCompressStream(encoder_.get(), op, &available_in, &next_in,
&available_out, &next_out, nullptr)) {
return BrotliEncodeResult::Error;
}
size_t bytes_consumed = input_buffer_.front_size() - available_in;
input_buffer_.drop_front(bytes_consumed);
output_bytes_left_ = available_out;
if (BrotliEncoderIsFinished(encoder_.get())) {
output_block_.resize(OutputBlockSize - output_bytes_left_);
*output = std::move(output_block_);
return BrotliEncodeResult::Done;
} else if (output_bytes_left_ == 0) {
*output = std::move(output_block_);
output_block_.resize(OutputBlockSize);
output_bytes_left_ = OutputBlockSize;
return BrotliEncodeResult::MoreOutput;
} else if (input_buffer_.empty()) {
return BrotliEncodeResult::NeedInput;
}
}
}
private:
bool finished_ = false;
IOVector input_buffer_;
Block output_block_;
size_t output_bytes_left_;
std::unique_ptr<BrotliEncoderState, void (*)(BrotliEncoderState*)> encoder_;
};

View File

@ -286,7 +286,7 @@ static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy)
}
}
if (do_sync_push(apk_file, apk_dest.c_str(), false)) {
if (do_sync_push(apk_file, apk_dest.c_str(), false, true)) {
result = pm_command(argc, argv);
delete_device_file(apk_dest);
}

View File

@ -129,15 +129,21 @@ static void help() {
" reverse --remove-all remove all reverse socket connections from device\n"
"\n"
"file transfer:\n"
" push [--sync] LOCAL... REMOTE\n"
" push [--sync] [-zZ] LOCAL... REMOTE\n"
" copy local files/directories to device\n"
" --sync: only push files that are newer on the host than the device\n"
" pull [-a] REMOTE... LOCAL\n"
" -z: enable compression\n"
" -Z: disable compression\n"
" pull [-azZ] REMOTE... LOCAL\n"
" copy files/dirs from device\n"
" -a: preserve file timestamp and mode\n"
" sync [all|data|odm|oem|product|system|system_ext|vendor]\n"
" -z: enable compression\n"
" -Z: disable compression\n"
" sync [-lzZ] [all|data|odm|oem|product|system|system_ext|vendor]\n"
" sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n"
" -l: list files that would be copied, but don't copy them\n"
" -z: enable compression\n"
" -Z: disable compression\n"
"\n"
"shell:\n"
" shell [-e ESCAPE] [-n] [-Tt] [-x] [COMMAND...]\n"
@ -1309,8 +1315,12 @@ static int restore(int argc, const char** argv) {
}
static void parse_push_pull_args(const char** arg, int narg, std::vector<const char*>* srcs,
const char** dst, bool* copy_attrs, bool* sync) {
const char** dst, bool* copy_attrs, bool* sync, bool* compressed) {
*copy_attrs = false;
const char* adb_compression = getenv("ADB_COMPRESSION");
if (adb_compression && strcmp(adb_compression, "0") == 0) {
*compressed = false;
}
srcs->clear();
bool ignore_flags = false;
@ -1322,6 +1332,14 @@ static void parse_push_pull_args(const char** arg, int narg, std::vector<const c
// Silently ignore for backwards compatibility.
} else if (!strcmp(*arg, "-a")) {
*copy_attrs = true;
} else if (!strcmp(*arg, "-z")) {
if (compressed != nullptr) {
*compressed = true;
}
} else if (!strcmp(*arg, "-Z")) {
if (compressed != nullptr) {
*compressed = false;
}
} else if (!strcmp(*arg, "--sync")) {
if (sync != nullptr) {
*sync = true;
@ -1876,20 +1894,22 @@ int adb_commandline(int argc, const char** argv) {
} else if (!strcmp(argv[0], "push")) {
bool copy_attrs = false;
bool sync = false;
bool compressed = true;
std::vector<const char*> srcs;
const char* dst = nullptr;
parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync);
parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync, &compressed);
if (srcs.empty() || !dst) error_exit("push requires an argument");
return do_sync_push(srcs, dst, sync) ? 0 : 1;
return do_sync_push(srcs, dst, sync, compressed) ? 0 : 1;
} else if (!strcmp(argv[0], "pull")) {
bool copy_attrs = false;
bool compressed = true;
std::vector<const char*> srcs;
const char* dst = ".";
parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr);
parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr, &compressed);
if (srcs.empty()) error_exit("pull requires an argument");
return do_sync_pull(srcs, dst, copy_attrs) ? 0 : 1;
return do_sync_pull(srcs, dst, copy_attrs, compressed) ? 0 : 1;
} else if (!strcmp(argv[0], "install")) {
if (argc < 2) error_exit("install requires an argument");
return install_app(argc, argv);
@ -1905,18 +1925,38 @@ int adb_commandline(int argc, const char** argv) {
} else if (!strcmp(argv[0], "sync")) {
std::string src;
bool list_only = false;
if (argc < 2) {
// No partition specified: sync all of them.
} else if (argc >= 2 && strcmp(argv[1], "-l") == 0) {
list_only = true;
if (argc == 3) src = argv[2];
} else if (argc == 2) {
src = argv[1];
} else {
error_exit("usage: adb sync [-l] [PARTITION]");
bool compressed = true;
const char* adb_compression = getenv("ADB_COMPRESSION");
if (adb_compression && strcmp(adb_compression, "0") == 0) {
compressed = false;
}
int opt;
while ((opt = getopt(argc, const_cast<char**>(argv), "lzZ")) != -1) {
switch (opt) {
case 'l':
list_only = true;
break;
case 'z':
compressed = true;
break;
case 'Z':
compressed = false;
break;
default:
error_exit("usage: adb sync [-lzZ] [PARTITION]");
}
}
if (optind == argc) {
src = "all";
} else if (optind + 1 == argc) {
src = argv[optind];
} else {
error_exit("usage: adb sync [-lzZ] [PARTITION]");
}
if (src.empty()) src = "all";
std::vector<std::string> partitions{"data", "odm", "oem", "product",
"system", "system_ext", "vendor"};
bool found = false;
@ -1925,7 +1965,7 @@ int adb_commandline(int argc, const char** argv) {
std::string src_dir{product_file(partition)};
if (!directory_exists(src_dir)) continue;
found = true;
if (!do_sync_sync(src_dir, "/" + partition, list_only)) return 1;
if (!do_sync_sync(src_dir, "/" + partition, list_only, compressed)) return 1;
}
}
if (!found) error_exit("don't know how to sync %s partition", src.c_str());

View File

@ -112,7 +112,7 @@ static void push_to_device(const void* data, size_t byte_count, const char* dst,
// but can't be removed until after the push.
unix_close(tf.release());
if (!do_sync_push(srcs, dst, sync)) {
if (!do_sync_push(srcs, dst, sync, true)) {
error_exit("Failed to push fastdeploy agent to device.");
}
}

View File

@ -42,6 +42,7 @@
#include "adb_client.h"
#include "adb_io.h"
#include "adb_utils.h"
#include "brotli_utils.h"
#include "file_sync_protocol.h"
#include "line_printer.h"
#include "sysdeps/errno.h"
@ -233,6 +234,8 @@ class SyncConnection {
} else {
have_stat_v2_ = CanUseFeature(features_, kFeatureStat2);
have_ls_v2_ = CanUseFeature(features_, kFeatureLs2);
have_sendrecv_v2_ = CanUseFeature(features_, kFeatureSendRecv2);
have_sendrecv_v2_brotli_ = CanUseFeature(features_, kFeatureSendRecv2Brotli);
fd.reset(adb_connect("sync:", &error));
if (fd < 0) {
Error("connect failed: %s", error.c_str());
@ -256,6 +259,9 @@ class SyncConnection {
line_printer_.KeepInfoLine();
}
bool HaveSendRecv2() const { return have_sendrecv_v2_; }
bool HaveSendRecv2Brotli() const { return have_sendrecv_v2_brotli_; }
const FeatureSet& Features() const { return features_; }
bool IsValid() { return fd >= 0; }
@ -314,6 +320,62 @@ class SyncConnection {
req->path_length = path.length();
char* data = reinterpret_cast<char*>(req + 1);
memcpy(data, path.data(), path.length());
return WriteFdExactly(fd, buf.data(), buf.size());
}
bool SendSend2(std::string_view path, mode_t mode, bool compressed) {
if (path.length() > 1024) {
Error("SendRequest failed: path too long: %zu", path.length());
errno = ENAMETOOLONG;
return false;
}
Block buf;
SyncRequest req;
req.id = ID_SEND_V2;
req.path_length = path.length();
syncmsg msg;
msg.send_v2_setup.id = ID_SEND_V2;
msg.send_v2_setup.mode = mode;
msg.send_v2_setup.flags = compressed ? kSyncFlagBrotli : kSyncFlagNone;
buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.send_v2_setup));
void* p = buf.data();
p = mempcpy(p, &req, sizeof(SyncRequest));
p = mempcpy(p, path.data(), path.length());
p = mempcpy(p, &msg.send_v2_setup, sizeof(msg.send_v2_setup));
return WriteFdExactly(fd, buf.data(), buf.size());
}
bool SendRecv2(const std::string& path) {
if (path.length() > 1024) {
Error("SendRequest failed: path too long: %zu", path.length());
errno = ENAMETOOLONG;
return false;
}
Block buf;
SyncRequest req;
req.id = ID_RECV_V2;
req.path_length = path.length();
syncmsg msg;
msg.recv_v2_setup.id = ID_RECV_V2;
msg.recv_v2_setup.flags = kSyncFlagBrotli;
buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.recv_v2_setup));
void* p = buf.data();
p = mempcpy(p, &req, sizeof(SyncRequest));
p = mempcpy(p, path.data(), path.length());
p = mempcpy(p, &msg.recv_v2_setup, sizeof(msg.recv_v2_setup));
return WriteFdExactly(fd, buf.data(), buf.size());
}
@ -370,8 +432,8 @@ class SyncConnection {
}
if (msg.stat_v1.id != ID_LSTAT_V1) {
PLOG(FATAL) << "protocol fault: stat response has wrong message id: "
<< msg.stat_v1.id;
LOG(FATAL) << "protocol fault: stat response has wrong message id: "
<< msg.stat_v1.id;
}
if (msg.stat_v1.mode == 0 && msg.stat_v1.size == 0 && msg.stat_v1.mtime == 0) {
@ -445,7 +507,7 @@ class SyncConnection {
char* p = &buf[0];
SyncRequest* req_send = reinterpret_cast<SyncRequest*>(p);
req_send->id = ID_SEND;
req_send->id = ID_SEND_V1;
req_send->path_length = path_and_mode.length();
p += sizeof(SyncRequest);
memcpy(p, path_and_mode.data(), path_and_mode.size());
@ -471,11 +533,92 @@ class SyncConnection {
return true;
}
bool SendLargeFileCompressed(const std::string& path, mode_t mode, const std::string& lpath,
const std::string& rpath, unsigned mtime) {
if (!SendSend2(path, mode, true)) {
Error("failed to send ID_SEND_V2 message '%s': %s", path.c_str(), strerror(errno));
return false;
}
struct stat st;
if (stat(lpath.c_str(), &st) == -1) {
Error("cannot stat '%s': %s", lpath.c_str(), strerror(errno));
return false;
}
uint64_t total_size = st.st_size;
uint64_t bytes_copied = 0;
unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY | O_CLOEXEC));
if (lfd < 0) {
Error("opening '%s' locally failed: %s", lpath.c_str(), strerror(errno));
return false;
}
syncsendbuf sbuf;
sbuf.id = ID_DATA;
BrotliEncoder<SYNC_DATA_MAX> encoder;
bool sending = true;
while (sending) {
Block input(SYNC_DATA_MAX);
int r = adb_read(lfd.get(), input.data(), input.size());
if (r < 0) {
Error("reading '%s' locally failed: %s", lpath.c_str(), strerror(errno));
return false;
}
if (r == 0) {
encoder.Finish();
} else {
input.resize(r);
encoder.Append(std::move(input));
RecordBytesTransferred(r);
bytes_copied += r;
ReportProgress(rpath, bytes_copied, total_size);
}
while (true) {
Block output;
BrotliEncodeResult result = encoder.Encode(&output);
if (result == BrotliEncodeResult::Error) {
Error("compressing '%s' locally failed", lpath.c_str());
return false;
}
if (!output.empty()) {
sbuf.size = output.size();
memcpy(sbuf.data, output.data(), output.size());
WriteOrDie(lpath, rpath, &sbuf, sizeof(SyncRequest) + output.size());
}
if (result == BrotliEncodeResult::Done) {
sending = false;
break;
} else if (result == BrotliEncodeResult::NeedInput) {
break;
} else if (result == BrotliEncodeResult::MoreOutput) {
continue;
}
}
}
syncmsg msg;
msg.data.id = ID_DONE;
msg.data.size = mtime;
RecordFileSent(lpath, rpath);
return WriteOrDie(lpath, rpath, &msg.data, sizeof(msg.data));
}
bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
const std::string& rpath, unsigned mtime) {
const std::string& rpath, unsigned mtime, bool compressed) {
if (compressed && HaveSendRecv2Brotli()) {
return SendLargeFileCompressed(path, mode, lpath, rpath, mtime);
}
std::string path_and_mode = android::base::StringPrintf("%s,%d", path.c_str(), mode);
if (!SendRequest(ID_SEND, path_and_mode)) {
Error("failed to send ID_SEND message '%s': %s", path_and_mode.c_str(),
if (!SendRequest(ID_SEND_V1, path_and_mode.c_str())) {
Error("failed to send ID_SEND_V1 message '%s': %s", path_and_mode.c_str(),
strerror(errno));
return false;
}
@ -489,7 +632,7 @@ class SyncConnection {
uint64_t total_size = st.st_size;
uint64_t bytes_copied = 0;
unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY));
unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY | O_CLOEXEC));
if (lfd < 0) {
Error("opening '%s' locally failed: %s", lpath.c_str(), strerror(errno));
return false;
@ -497,8 +640,9 @@ class SyncConnection {
syncsendbuf sbuf;
sbuf.id = ID_DATA;
while (true) {
int bytes_read = adb_read(lfd, sbuf.data, max - sizeof(SyncRequest));
int bytes_read = adb_read(lfd, sbuf.data, max);
if (bytes_read == -1) {
Error("reading '%s' locally failed: %s", lpath.c_str(), strerror(errno));
return false;
@ -511,7 +655,6 @@ class SyncConnection {
RecordBytesTransferred(bytes_read);
bytes_copied += bytes_read;
ReportProgress(rpath, bytes_copied, total_size);
}
@ -695,6 +838,8 @@ class SyncConnection {
FeatureSet features_;
bool have_stat_v2_;
bool have_ls_v2_;
bool have_sendrecv_v2_;
bool have_sendrecv_v2_brotli_;
TransferLedger global_ledger_;
TransferLedger current_ledger_;
@ -776,7 +921,7 @@ static bool sync_stat_fallback(SyncConnection& sc, const std::string& path, stru
}
static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::string& rpath,
unsigned mtime, mode_t mode, bool sync) {
unsigned mtime, mode_t mode, bool sync, bool compressed) {
if (sync) {
struct stat st;
if (sync_lstat(sc, rpath, &st)) {
@ -819,16 +964,16 @@ static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::s
return false;
}
} else {
if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime)) {
if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compressed)) {
return false;
}
}
return sc.ReadAcknowledgements();
}
static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
const char* name, uint64_t expected_size) {
if (!sc.SendRequest(ID_RECV, rpath)) return false;
static bool sync_recv_v1(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
uint64_t expected_size) {
if (!sc.SendRequest(ID_RECV_V1, rpath)) return false;
adb_unlink(lpath);
unique_fd lfd(adb_creat(lpath, 0644));
@ -881,6 +1026,114 @@ static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
return true;
}
static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
uint64_t expected_size) {
if (!sc.SendRecv2(rpath)) return false;
adb_unlink(lpath);
unique_fd lfd(adb_creat(lpath, 0644));
if (lfd < 0) {
sc.Error("cannot create '%s': %s", lpath, strerror(errno));
return false;
}
uint64_t bytes_copied = 0;
Block buffer(SYNC_DATA_MAX);
BrotliDecoder decoder(std::span(buffer.data(), buffer.size()));
bool reading = true;
while (reading) {
syncmsg msg;
if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
adb_unlink(lpath);
return false;
}
if (msg.data.id == ID_DONE) {
adb_unlink(lpath);
sc.Error("unexpected ID_DONE");
return false;
}
if (msg.data.id != ID_DATA) {
adb_unlink(lpath);
sc.ReportCopyFailure(rpath, lpath, msg);
return false;
}
if (msg.data.size > sc.max) {
sc.Error("msg.data.size too large: %u (max %zu)", msg.data.size, sc.max);
adb_unlink(lpath);
return false;
}
Block block(msg.data.size);
if (!ReadFdExactly(sc.fd, block.data(), msg.data.size)) {
adb_unlink(lpath);
return false;
}
decoder.Append(std::move(block));
while (true) {
std::span<char> output;
BrotliDecodeResult result = decoder.Decode(&output);
if (result == BrotliDecodeResult::Error) {
sc.Error("decompress failed");
adb_unlink(lpath);
return false;
}
if (!output.empty()) {
if (!WriteFdExactly(lfd, output.data(), output.size())) {
sc.Error("cannot write '%s': %s", lpath, strerror(errno));
adb_unlink(lpath);
return false;
}
}
bytes_copied += output.size();
sc.RecordBytesTransferred(msg.data.size);
sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, expected_size);
if (result == BrotliDecodeResult::NeedInput) {
break;
} else if (result == BrotliDecodeResult::MoreOutput) {
continue;
} else if (result == BrotliDecodeResult::Done) {
reading = false;
break;
} else {
LOG(FATAL) << "invalid BrotliDecodeResult: " << static_cast<int>(result);
}
}
}
syncmsg msg;
if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
sc.Error("failed to read ID_DONE");
return false;
}
if (msg.data.id != ID_DONE) {
sc.Error("unexpected message after transfer: id = %d (expected ID_DONE)", msg.data.id);
return false;
}
sc.RecordFilesTransferred(1);
return true;
}
static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
uint64_t expected_size, bool compressed) {
if (sc.HaveSendRecv2() && compressed) {
return sync_recv_v2(sc, rpath, lpath, name, expected_size);
} else {
return sync_recv_v1(sc, rpath, lpath, name, expected_size);
}
}
bool do_sync_ls(const char* path) {
SyncConnection sc;
if (!sc.IsValid()) return false;
@ -956,9 +1209,8 @@ static bool is_root_dir(std::string_view path) {
return true;
}
static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,
std::string rpath, bool check_timestamps,
bool list_only) {
static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::string rpath,
bool check_timestamps, bool list_only, bool compressed) {
sc.NewTransfer();
// Make sure that both directory paths end in a slash.
@ -1040,7 +1292,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,
if (list_only) {
sc.Println("would push: %s -> %s", ci.lpath.c_str(), ci.rpath.c_str());
} else {
if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false)) {
if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compressed)) {
return false;
}
}
@ -1055,7 +1307,8 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,
return success;
}
bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync) {
bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
bool compressed) {
SyncConnection sc;
if (!sc.IsValid()) return false;
@ -1120,7 +1373,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sy
dst_dir.append(android::base::Basename(src_path));
}
success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false);
success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compressed);
continue;
} else if (!should_push_file(st.st_mode)) {
sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@ -1141,7 +1394,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sy
sc.NewTransfer();
sc.SetExpectedTotalBytes(st.st_size);
success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync);
success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compressed);
sc.ReportTransferRate(src_path, TransferDirection::push);
}
@ -1226,8 +1479,8 @@ static int set_time_and_mode(const std::string& lpath, time_t time,
return r1 ? r1 : r2;
}
static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath,
std::string lpath, bool copy_attrs) {
static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::string lpath,
bool copy_attrs, bool compressed) {
sc.NewTransfer();
// Make sure that both directory paths end in a slash.
@ -1257,7 +1510,7 @@ static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath,
continue;
}
if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size)) {
if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compressed)) {
return false;
}
@ -1274,8 +1527,8 @@ static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath,
return true;
}
bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
bool copy_attrs, const char* name) {
bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
bool compressed, const char* name) {
SyncConnection sc;
if (!sc.IsValid()) return false;
@ -1349,7 +1602,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
dst_dir.append(android::base::Basename(src_path));
}
success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs);
success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs, compressed);
continue;
} else if (!should_pull_file(src_st.st_mode)) {
sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_st.st_mode);
@ -1368,7 +1621,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
sc.NewTransfer();
sc.SetExpectedTotalBytes(src_st.st_size);
if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size)) {
if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compressed)) {
success = false;
continue;
}
@ -1384,11 +1637,12 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
return success;
}
bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only) {
bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
bool compressed) {
SyncConnection sc;
if (!sc.IsValid()) return false;
bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only);
bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compressed);
if (!list_only) {
sc.ReportOverallTransferRate(TransferDirection::push);
}

View File

@ -20,8 +20,10 @@
#include <vector>
bool do_sync_ls(const char* path);
bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync);
bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
bool compressed);
bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
const char* name = nullptr);
bool compressed, const char* name = nullptr);
bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only);
bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
bool compressed);

View File

@ -32,6 +32,8 @@
#include <utime.h>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <vector>
@ -55,10 +57,12 @@
#include "adb_io.h"
#include "adb_trace.h"
#include "adb_utils.h"
#include "brotli_utils.h"
#include "file_sync_protocol.h"
#include "security_log_tags.h"
#include "sysdeps/errno.h"
using android::base::borrowed_fd;
using android::base::Dirname;
using android::base::StringPrintf;
@ -249,7 +253,7 @@ static bool do_list_v2(int s, const char* path) {
// Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
#pragma GCC poison SendFail
static bool SendSyncFail(int fd, const std::string& reason) {
static bool SendSyncFail(borrowed_fd fd, const std::string& reason) {
D("sync: failure: %s", reason.c_str());
syncmsg msg;
@ -258,13 +262,89 @@ static bool SendSyncFail(int fd, const std::string& reason) {
return WriteFdExactly(fd, &msg.data, sizeof(msg.data)) && WriteFdExactly(fd, reason);
}
static bool SendSyncFailErrno(int fd, const std::string& reason) {
static bool SendSyncFailErrno(borrowed_fd fd, const std::string& reason) {
return SendSyncFail(fd, StringPrintf("%s: %s", reason.c_str(), strerror(errno)));
}
static bool handle_send_file(int s, const char* path, uint32_t* timestamp, uid_t uid, gid_t gid,
uint64_t capabilities, mode_t mode, std::vector<char>& buffer,
bool do_unlink) {
static bool handle_send_file_compressed(borrowed_fd s, unique_fd fd, uint32_t* timestamp) {
syncmsg msg;
Block decode_buffer(SYNC_DATA_MAX);
BrotliDecoder decoder(std::span(decode_buffer.data(), decode_buffer.size()));
while (true) {
if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) return false;
if (msg.data.id != ID_DATA) {
if (msg.data.id == ID_DONE) {
*timestamp = msg.data.size;
return true;
}
SendSyncFail(s, "invalid data message");
return false;
}
Block block(msg.data.size);
if (!ReadFdExactly(s, block.data(), msg.data.size)) return false;
decoder.Append(std::move(block));
while (true) {
std::span<char> output;
BrotliDecodeResult result = decoder.Decode(&output);
if (result == BrotliDecodeResult::Error) {
SendSyncFailErrno(s, "decompress failed");
return false;
}
if (!WriteFdExactly(fd, output.data(), output.size())) {
SendSyncFailErrno(s, "write failed");
return false;
}
if (result == BrotliDecodeResult::NeedInput) {
break;
} else if (result == BrotliDecodeResult::MoreOutput) {
continue;
} else if (result == BrotliDecodeResult::Done) {
break;
} else {
LOG(FATAL) << "invalid BrotliDecodeResult: " << static_cast<int>(result);
}
}
}
__builtin_unreachable();
}
static bool handle_send_file_uncompressed(borrowed_fd s, unique_fd fd, uint32_t* timestamp,
std::vector<char>& buffer) {
syncmsg msg;
while (true) {
if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) return false;
if (msg.data.id != ID_DATA) {
if (msg.data.id == ID_DONE) {
*timestamp = msg.data.size;
return true;
}
SendSyncFail(s, "invalid data message");
return false;
}
if (msg.data.size > buffer.size()) { // TODO: resize buffer?
SendSyncFail(s, "oversize data message");
return false;
}
if (!ReadFdExactly(s, &buffer[0], msg.data.size)) return false;
if (!WriteFdExactly(fd, &buffer[0], msg.data.size)) {
SendSyncFailErrno(s, "write failed");
return false;
}
}
}
static bool handle_send_file(borrowed_fd s, const char* path, uint32_t* timestamp, uid_t uid,
gid_t gid, uint64_t capabilities, mode_t mode, bool compressed,
std::vector<char>& buffer, bool do_unlink) {
int rc;
syncmsg msg;
@ -302,45 +382,33 @@ static bool handle_send_file(int s, const char* path, uint32_t* timestamp, uid_t
fchmod(fd.get(), mode);
}
rc = posix_fadvise(fd.get(), 0, 0,
POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED);
if (rc != 0) {
D("[ Failed to fadvise: %s ]", strerror(rc));
}
while (true) {
if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) goto fail;
if (msg.data.id != ID_DATA) {
if (msg.data.id == ID_DONE) {
*timestamp = msg.data.size;
break;
}
SendSyncFail(s, "invalid data message");
goto abort;
{
rc = posix_fadvise(fd.get(), 0, 0,
POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED);
if (rc != 0) {
D("[ Failed to fadvise: %s ]", strerror(rc));
}
if (msg.data.size > buffer.size()) { // TODO: resize buffer?
SendSyncFail(s, "oversize data message");
goto abort;
bool result;
if (compressed) {
result = handle_send_file_compressed(s, std::move(fd), timestamp);
} else {
result = handle_send_file_uncompressed(s, std::move(fd), timestamp, buffer);
}
if (!ReadFdExactly(s, &buffer[0], msg.data.size)) goto abort;
if (!WriteFdExactly(fd.get(), &buffer[0], msg.data.size)) {
SendSyncFailErrno(s, "write failed");
if (!result) {
goto fail;
}
}
if (!update_capabilities(path, capabilities)) {
SendSyncFailErrno(s, "update_capabilities failed");
goto fail;
}
if (!update_capabilities(path, capabilities)) {
SendSyncFailErrno(s, "update_capabilities failed");
goto fail;
}
msg.status.id = ID_OKAY;
msg.status.msglen = 0;
return WriteFdExactly(s, &msg.status, sizeof(msg.status));
msg.status.id = ID_OKAY;
msg.status.msglen = 0;
return WriteFdExactly(s, &msg.status, sizeof(msg.status));
}
fail:
// If there's a problem on the device, we'll send an ID_FAIL message and
@ -371,7 +439,6 @@ fail:
if (!ReadFdExactly(s, &buffer[0], msg.data.size)) break;
}
abort:
if (do_unlink) adb_unlink(path);
return false;
}
@ -432,23 +499,8 @@ static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp
}
#endif
static bool do_send(int s, const std::string& spec, std::vector<char>& buffer) {
// 'spec' is of the form "/some/path,0755". Break it up.
size_t comma = spec.find_last_of(',');
if (comma == std::string::npos) {
SendSyncFail(s, "missing , in ID_SEND");
return false;
}
std::string path = spec.substr(0, comma);
errno = 0;
mode_t mode = strtoul(spec.substr(comma + 1).c_str(), nullptr, 0);
if (errno != 0) {
SendSyncFail(s, "bad mode");
return false;
}
static bool send_impl(int s, const std::string& path, mode_t mode, bool compressed,
std::vector<char>& buffer) {
// Don't delete files before copying if they are not "regular" or symlinks.
struct stat st;
bool do_unlink = (lstat(path.c_str(), &st) == -1) || S_ISREG(st.st_mode) ||
@ -474,8 +526,8 @@ static bool do_send(int s, const std::string& spec, std::vector<char>& buffer) {
adbd_fs_config(path.c_str(), 0, nullptr, &uid, &gid, &mode, &capabilities);
}
result = handle_send_file(s, path.c_str(), &timestamp, uid, gid, capabilities, mode, buffer,
do_unlink);
result = handle_send_file(s, path.c_str(), &timestamp, uid, gid, capabilities, mode,
compressed, buffer, do_unlink);
}
if (!result) {
@ -491,7 +543,125 @@ static bool do_send(int s, const std::string& spec, std::vector<char>& buffer) {
return true;
}
static bool do_recv(int s, const char* path, std::vector<char>& buffer) {
static bool do_send_v1(int s, const std::string& spec, std::vector<char>& buffer) {
// 'spec' is of the form "/some/path,0755". Break it up.
size_t comma = spec.find_last_of(',');
if (comma == std::string::npos) {
SendSyncFail(s, "missing , in ID_SEND_V1");
return false;
}
std::string path = spec.substr(0, comma);
errno = 0;
mode_t mode = strtoul(spec.substr(comma + 1).c_str(), nullptr, 0);
if (errno != 0) {
SendSyncFail(s, "bad mode");
return false;
}
return send_impl(s, path, mode, false, buffer);
}
static bool do_send_v2(int s, const std::string& path, std::vector<char>& buffer) {
// Read the setup packet.
syncmsg msg;
int rc = ReadFdExactly(s, &msg.send_v2_setup, sizeof(msg.send_v2_setup));
if (rc == 0) {
LOG(ERROR) << "failed to read send_v2 setup packet: EOF";
return false;
} else if (rc < 0) {
PLOG(ERROR) << "failed to read send_v2 setup packet";
}
bool compressed = false;
if (msg.send_v2_setup.flags & kSyncFlagBrotli) {
msg.send_v2_setup.flags &= ~kSyncFlagBrotli;
compressed = true;
}
if (msg.send_v2_setup.flags) {
SendSyncFail(s, android::base::StringPrintf("unknown flags: %d", msg.send_v2_setup.flags));
return false;
}
errno = 0;
return send_impl(s, path, msg.send_v2_setup.mode, compressed, buffer);
}
static bool recv_uncompressed(borrowed_fd s, unique_fd fd, std::vector<char>& buffer) {
syncmsg msg;
msg.data.id = ID_DATA;
std::optional<BrotliEncoder<SYNC_DATA_MAX>> encoder;
while (true) {
int r = adb_read(fd.get(), &buffer[0], buffer.size() - sizeof(msg.data));
if (r <= 0) {
if (r == 0) break;
SendSyncFailErrno(s, "read failed");
return false;
}
msg.data.size = r;
if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) || !WriteFdExactly(s, &buffer[0], r)) {
return false;
}
}
return true;
}
static bool recv_compressed(borrowed_fd s, unique_fd fd) {
syncmsg msg;
msg.data.id = ID_DATA;
BrotliEncoder<SYNC_DATA_MAX> encoder;
bool sending = true;
while (sending) {
Block input(SYNC_DATA_MAX);
int r = adb_read(fd.get(), input.data(), input.size());
if (r < 0) {
SendSyncFailErrno(s, "read failed");
return false;
}
if (r == 0) {
encoder.Finish();
} else {
input.resize(r);
encoder.Append(std::move(input));
}
while (true) {
Block output;
BrotliEncodeResult result = encoder.Encode(&output);
if (result == BrotliEncodeResult::Error) {
SendSyncFailErrno(s, "compress failed");
return false;
}
if (!output.empty()) {
msg.data.size = output.size();
if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) ||
!WriteFdExactly(s, output.data(), output.size())) {
return false;
}
}
if (result == BrotliEncodeResult::Done) {
sending = false;
break;
} else if (result == BrotliEncodeResult::NeedInput) {
break;
} else if (result == BrotliEncodeResult::MoreOutput) {
continue;
}
}
}
return true;
}
static bool recv_impl(borrowed_fd s, const char* path, bool compressed, std::vector<char>& buffer) {
__android_log_security_bswrite(SEC_TAG_ADB_RECV_FILE, path);
unique_fd fd(adb_open(path, O_RDONLY | O_CLOEXEC));
@ -505,26 +675,51 @@ static bool do_recv(int s, const char* path, std::vector<char>& buffer) {
D("[ Failed to fadvise: %s ]", strerror(rc));
}
syncmsg msg;
msg.data.id = ID_DATA;
while (true) {
int r = adb_read(fd.get(), &buffer[0], buffer.size() - sizeof(msg.data));
if (r <= 0) {
if (r == 0) break;
SendSyncFailErrno(s, "read failed");
return false;
}
msg.data.size = r;
if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) || !WriteFdExactly(s, &buffer[0], r)) {
return false;
}
bool result;
if (compressed) {
result = recv_compressed(s, std::move(fd));
} else {
result = recv_uncompressed(s, std::move(fd), buffer);
}
if (!result) {
return false;
}
syncmsg msg;
msg.data.id = ID_DONE;
msg.data.size = 0;
return WriteFdExactly(s, &msg.data, sizeof(msg.data));
}
static bool do_recv_v1(borrowed_fd s, const char* path, std::vector<char>& buffer) {
return recv_impl(s, path, false, buffer);
}
static bool do_recv_v2(borrowed_fd s, const char* path, std::vector<char>& buffer) {
syncmsg msg;
// Read the setup packet.
int rc = ReadFdExactly(s, &msg.recv_v2_setup, sizeof(msg.recv_v2_setup));
if (rc == 0) {
LOG(ERROR) << "failed to read recv_v2 setup packet: EOF";
return false;
} else if (rc < 0) {
PLOG(ERROR) << "failed to read recv_v2 setup packet";
}
bool compressed = false;
if (msg.recv_v2_setup.flags & kSyncFlagBrotli) {
msg.recv_v2_setup.flags &= ~kSyncFlagBrotli;
compressed = true;
}
if (msg.recv_v2_setup.flags) {
SendSyncFail(s, android::base::StringPrintf("unknown flags: %d", msg.recv_v2_setup.flags));
return false;
}
return recv_impl(s, path, compressed, buffer);
}
static const char* sync_id_to_name(uint32_t id) {
switch (id) {
case ID_LSTAT_V1:
@ -537,10 +732,14 @@ static const char* sync_id_to_name(uint32_t id) {
return "list_v1";
case ID_LIST_V2:
return "list_v2";
case ID_SEND:
return "send";
case ID_RECV:
return "recv";
case ID_SEND_V1:
return "send_v1";
case ID_SEND_V2:
return "send_v2";
case ID_RECV_V1:
return "recv_v1";
case ID_RECV_V2:
return "recv_v2";
case ID_QUIT:
return "quit";
default:
@ -585,11 +784,17 @@ static bool handle_sync_command(int fd, std::vector<char>& buffer) {
case ID_LIST_V2:
if (!do_list_v2(fd, name)) return false;
break;
case ID_SEND:
if (!do_send(fd, name, buffer)) return false;
case ID_SEND_V1:
if (!do_send_v1(fd, name, buffer)) return false;
break;
case ID_RECV:
if (!do_recv(fd, name, buffer)) return false;
case ID_SEND_V2:
if (!do_send_v2(fd, name, buffer)) return false;
break;
case ID_RECV_V1:
if (!do_recv_v1(fd, name, buffer)) return false;
break;
case ID_RECV_V2:
if (!do_recv_v2(fd, name, buffer)) return false;
break;
case ID_QUIT:
return false;

View File

@ -27,8 +27,10 @@
#define ID_DENT_V1 MKID('D', 'E', 'N', 'T')
#define ID_DENT_V2 MKID('D', 'N', 'T', '2')
#define ID_SEND MKID('S', 'E', 'N', 'D')
#define ID_RECV MKID('R', 'E', 'C', 'V')
#define ID_SEND_V1 MKID('S', 'E', 'N', 'D')
#define ID_SEND_V2 MKID('S', 'N', 'D', '2')
#define ID_RECV_V1 MKID('R', 'E', 'C', 'V')
#define ID_RECV_V2 MKID('R', 'C', 'V', '2')
#define ID_DONE MKID('D', 'O', 'N', 'E')
#define ID_DATA MKID('D', 'A', 'T', 'A')
#define ID_OKAY MKID('O', 'K', 'A', 'Y')
@ -87,6 +89,26 @@ struct __attribute__((packed)) sync_dent_v2 {
uint32_t namelen;
}; // followed by `namelen` bytes of the name.
enum SyncFlag : uint32_t {
kSyncFlagNone = 0,
kSyncFlagBrotli = 1,
};
// send_v1 sent the path in a buffer, followed by a comma and the mode as a string.
// send_v2 sends just the path in the first request, and then sends another syncmsg (with the
// same ID!) with details.
struct __attribute__((packed)) sync_send_v2 {
uint32_t id;
uint32_t mode;
uint32_t flags;
};
// Likewise, recv_v1 just sent the path without any accompanying data.
struct __attribute__((packed)) sync_recv_v2 {
uint32_t id;
uint32_t flags;
};
struct __attribute__((packed)) sync_data {
uint32_t id;
uint32_t size;
@ -104,6 +126,8 @@ union syncmsg {
sync_dent_v2 dent_v2;
sync_data data;
sync_status status;
sync_send_v2 send_v2_setup;
sync_recv_v2 recv_v2_setup;
};
#define SYNC_DATA_MAX (64 * 1024)

View File

@ -82,6 +82,8 @@ const char* const kFeatureFixedPushSymlinkTimestamp = "fixed_push_symlink_timest
const char* const kFeatureAbbExec = "abb_exec";
const char* const kFeatureRemountShell = "remount_shell";
const char* const kFeatureTrackApp = "track_app";
const char* const kFeatureSendRecv2 = "sendrecv_v2";
const char* const kFeatureSendRecv2Brotli = "sendrecv_v2_brotli";
namespace {
@ -1177,6 +1179,8 @@ const FeatureSet& supported_features() {
kFeatureAbbExec,
kFeatureRemountShell,
kFeatureTrackApp,
kFeatureSendRecv2,
kFeatureSendRecv2Brotli,
// Increment ADB_SERVER_VERSION when adding a feature that adbd needs
// to know about. Otherwise, the client can be stuck running an old
// version of the server even after upgrading their copy of adb.

View File

@ -81,9 +81,14 @@ extern const char* const kFeatureAbb;
extern const char* const kFeatureAbbExec;
// adbd properly updates symlink timestamps on push.
extern const char* const kFeatureFixedPushSymlinkTimestamp;
// Implement `adb remount` via shelling out to /system/bin/remount.
extern const char* const kFeatureRemountShell;
// adbd supports `track-app` service reporting debuggable/profileable apps.
extern const char* const kFeatureTrackApp;
// adbd supports version 2 of send/recv.
extern const char* const kFeatureSendRecv2;
// adbd supports brotli for send/recv v2.
extern const char* const kFeatureSendRecv2Brotli;
TransportId NextTransportId();

View File

@ -150,6 +150,22 @@ struct IOVector {
IOVector& operator=(const IOVector& copy) = delete;
IOVector& operator=(IOVector&& move) noexcept;
const value_type* front_data() const {
if (chain_.empty()) {
return nullptr;
}
return chain_.front().data() + begin_offset_;
}
size_type front_size() const {
if (chain_.empty()) {
return 0;
}
return chain_.front().size() - begin_offset_;
}
size_type size() const { return chain_length_ - begin_offset_; }
bool empty() const { return size() == 0; }