Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a763771 authored by Josh Gao's avatar Josh Gao Committed by Gerrit Code Review
Browse files

Merge changes I1a122235,I6b22ead8

* changes:
  Revert "Revert "adb: extend sync protocol's stat support.""
  Revert "Revert "adb: move adb_strerror to sysdeps/win32/errno.cpp.""
parents e209ed7c 5a1e3fda
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -50,6 +50,7 @@ LIBADB_SRC_FILES := \
    fdevent.cpp \
    fdevent.cpp \
    sockets.cpp \
    sockets.cpp \
    socket_spec.cpp \
    socket_spec.cpp \
    sysdeps/errno.cpp \
    transport.cpp \
    transport.cpp \
    transport_local.cpp \
    transport_local.cpp \
    transport_usb.cpp \
    transport_usb.cpp \
@@ -88,10 +89,12 @@ LIBADB_linux_SRC_FILES := \


LIBADB_windows_SRC_FILES := \
LIBADB_windows_SRC_FILES := \
    sysdeps_win32.cpp \
    sysdeps_win32.cpp \
    sysdeps/win32/errno.cpp \
    sysdeps/win32/stat.cpp \
    sysdeps/win32/stat.cpp \
    usb_windows.cpp \
    usb_windows.cpp \


LIBADB_TEST_windows_SRCS := \
LIBADB_TEST_windows_SRCS := \
    sysdeps/win32/errno_test.cpp \
    sysdeps_win32_test.cpp \
    sysdeps_win32_test.cpp \


include $(CLEAR_VARS)
include $(CLEAR_VARS)
+1 −1
Original line number Original line Diff line number Diff line
@@ -51,7 +51,7 @@ constexpr size_t MAX_PAYLOAD = MAX_PAYLOAD_V2;
std::string adb_version();
std::string adb_version();


// Increment this when we want to force users to start a new adb server.
// 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;
class atransport;
struct usb_handle;
struct usb_handle;
+173 −67
Original line number Original line Diff line number Diff line
@@ -15,12 +15,10 @@
 */
 */


#include <dirent.h>
#include <dirent.h>
#include <errno.h>
#include <inttypes.h>
#include <inttypes.h>
#include <limits.h>
#include <limits.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/types.h>
@@ -43,6 +41,7 @@
#include "adb_utils.h"
#include "adb_utils.h"
#include "file_sync_service.h"
#include "file_sync_service.h"
#include "line_printer.h"
#include "line_printer.h"
#include "sysdeps/errno.h"
#include "sysdeps/stat.h"
#include "sysdeps/stat.h"


#include <android-base/file.h>
#include <android-base/file.h>
@@ -75,8 +74,8 @@ static bool should_push_file(mode_t mode) {
struct copyinfo {
struct copyinfo {
    std::string lpath;
    std::string lpath;
    std::string rpath;
    std::string rpath;
    unsigned int time = 0;
    int64_t time = 0;
    unsigned int mode;
    uint32_t mode;
    uint64_t size = 0;
    uint64_t size = 0;
    bool skip = false;
    bool skip = false;


@@ -203,11 +202,18 @@ class SyncConnection {
        max = SYNC_DATA_MAX; // TODO: decide at runtime.
        max = SYNC_DATA_MAX; // TODO: decide at runtime.


        std::string error;
        std::string error;
        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);
            fd = adb_connect("sync:", &error);
            if (fd < 0) {
            if (fd < 0) {
                Error("connect failed: %s", error.c_str());
                Error("connect failed: %s", error.c_str());
            }
            }
        }
        }
    }


    ~SyncConnection() {
    ~SyncConnection() {
        if (!IsValid()) return;
        if (!IsValid()) return;
@@ -292,6 +298,77 @@ class SyncConnection {
        return WriteFdExactly(fd, &buf[0], buf.size());
        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 = errno_from_wire(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
    // Sending header, payload, and footer in a single write makes a huge
    // difference to "adb sync" performance.
    // difference to "adb sync" performance.
    bool SendSmallFile(const char* path_and_mode,
    bool SendSmallFile(const char* path_and_mode,
@@ -429,7 +506,7 @@ class SyncConnection {
            return false;
            return false;
        }
        }
        buf[msg.status.msglen] = 0;
        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;
        return false;
    }
    }


@@ -500,6 +577,7 @@ class SyncConnection {


  private:
  private:
    bool expect_done_;
    bool expect_done_;
    bool have_stat_v2_;


    TransferLedger global_ledger_;
    TransferLedger global_ledger_;
    TransferLedger current_ledger_;
    TransferLedger current_ledger_;
@@ -555,23 +633,45 @@ static bool sync_ls(SyncConnection& sc, const char* path,
    }
    }
}
}


static bool sync_finish_stat(SyncConnection& sc, unsigned int* timestamp,
static bool sync_stat(SyncConnection& sc, const char* path, struct stat* st) {
                             unsigned int* mode, unsigned int* size) {
    return sc.SendStat(path) && sc.FinishStat(st);
    syncmsg msg;
    if (!ReadFdExactly(sc.fd, &msg.stat, sizeof(msg.stat)) || msg.stat.id != ID_STAT) {
        return false;
}
}


    if (timestamp) *timestamp = msg.stat.time;
static bool sync_lstat(SyncConnection& sc, const char* path, struct stat* st) {
    if (mode) *mode = msg.stat.mode;
    return sc.SendLstat(path) && sc.FinishStat(st);
    if (size) *size = msg.stat.size;
}


static bool sync_stat_fallback(SyncConnection& sc, const char* path, struct stat* st) {
    if (sync_stat(sc, path, st)) {
        return true;
        return true;
    }
    }


static bool sync_stat(SyncConnection& sc, const char* path,
    if (errno != ENOTSUP) {
                      unsigned int* timestamp, unsigned int* mode, unsigned int* size) {
        return false;
    return sc.SendRequest(ID_STAT, path) && sync_finish_stat(sc, timestamp, mode, 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_send(SyncConnection& sc, const char* lpath, const char* rpath,
static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath,
@@ -621,8 +721,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,
static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
                      const char* name=nullptr) {
                      const char* name=nullptr) {
    unsigned size = 0;
    struct stat st;
    if (!sync_stat(sc, rpath, nullptr, nullptr, &size)) return false;
    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;
    if (!sc.SendRequest(ID_RECV, rpath)) return false;


@@ -675,7 +778,7 @@ static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
        bytes_copied += msg.data.size;
        bytes_copied += msg.data.size;


        sc.RecordBytesTransferred(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);
    sc.RecordFilesTransferred(1);
@@ -778,24 +881,24 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,


    if (check_timestamps) {
    if (check_timestamps) {
        for (const copyinfo& ci : file_list) {
        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;
                return false;
            }
            }
        }
        }
        for (copyinfo& ci : file_list) {
        for (copyinfo& ci : file_list) {
            unsigned int timestamp, mode, size;
            struct stat st;
            if (!sync_finish_stat(sc, &timestamp, &mode, &size)) {
            if (sc.FinishStat(&st)) {
                return false;
                if (st.st_size == static_cast<off_t>(ci.size)) {
            }
            if (size == ci.size) {
                    // For links, we cannot update the atime/mtime.
                    // For links, we cannot update the atime/mtime.
                if ((S_ISREG(ci.mode & mode) && timestamp == ci.time) ||
                    if ((S_ISREG(ci.mode & st.st_mode) && st.st_mtime == ci.time) ||
                        (S_ISLNK(ci.mode & mode) && timestamp >= ci.time)) {
                        (S_ISLNK(ci.mode & st.st_mode) && st.st_mtime >= ci.time)) {
                        ci.skip = true;
                        ci.skip = true;
                    }
                    }
                }
                }
            }
            }
        }
        }
    }


    sc.ComputeExpectedTotalBytes(file_list);
    sc.ComputeExpectedTotalBytes(file_list);


@@ -823,10 +926,22 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
    if (!sc.IsValid()) return false;
    if (!sc.IsValid()) return false;


    bool success = true;
    bool success = true;
    unsigned dst_mode;
    bool dst_exists;
    if (!sync_stat(sc, dst, nullptr, &dst_mode, nullptr)) return false;
    bool dst_isdir;
    bool dst_exists = (dst_mode != 0);

    bool dst_isdir = S_ISDIR(dst_mode);
    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 (!dst_isdir) {
        if (srcs.size() > 1) {
        if (srcs.size() > 1) {
@@ -871,8 +986,7 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
                dst_dir.append(adb_basename(src_path));
                dst_dir.append(adb_basename(src_path));
            }
            }


            success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(),
            success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), false, false);
                                             false, false);
            continue;
            continue;
        } else if (!should_push_file(st.st_mode)) {
        } else if (!should_push_file(st.st_mode)) {
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@@ -901,17 +1015,6 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
    return success;
    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,
static bool remote_build_list(SyncConnection& sc, std::vector<copyinfo>* file_list,
                              const std::string& rpath, const std::string& lpath) {
                              const std::string& rpath, const std::string& lpath) {
    std::vector<copyinfo> dirlist;
    std::vector<copyinfo> dirlist;
@@ -949,7 +1052,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.
    // Check each symlink we found to see whether it's a file or directory.
    for (copyinfo& link_ci : linklist) {
    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));
            dirlist.emplace_back(std::move(link_ci));
        } else {
        } else {
            file_list->emplace_back(std::move(link_ci));
            file_list->emplace_back(std::move(link_ci));
@@ -1075,22 +1184,19 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,


    for (const char* src_path : srcs) {
    for (const char* src_path : srcs) {
        const char* dst_path = dst;
        const char* dst_path = dst;
        unsigned src_mode, src_time, src_size;
        struct stat src_st;
        if (!sync_stat(sc, src_path, &src_time, &src_mode, &src_size)) {
        if (!sync_stat_fallback(sc, src_path, &src_st)) {
            sc.Error("failed to stat remote object '%s'", src_path);
            if (errno == ENOPROTOOPT) {
            return false;
        }
        if (src_mode == 0) {
                sc.Error("remote object '%s' does not exist", src_path);
                sc.Error("remote object '%s' does not exist", src_path);
            success = false;
            } else {
            continue;
                sc.Error("failed to stat remote object '%s': %s", src_path, strerror(errno));
            }
            }


        bool src_isdir = S_ISDIR(src_mode);
            success = false;
        if (S_ISLNK(src_mode)) {
            continue;
            src_isdir = remote_symlink_isdir(sc, src_path);
        }
        }


        bool src_isdir = S_ISDIR(src_st.st_mode);
        if (src_isdir) {
        if (src_isdir) {
            std::string dst_dir = dst;
            std::string dst_dir = dst;


@@ -1109,8 +1215,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);
            success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), copy_attrs);
            continue;
            continue;
        } else if (!should_pull_file(src_mode)) {
        } else if (!should_pull_file(src_st.st_mode)) {
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_mode);
            sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_st.st_mode);
            continue;
            continue;
        }
        }


@@ -1125,13 +1231,13 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
        }
        }


        sc.NewTransfer();
        sc.NewTransfer();
        sc.SetExpectedTotalBytes(src_size);
        sc.SetExpectedTotalBytes(src_st.st_size);
        if (!sync_recv(sc, src_path, dst_path, name)) {
        if (!sync_recv(sc, src_path, dst_path, name)) {
            success = false;
            success = false;
            continue;
            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;
            success = false;
            continue;
            continue;
        }
        }
+61 −27
Original line number Original line Diff line number Diff line
@@ -41,6 +41,7 @@
#include "adb_io.h"
#include "adb_io.h"
#include "adb_utils.h"
#include "adb_utils.h"
#include "security_log_tags.h"
#include "security_log_tags.h"
#include "sysdeps/errno.h"


static bool should_use_fs_config(const std::string& path) {
static bool should_use_fs_config(const std::string& path) {
    // TODO: use fs_config to configure permissions on /data.
    // TODO: use fs_config to configure permissions on /data.
@@ -98,18 +99,47 @@ static bool secure_mkdirs(const std::string& path) {
    return true;
    return true;
}
}


static bool do_stat(int s, const char* path) {
static bool do_lstat_v1(int s, const char* path) {
    syncmsg msg;
    syncmsg msg = {};
    msg.stat.id = ID_STAT;
    msg.stat_v1.id = ID_LSTAT_V1;


    struct stat st = {};
    struct stat st = {};
    // TODO: add a way to report that the stat failed!
    lstat(path, &st);
    lstat(path, &st);
    msg.stat.mode = st.st_mode;
    msg.stat_v1.mode = st.st_mode;
    msg.stat.size = st.st_size;
    msg.stat_v1.size = st.st_size;
    msg.stat.time = st.st_mtime;
    msg.stat_v1.time = st.st_mtime;
    return WriteFdExactly(s, &msg.stat_v1, sizeof(msg.stat_v1));
}

static bool do_stat_v2(int s, uint32_t id, const char* path) {
    syncmsg msg = {};
    msg.stat_v2.id = id;


    return WriteFdExactly(s, &msg.stat, sizeof(msg.stat));
    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_to_wire(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) {
static bool do_list(int s, const char* path) {
@@ -427,8 +457,12 @@ static bool handle_sync_command(int fd, std::vector<char>& buffer) {
    D("sync: '%.4s' '%s'", id, name);
    D("sync: '%.4s' '%s'", id, name);


    switch (request.id) {
    switch (request.id) {
      case ID_STAT:
        case ID_LSTAT_V1:
        if (!do_stat(fd, name)) return false;
            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;
            break;
        case ID_LIST:
        case ID_LIST:
            if (!do_list(fd, name)) return false;
            if (!do_list(fd, name)) return false;
@@ -442,15 +476,15 @@ static bool handle_sync_command(int fd, std::vector<char>& buffer) {
        case ID_QUIT:
        case ID_QUIT:
            return false;
            return false;
        default:
        default:
        SendSyncFail(fd, android::base::StringPrintf("unknown command '%.4s' (%08x)",
            SendSyncFail(
                                                     id, request.id));
                fd, android::base::StringPrintf("unknown command '%.4s' (%08x)", id, request.id));
            return false;
            return false;
    }
    }


    return true;
    return true;
}
}


void file_sync_service(int fd, void* cookie) {
void file_sync_service(int fd, void*) {
    std::vector<char> buffer(SYNC_DATA_MAX);
    std::vector<char> buffer(SYNC_DATA_MAX);


    while (handle_sync_command(fd, buffer)) {
    while (handle_sync_command(fd, buffer)) {
+18 −2
Original line number Original line Diff line number Diff line
@@ -22,7 +22,9 @@


#define MKID(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24))
#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_LIST MKID('L','I','S','T')
#define ID_SEND MKID('S','E','N','D')
#define ID_SEND MKID('S','E','N','D')
#define ID_RECV MKID('R','E','C','V')
#define ID_RECV MKID('R','E','C','V')
@@ -45,7 +47,21 @@ union syncmsg {
        uint32_t mode;
        uint32_t mode;
        uint32_t size;
        uint32_t size;
        uint32_t time;
        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)) {
    struct __attribute__((packed)) {
        uint32_t id;
        uint32_t id;
        uint32_t mode;
        uint32_t mode;
Loading