adb: extend sync protocol's stat support.

am: d6d5c38469

Change-Id: I26a1518bba4f65c0fdcc7c75d751badd9a3563d3
This commit is contained in:
Josh Gao 2016-12-05 20:35:36 +00:00 committed by android-build-merger
commit 4733b631ed
9 changed files with 350 additions and 96 deletions

View File

@ -50,6 +50,7 @@ LIBADB_SRC_FILES := \
fdevent.cpp \
sockets.cpp \
socket_spec.cpp \
sysdeps/errno.cpp \
transport.cpp \
transport_local.cpp \
transport_usb.cpp \

View File

@ -51,7 +51,7 @@ constexpr size_t MAX_PAYLOAD = MAX_PAYLOAD_V2;
std::string adb_version();
// Increment this when we want to force users to start a new adb server.
#define ADB_SERVER_VERSION 37
#define ADB_SERVER_VERSION 38
class atransport;
struct usb_handle;

View File

@ -43,6 +43,7 @@
#include "adb_utils.h"
#include "file_sync_service.h"
#include "line_printer.h"
#include "sysdeps/errno.h"
#include "sysdeps/stat.h"
#include <android-base/file.h>
@ -75,8 +76,8 @@ static bool should_push_file(mode_t mode) {
struct copyinfo {
std::string lpath;
std::string rpath;
unsigned int time = 0;
unsigned int mode;
int64_t time = 0;
uint32_t mode;
uint64_t size = 0;
bool skip = false;
@ -203,9 +204,16 @@ class SyncConnection {
max = SYNC_DATA_MAX; // TODO: decide at runtime.
std::string error;
fd = adb_connect("sync:", &error);
if (fd < 0) {
Error("connect failed: %s", error.c_str());
FeatureSet features;
if (!adb_get_feature_set(&features, &error)) {
fd = -1;
Error("failed to get feature set: %s", error.c_str());
} else {
have_stat_v2_ = CanUseFeature(features, kFeatureStat2);
fd = adb_connect("sync:", &error);
if (fd < 0) {
Error("connect failed: %s", error.c_str());
}
}
}
@ -292,6 +300,77 @@ class SyncConnection {
return WriteFdExactly(fd, &buf[0], buf.size());
}
bool SendStat(const char* path_and_mode) {
if (!have_stat_v2_) {
errno = ENOTSUP;
return false;
}
return SendRequest(ID_STAT_V2, path_and_mode);
}
bool SendLstat(const char* path_and_mode) {
if (have_stat_v2_) {
return SendRequest(ID_LSTAT_V2, path_and_mode);
} else {
return SendRequest(ID_LSTAT_V1, path_and_mode);
}
}
bool FinishStat(struct stat* st) {
syncmsg msg;
memset(st, 0, sizeof(*st));
if (have_stat_v2_) {
if (!ReadFdExactly(fd, &msg.stat_v2, sizeof(msg.stat_v2))) {
fatal_errno("protocol fault: failed to read stat response");
}
if (msg.stat_v2.id != ID_LSTAT_V2 && msg.stat_v2.id != ID_STAT_V2) {
fatal_errno("protocol fault: stat response has wrong message id: %" PRIx32,
msg.stat_v2.id);
}
if (msg.stat_v2.error != 0) {
errno = translate_linux_errno(msg.stat_v2.error);
return false;
}
st->st_dev = msg.stat_v2.dev;
st->st_ino = msg.stat_v2.ino;
st->st_mode = msg.stat_v2.mode;
st->st_nlink = msg.stat_v2.nlink;
st->st_uid = msg.stat_v2.uid;
st->st_gid = msg.stat_v2.gid;
st->st_size = msg.stat_v2.size;
st->st_atime = msg.stat_v2.atime;
st->st_mtime = msg.stat_v2.mtime;
st->st_ctime = msg.stat_v2.ctime;
return true;
} else {
if (!ReadFdExactly(fd, &msg.stat_v1, sizeof(msg.stat_v1))) {
fatal_errno("protocol fault: failed to read stat response");
}
if (msg.stat_v1.id != ID_LSTAT_V1) {
fatal_errno("protocol fault: stat response has wrong message id: %" PRIx32,
msg.stat_v1.id);
}
if (msg.stat_v1.mode == 0 && msg.stat_v1.size == 0 && msg.stat_v1.time == 0) {
// There's no way for us to know what the error was.
errno = ENOPROTOOPT;
return false;
}
st->st_mode = msg.stat_v1.mode;
st->st_size = msg.stat_v1.size;
st->st_ctime = msg.stat_v1.time;
st->st_mtime = msg.stat_v1.time;
}
return true;
}
// Sending header, payload, and footer in a single write makes a huge
// difference to "adb sync" performance.
bool SendSmallFile(const char* path_and_mode,
@ -429,7 +508,7 @@ class SyncConnection {
return false;
}
buf[msg.status.msglen] = 0;
Error("failed to copy '%s' to '%s': %s", from, to, &buf[0]);
Error("failed to copy '%s' to '%s': remote %s", from, to, &buf[0]);
return false;
}
@ -500,6 +579,7 @@ class SyncConnection {
private:
bool expect_done_;
bool have_stat_v2_;
TransferLedger global_ledger_;
TransferLedger current_ledger_;
@ -555,25 +635,47 @@ static bool sync_ls(SyncConnection& sc, const char* path,
}
}
static bool sync_finish_stat(SyncConnection& sc, unsigned int* timestamp,
unsigned int* mode, unsigned int* size) {
syncmsg msg;
if (!ReadFdExactly(sc.fd, &msg.stat, sizeof(msg.stat)) || msg.stat.id != ID_STAT) {
static bool sync_stat(SyncConnection& sc, const char* path, struct stat* st) {
return sc.SendStat(path) && sc.FinishStat(st);
}
static bool sync_lstat(SyncConnection& sc, const char* path, struct stat* st) {
return sc.SendLstat(path) && sc.FinishStat(st);
}
static bool sync_stat_fallback(SyncConnection& sc, const char* path, struct stat* st) {
if (sync_stat(sc, path, st)) {
return true;
}
if (errno != ENOTSUP) {
return false;
}
if (timestamp) *timestamp = msg.stat.time;
if (mode) *mode = msg.stat.mode;
if (size) *size = msg.stat.size;
// Try to emulate the parts we can when talking to older adbds.
bool lstat_result = sync_lstat(sc, path, st);
if (!lstat_result) {
return false;
}
if (S_ISLNK(st->st_mode)) {
// If the target is a symlink, figure out whether it's a file or a directory.
// Also, zero out the st_size field, since no one actually cares what the path length is.
st->st_size = 0;
std::string dir_path = path;
dir_path.push_back('/');
struct stat tmp_st;
st->st_mode &= ~S_IFMT;
if (sync_lstat(sc, dir_path.c_str(), &tmp_st)) {
st->st_mode |= S_IFDIR;
} else {
st->st_mode |= S_IFREG;
}
}
return true;
}
static bool sync_stat(SyncConnection& sc, const char* path,
unsigned int* timestamp, unsigned int* mode, unsigned int* size) {
return sc.SendRequest(ID_STAT, path) && sync_finish_stat(sc, timestamp, mode, size);
}
static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath,
unsigned mtime, mode_t mode)
{
@ -621,8 +723,11 @@ static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath,
static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
const char* name=nullptr) {
unsigned size = 0;
if (!sync_stat(sc, rpath, nullptr, nullptr, &size)) return false;
struct stat st;
if (!sync_stat_fallback(sc, rpath, &st)) {
sc.Error("stat failed when trying to receive %s: %s", rpath, strerror(errno));
return false;
}
if (!sc.SendRequest(ID_RECV, rpath)) return false;
@ -675,7 +780,7 @@ static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
bytes_copied += msg.data.size;
sc.RecordBytesTransferred(msg.data.size);
sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, size);
sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, st.st_size);
}
sc.RecordFilesTransferred(1);
@ -778,20 +883,20 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,
if (check_timestamps) {
for (const copyinfo& ci : file_list) {
if (!sc.SendRequest(ID_STAT, ci.rpath.c_str())) {
if (!sc.SendLstat(ci.rpath.c_str())) {
sc.Error("failed to send lstat");
return false;
}
}
for (copyinfo& ci : file_list) {
unsigned int timestamp, mode, size;
if (!sync_finish_stat(sc, &timestamp, &mode, &size)) {
return false;
}
if (size == ci.size) {
// For links, we cannot update the atime/mtime.
if ((S_ISREG(ci.mode & mode) && timestamp == ci.time) ||
(S_ISLNK(ci.mode & mode) && timestamp >= ci.time)) {
ci.skip = true;
struct stat st;
if (sc.FinishStat(&st)) {
if (st.st_size == static_cast<off_t>(ci.size)) {
// For links, we cannot update the atime/mtime.
if ((S_ISREG(ci.mode & st.st_mode) && st.st_mtime == ci.time) ||
(S_ISLNK(ci.mode & st.st_mode) && st.st_mtime >= ci.time)) {
ci.skip = true;
}
}
}
}
@ -823,10 +928,22 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
if (!sc.IsValid()) return false;
bool success = true;
unsigned dst_mode;
if (!sync_stat(sc, dst, nullptr, &dst_mode, nullptr)) return false;
bool dst_exists = (dst_mode != 0);
bool dst_isdir = S_ISDIR(dst_mode);
bool dst_exists;
bool dst_isdir;
struct stat st;
if (sync_stat_fallback(sc, dst, &st)) {
dst_exists = true;
dst_isdir = S_ISDIR(st.st_mode);
} else {
if (errno == ENOENT || errno == ENOPROTOOPT) {
dst_exists = false;
dst_isdir = false;
} else {
sc.Error("stat failed when trying to push to %s: %s", dst, strerror(errno));
return false;
}
}
if (!dst_isdir) {
if (srcs.size() > 1) {
@ -871,8 +988,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
dst_dir.append(adb_basename(src_path));
}
success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(),
false, false);
success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), false, false);
continue;
} else if (!should_push_file(st.st_mode)) {
sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@ -901,17 +1017,6 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
return success;
}
static bool remote_symlink_isdir(SyncConnection& sc, const std::string& rpath) {
unsigned mode;
std::string dir_path = rpath;
dir_path.push_back('/');
if (!sync_stat(sc, dir_path.c_str(), nullptr, &mode, nullptr)) {
sc.Error("failed to stat remote symlink '%s'", dir_path.c_str());
return false;
}
return S_ISDIR(mode);
}
static bool remote_build_list(SyncConnection& sc, std::vector<copyinfo>* file_list,
const std::string& rpath, const std::string& lpath) {
std::vector<copyinfo> dirlist;
@ -949,7 +1054,13 @@ static bool remote_build_list(SyncConnection& sc, std::vector<copyinfo>* file_li
// Check each symlink we found to see whether it's a file or directory.
for (copyinfo& link_ci : linklist) {
if (remote_symlink_isdir(sc, link_ci.rpath)) {
struct stat st;
if (!sync_stat_fallback(sc, link_ci.rpath.c_str(), &st)) {
sc.Warning("stat failed for path %s: %s", link_ci.rpath.c_str(), strerror(errno));
continue;
}
if (S_ISDIR(st.st_mode)) {
dirlist.emplace_back(std::move(link_ci));
} else {
file_list->emplace_back(std::move(link_ci));
@ -1075,22 +1186,19 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
for (const char* src_path : srcs) {
const char* dst_path = dst;
unsigned src_mode, src_time, src_size;
if (!sync_stat(sc, src_path, &src_time, &src_mode, &src_size)) {
sc.Error("failed to stat remote object '%s'", src_path);
return false;
}
if (src_mode == 0) {
sc.Error("remote object '%s' does not exist", src_path);
struct stat src_st;
if (!sync_stat_fallback(sc, src_path, &src_st)) {
if (errno == ENOPROTOOPT) {
sc.Error("remote object '%s' does not exist", src_path);
} else {
sc.Error("failed to stat remote object '%s': %s", src_path, strerror(errno));
}
success = false;
continue;
}
bool src_isdir = S_ISDIR(src_mode);
if (S_ISLNK(src_mode)) {
src_isdir = remote_symlink_isdir(sc, src_path);
}
bool src_isdir = S_ISDIR(src_st.st_mode);
if (src_isdir) {
std::string dst_dir = dst;
@ -1109,8 +1217,8 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), copy_attrs);
continue;
} else if (!should_pull_file(src_mode)) {
sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_mode);
} else if (!should_pull_file(src_st.st_mode)) {
sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_st.st_mode);
continue;
}
@ -1125,13 +1233,13 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
}
sc.NewTransfer();
sc.SetExpectedTotalBytes(src_size);
sc.SetExpectedTotalBytes(src_st.st_size);
if (!sync_recv(sc, src_path, dst_path, name)) {
success = false;
continue;
}
if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) {
if (copy_attrs && set_time_and_mode(dst_path, src_st.st_mtime, src_st.st_mode) != 0) {
success = false;
continue;
}

View File

@ -98,18 +98,47 @@ static bool secure_mkdirs(const std::string& path) {
return true;
}
static bool do_stat(int s, const char* path) {
syncmsg msg;
msg.stat.id = ID_STAT;
static bool do_lstat_v1(int s, const char* path) {
syncmsg msg = {};
msg.stat_v1.id = ID_LSTAT_V1;
struct stat st = {};
// TODO: add a way to report that the stat failed!
lstat(path, &st);
msg.stat.mode = st.st_mode;
msg.stat.size = st.st_size;
msg.stat.time = st.st_mtime;
msg.stat_v1.mode = st.st_mode;
msg.stat_v1.size = st.st_size;
msg.stat_v1.time = st.st_mtime;
return WriteFdExactly(s, &msg.stat_v1, sizeof(msg.stat_v1));
}
return WriteFdExactly(s, &msg.stat, sizeof(msg.stat));
static bool do_stat_v2(int s, uint32_t id, const char* path) {
syncmsg msg = {};
msg.stat_v2.id = id;
decltype(&stat) stat_fn;
if (id == ID_STAT_V2) {
stat_fn = stat;
} else {
stat_fn = lstat;
}
struct stat st = {};
int rc = stat_fn(path, &st);
if (rc == -1) {
msg.stat_v2.error = errno;
} else {
msg.stat_v2.dev = st.st_dev;
msg.stat_v2.ino = st.st_ino;
msg.stat_v2.mode = st.st_mode;
msg.stat_v2.nlink = st.st_nlink;
msg.stat_v2.uid = st.st_uid;
msg.stat_v2.gid = st.st_gid;
msg.stat_v2.size = st.st_size;
msg.stat_v2.atime = st.st_atime;
msg.stat_v2.mtime = st.st_mtime;
msg.stat_v2.ctime = st.st_ctime;
}
return WriteFdExactly(s, &msg.stat_v2, sizeof(msg.stat_v2));
}
static bool do_list(int s, const char* path) {
@ -427,30 +456,34 @@ static bool handle_sync_command(int fd, std::vector<char>& buffer) {
D("sync: '%.4s' '%s'", id, name);
switch (request.id) {
case ID_STAT:
if (!do_stat(fd, name)) return false;
break;
case ID_LIST:
if (!do_list(fd, name)) return false;
break;
case ID_SEND:
if (!do_send(fd, name, buffer)) return false;
break;
case ID_RECV:
if (!do_recv(fd, name, buffer)) return false;
break;
case ID_QUIT:
return false;
default:
SendSyncFail(fd, android::base::StringPrintf("unknown command '%.4s' (%08x)",
id, request.id));
return false;
case ID_LSTAT_V1:
if (!do_lstat_v1(fd, name)) return false;
break;
case ID_LSTAT_V2:
case ID_STAT_V2:
if (!do_stat_v2(fd, request.id, name)) return false;
break;
case ID_LIST:
if (!do_list(fd, name)) return false;
break;
case ID_SEND:
if (!do_send(fd, name, buffer)) return false;
break;
case ID_RECV:
if (!do_recv(fd, name, buffer)) return false;
break;
case ID_QUIT:
return false;
default:
SendSyncFail(
fd, android::base::StringPrintf("unknown command '%.4s' (%08x)", id, request.id));
return false;
}
return true;
}
void file_sync_service(int fd, void* cookie) {
void file_sync_service(int fd, void*) {
std::vector<char> buffer(SYNC_DATA_MAX);
while (handle_sync_command(fd, buffer)) {

View File

@ -22,7 +22,9 @@
#define MKID(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24))
#define ID_STAT MKID('S','T','A','T')
#define ID_LSTAT_V1 MKID('S','T','A','T')
#define ID_STAT_V2 MKID('S','T','A','2')
#define ID_LSTAT_V2 MKID('L','S','T','2')
#define ID_LIST MKID('L','I','S','T')
#define ID_SEND MKID('S','E','N','D')
#define ID_RECV MKID('R','E','C','V')
@ -45,7 +47,21 @@ union syncmsg {
uint32_t mode;
uint32_t size;
uint32_t time;
} stat;
} stat_v1;
struct __attribute__((packed)) {
uint32_t id;
uint32_t error;
uint64_t dev;
uint64_t ino;
uint32_t mode;
uint32_t nlink;
uint32_t uid;
uint32_t gid;
uint64_t size;
int64_t atime;
int64_t mtime;
int64_t ctime;
} stat_v2;
struct __attribute__((packed)) {
uint32_t id;
uint32_t mode;

74
adb/sysdeps/errno.cpp Normal file
View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2016 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.
*/
#include "sysdeps/errno.h"
#include <errno.h>
#include <thread>
#include <unordered_map>
#include "adb.h"
#if defined(_WIN32)
#define ETXTBSY EBUSY
#endif
static std::unordered_map<int, int> initialize_translations() {
std::unordered_map<int, int> translations;
#if defined(__linux__)
#define ERRNO_VALUE(error_name, linux_value) static_assert((error_name) == (linux_value), "")
#else
#define ERRNO_VALUE(error_name, linux_value) \
translations.insert(std::make_pair((linux_value), (error_name)))
#endif
// Untranslated errno values returned by open: EDQUOT, ENODEV, ENXIO, EWOULDBLOCK
ERRNO_VALUE(EACCES, 13);
ERRNO_VALUE(EEXIST, 17);
ERRNO_VALUE(EFAULT, 14);
ERRNO_VALUE(EFBIG, 27);
ERRNO_VALUE(EINTR, 4);
ERRNO_VALUE(EINVAL, 22);
ERRNO_VALUE(EIO, 5);
ERRNO_VALUE(EISDIR, 21);
ERRNO_VALUE(ELOOP, 40);
ERRNO_VALUE(EMFILE, 24);
ERRNO_VALUE(ENAMETOOLONG, 36);
ERRNO_VALUE(ENFILE, 23);
ERRNO_VALUE(ENOENT, 2);
ERRNO_VALUE(ENOMEM, 12);
ERRNO_VALUE(ENOSPC, 28);
ERRNO_VALUE(ENOTDIR, 20);
ERRNO_VALUE(EOVERFLOW, 75);
ERRNO_VALUE(EPERM, 1);
ERRNO_VALUE(EROFS, 30);
ERRNO_VALUE(ETXTBSY, 26);
return translations;
}
int translate_linux_errno(int error) {
#if defined(__linux__)
UNUSED(initialize_translations);
return error;
#else
static std::unordered_map<int, int> translations = initialize_translations();
auto it = translations.find(error);
if (it != translations.end()) {
return it->second;
}
fatal("received unexpected remote errno: %d", error);
#endif
}

19
adb/sysdeps/errno.h Normal file
View File

@ -0,0 +1,19 @@
/*
* Copyright (C) 2016 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
int translate_linux_errno(int error);

View File

@ -49,6 +49,7 @@ static std::mutex& transport_lock = *new std::mutex();
const char* const kFeatureShell2 = "shell_v2";
const char* const kFeatureCmd = "cmd";
const char* const kFeatureStat2 = "stat_v2";
static std::string dump_packet(const char* name, const char* func, apacket* p) {
unsigned command = p->msg.command;
@ -771,7 +772,8 @@ const FeatureSet& supported_features() {
// Local static allocation to avoid global non-POD variables.
static const FeatureSet* features = new FeatureSet{
kFeatureShell2,
kFeatureCmd
kFeatureCmd,
kFeatureStat2,
// Increment ADB_SERVER_VERSION whenever the feature list changes to
// make sure that the adb client and server features stay in sync
// (http://b/24370690).

View File

@ -46,6 +46,7 @@ bool CanUseFeature(const FeatureSet& feature_set, const std::string& feature);
extern const char* const kFeatureShell2;
// The 'cmd' command is available
extern const char* const kFeatureCmd;
extern const char* const kFeatureStat2;
class atransport {
public: