diff --git a/adb/adb.cpp b/adb/adb.cpp index 9c0eecafc..fdf720c5c 100644 --- a/adb/adb.cpp +++ b/adb/adb.cpp @@ -357,9 +357,9 @@ void handle_packet(apacket *p, atransport *t) case A_OPEN: /* OPEN(local-id, 0, "destination") */ if (t->online && p->msg.arg0 != 0 && p->msg.arg1 == 0) { - // TODO: Switch to string_view. - std::string address(p->payload.begin(), p->payload.end()); - asocket* s = create_local_service_socket(address.c_str(), t); + std::string_view address(p->payload.begin(), p->payload.size()); + + asocket* s = create_local_service_socket(address, t); if (s == nullptr) { send_close(0, p->msg.arg0, t); } else { @@ -600,7 +600,7 @@ static void ReportServerStartupFailure(pid_t pid) { fprintf(stderr, "Full server startup log: %s\n", GetLogFilePath().c_str()); fprintf(stderr, "Server had pid: %d\n", pid); - android::base::unique_fd fd(unix_open(GetLogFilePath().c_str(), O_RDONLY)); + android::base::unique_fd fd(unix_open(GetLogFilePath(), O_RDONLY)); if (fd == -1) return; // Let's not show more than 128KiB of log... diff --git a/adb/adb.h b/adb/adb.h index cdd63465c..47ea0e841 100644 --- a/adb/adb.h +++ b/adb/adb.h @@ -139,9 +139,9 @@ atransport* find_emulator_transport_by_adb_port(int adb_port); atransport* find_emulator_transport_by_console_port(int console_port); #endif -int service_to_fd(const char* name, atransport* transport); +int service_to_fd(std::string_view name, atransport* transport); #if !ADB_HOST -unique_fd daemon_service_to_fd(const char* name, atransport* transport); +unique_fd daemon_service_to_fd(std::string_view name, atransport* transport); #endif #if ADB_HOST diff --git a/adb/adb_trace.cpp b/adb/adb_trace.cpp index a024a89bf..2bd6a3ea1 100644 --- a/adb/adb_trace.cpp +++ b/adb/adb_trace.cpp @@ -72,8 +72,7 @@ static std::string get_log_file_name() { } void start_device_log(void) { - int fd = unix_open(get_log_file_name().c_str(), - O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640); + int fd = unix_open(get_log_file_name(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640); if (fd == -1) { return; } diff --git a/adb/adb_utils.h b/adb/adb_utils.h index 6d12225dc..8253487a8 100644 --- a/adb/adb_utils.h +++ b/adb/adb_utils.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -94,3 +96,47 @@ class BlockingQueue { }; std::string GetLogFilePath(); + +inline std::string_view StripTrailingNulls(std::string_view str) { + size_t n = 0; + for (auto it = str.rbegin(); it != str.rend(); ++it) { + if (*it != '\0') { + break; + } + ++n; + } + + str.remove_suffix(n); + return str; +} + +// Base-10 stroll on a string_view. +template +inline bool ParseUint(T* result, std::string_view str, std::string_view* remaining) { + if (str.empty() || !isdigit(str[0])) { + return false; + } + + T value = 0; + std::string_view::iterator it; + constexpr T max = std::numeric_limits::max(); + for (it = str.begin(); it != str.end() && isdigit(*it); ++it) { + if (value > max / 10) { + return false; + } + + value *= 10; + + T digit = *it - '0'; + if (value > max - digit) { + return false; + } + + value += digit; + } + *result = value; + if (remaining) { + *remaining = str.substr(it - str.begin()); + } + return true; +} diff --git a/adb/adb_utils_test.cpp b/adb/adb_utils_test.cpp index 870f6f048..bb094258c 100644 --- a/adb/adb_utils_test.cpp +++ b/adb/adb_utils_test.cpp @@ -181,3 +181,48 @@ TEST(adb_utils, test_forward_targets_are_valid) { EXPECT_FALSE(forward_targets_are_valid("tcp:8000", "tcp:a", &error)); EXPECT_FALSE(forward_targets_are_valid("tcp:8000", "tcp:22x", &error)); } + +void TestParseUint(std::string_view string, bool expected_success, uint32_t expected_value = 0) { + // Standalone. + { + uint32_t value; + std::string_view remaining; + bool success = ParseUint(&value, string, &remaining); + EXPECT_EQ(success, expected_success); + if (expected_success) { + EXPECT_EQ(value, expected_value); + } + EXPECT_TRUE(remaining.empty()); + } + + // With trailing text. + { + std::string text = std::string(string) + "foo"; + uint32_t value; + std::string_view remaining; + bool success = ParseUint(&value, text, &remaining); + EXPECT_EQ(success, expected_success); + if (expected_success) { + EXPECT_EQ(value, expected_value); + EXPECT_EQ(remaining, "foo"); + } + } +} + +TEST(adb_utils, ParseUint) { + TestParseUint("", false); + TestParseUint("foo", false); + TestParseUint("foo123", false); + TestParseUint("-1", false); + + TestParseUint("123", true, 123); + TestParseUint("9999999999999999999999999", false); + TestParseUint(std::to_string(UINT32_MAX), true, UINT32_MAX); + TestParseUint("0" + std::to_string(UINT32_MAX), true, UINT32_MAX); + TestParseUint(std::to_string(static_cast(UINT32_MAX) + 1), false); + TestParseUint("0" + std::to_string(static_cast(UINT32_MAX) + 1), false); + + std::string x = std::to_string(UINT32_MAX) + "123"; + std::string_view substr = std::string_view(x).substr(0, std::to_string(UINT32_MAX).size()); + TestParseUint(substr, true, UINT32_MAX); +} diff --git a/adb/client/main.cpp b/adb/client/main.cpp index fb581a6a6..2ee81a994 100644 --- a/adb/client/main.cpp +++ b/adb/client/main.cpp @@ -40,7 +40,7 @@ static void setup_daemon_logging() { const std::string log_file_path(GetLogFilePath()); - int fd = unix_open(log_file_path.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0640); + int fd = unix_open(log_file_path, O_WRONLY | O_CREAT | O_APPEND, 0640); if (fd == -1) { PLOG(FATAL) << "cannot open " << log_file_path; } diff --git a/adb/client/usb_linux.cpp b/adb/client/usb_linux.cpp index f1bf559a9..116895894 100644 --- a/adb/client/usb_linux.cpp +++ b/adb/client/usb_linux.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -90,7 +91,7 @@ struct usb_handle : public ::usb_handle { static auto& g_usb_handles_mutex = *new std::mutex(); static auto& g_usb_handles = *new std::list(); -static int is_known_device(const char* dev_name) { +static int is_known_device(std::string_view dev_name) { std::lock_guard lock(g_usb_handles_mutex); for (usb_handle* usb : g_usb_handles) { if (usb->path == dev_name) { @@ -152,11 +153,11 @@ static void find_usb_device(const std::string& base, if (contains_non_digit(de->d_name)) continue; std::string dev_name = bus_name + "/" + de->d_name; - if (is_known_device(dev_name.c_str())) { + if (is_known_device(dev_name)) { continue; } - int fd = unix_open(dev_name.c_str(), O_RDONLY | O_CLOEXEC); + int fd = unix_open(dev_name, O_RDONLY | O_CLOEXEC); if (fd == -1) { continue; } @@ -535,10 +536,10 @@ static void register_device(const char* dev_name, const char* dev_path, unsigned // Initialize mark so we don't get garbage collected after the device scan. usb->mark = true; - usb->fd = unix_open(usb->path.c_str(), O_RDWR | O_CLOEXEC); + usb->fd = unix_open(usb->path, O_RDWR | O_CLOEXEC); if (usb->fd == -1) { // Opening RW failed, so see if we have RO access. - usb->fd = unix_open(usb->path.c_str(), O_RDONLY | O_CLOEXEC); + usb->fd = unix_open(usb->path, O_RDONLY | O_CLOEXEC); if (usb->fd == -1) { D("[ usb open %s failed: %s]", usb->path.c_str(), strerror(errno)); return; diff --git a/adb/daemon/remount_service.cpp b/adb/daemon/remount_service.cpp index 1a923173d..80b3e06e6 100644 --- a/adb/daemon/remount_service.cpp +++ b/adb/daemon/remount_service.cpp @@ -103,7 +103,7 @@ bool dev_is_overlayfs(const std::string& dev) { bool make_block_device_writable(const std::string& dev) { if (dev_is_overlayfs(dev)) return true; - int fd = unix_open(dev.c_str(), O_RDONLY | O_CLOEXEC); + int fd = unix_open(dev, O_RDONLY | O_CLOEXEC); if (fd == -1) { return false; } diff --git a/adb/daemon/services.cpp b/adb/daemon/services.cpp index 3182ddd3a..84a76557c 100644 --- a/adb/daemon/services.cpp +++ b/adb/daemon/services.cpp @@ -151,14 +151,17 @@ void reconnect_service(unique_fd fd, atransport* t) { kick_transport(t); } -unique_fd reverse_service(const char* command, atransport* transport) { +unique_fd reverse_service(std::string_view command, atransport* transport) { + // TODO: Switch handle_forward_request to std::string_view. + std::string str(command); + int s[2]; if (adb_socketpair(s)) { PLOG(ERROR) << "cannot create service socket pair."; return unique_fd{}; } VLOG(SERVICES) << "service socketpair: " << s[0] << ", " << s[1]; - if (!handle_forward_request(command, transport, s[1])) { + if (!handle_forward_request(str.c_str(), transport, s[1])) { SendFail(s[1], "not a reverse forwarding command"); } adb_close(s[1]); @@ -167,15 +170,16 @@ unique_fd reverse_service(const char* command, atransport* transport) { // Shell service string can look like: // shell[,arg1,arg2,...]:[command] -unique_fd ShellService(const std::string& args, const atransport* transport) { +unique_fd ShellService(std::string_view args, const atransport* transport) { size_t delimiter_index = args.find(':'); if (delimiter_index == std::string::npos) { LOG(ERROR) << "No ':' found in shell service arguments: " << args; return unique_fd{}; } - const std::string service_args = args.substr(0, delimiter_index); - const std::string command = args.substr(delimiter_index + 1); + // TODO: android::base::Split(const std::string_view&, ...) + std::string service_args(args.substr(0, delimiter_index)); + std::string command(args.substr(delimiter_index + 1)); // Defaults: // PTY for interactive, raw for non-interactive. @@ -192,15 +196,15 @@ unique_fd ShellService(const std::string& args, const atransport* transport) { type = SubprocessType::kPty; } else if (arg == kShellServiceArgShellProtocol) { protocol = SubprocessProtocol::kShell; - } else if (android::base::StartsWith(arg, "TERM=")) { - terminal_type = arg.substr(5); + } else if (arg.starts_with("TERM=")) { + terminal_type = arg.substr(strlen("TERM=")); } else if (!arg.empty()) { // This is not an error to allow for future expansion. LOG(WARNING) << "Ignoring unknown shell service argument: " << arg; } } - return StartSubprocess(command.c_str(), terminal_type.c_str(), type, protocol); + return StartSubprocess(command, terminal_type.c_str(), type, protocol); } static void spin_service(unique_fd fd) { @@ -323,59 +327,77 @@ asocket* daemon_service_to_socket(std::string_view name) { return nullptr; } -unique_fd daemon_service_to_fd(const char* name, atransport* transport) { - if (!strncmp("dev:", name, 4)) { - return unique_fd{unix_open(name + 4, O_RDWR | O_CLOEXEC)}; - } else if (!strncmp(name, "framebuffer:", 12)) { +unique_fd daemon_service_to_fd(std::string_view name, atransport* transport) { + // Historically, we received service names as a char*, and stopped at the first NUL byte. + // The client unintentionally sent strings with embedded NULs, which post-string_view, start + // being interpreted as part of the string, unless we explicitly strip them. + // Notably, shell checks that the part after "shell:" is empty to determine whether the session + // is interactive, and {'\0'} is non-empty. + name = StripTrailingNulls(name); + + if (name.starts_with("dev:")) { + name.remove_prefix(strlen("dev:")); + return unique_fd{unix_open(name, O_RDWR | O_CLOEXEC)}; + } else if (name.starts_with("framebuffer:")) { return create_service_thread("fb", framebuffer_service); - } else if (!strncmp(name, "jdwp:", 5)) { - return create_jdwp_connection_fd(atoi(name + 5)); - } else if (!strncmp(name, "shell", 5)) { - return ShellService(name + 5, transport); - } else if (!strncmp(name, "exec:", 5)) { - return StartSubprocess(name + 5, nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone); - } else if (!strncmp(name, "sync:", 5)) { + } else if (name.starts_with("jdwp:")) { + name.remove_prefix(strlen("jdwp:")); + std::string str(name); + return create_jdwp_connection_fd(atoi(str.c_str())); + } else if (name.starts_with("shell")) { + name.remove_prefix(strlen("shell")); + return ShellService(name, transport); + } else if (name.starts_with("exec:")) { + name.remove_prefix(strlen("exec:")); + return StartSubprocess(std::string(name), nullptr, SubprocessType::kRaw, + SubprocessProtocol::kNone); + } else if (name.starts_with("sync:")) { return create_service_thread("sync", file_sync_service); - } else if (!strncmp(name, "remount:", 8)) { - std::string options(name + strlen("remount:")); + } else if (name.starts_with("remount:")) { + std::string arg(name.begin() + strlen("remount:"), name.end()); return create_service_thread("remount", - std::bind(remount_service, std::placeholders::_1, options)); - } else if (!strncmp(name, "reboot:", 7)) { - std::string arg(name + strlen("reboot:")); + std::bind(remount_service, std::placeholders::_1, arg)); + } else if (name.starts_with("reboot:")) { + std::string arg(name.begin() + strlen("reboot:"), name.end()); return create_service_thread("reboot", std::bind(reboot_service, std::placeholders::_1, arg)); - } else if (!strncmp(name, "root:", 5)) { + } else if (name.starts_with("root:")) { return create_service_thread("root", restart_root_service); - } else if (!strncmp(name, "unroot:", 7)) { + } else if (name.starts_with("unroot:")) { return create_service_thread("unroot", restart_unroot_service); - } else if (!strncmp(name, "backup:", 7)) { - return StartSubprocess( - android::base::StringPrintf("/system/bin/bu backup %s", (name + 7)).c_str(), - nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone); - } else if (!strncmp(name, "restore:", 8)) { + } else if (name.starts_with("backup:")) { + name.remove_prefix(strlen("backup:")); + std::string cmd = "/system/bin/bu backup "; + cmd += name; + return StartSubprocess(cmd, nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone); + } else if (name.starts_with("restore:")) { return StartSubprocess("/system/bin/bu restore", nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone); - } else if (!strncmp(name, "tcpip:", 6)) { + } else if (name.starts_with("tcpip:")) { + name.remove_prefix(strlen("tcpip:")); + std::string str(name); + int port; - if (sscanf(name + 6, "%d", &port) != 1) { + if (sscanf(str.c_str(), "%d", &port) != 1) { return unique_fd{}; } return create_service_thread("tcp", std::bind(restart_tcp_service, std::placeholders::_1, port)); - } else if (!strncmp(name, "usb:", 4)) { + } else if (name.starts_with("usb:")) { return create_service_thread("usb", restart_usb_service); - } else if (!strncmp(name, "reverse:", 8)) { - return reverse_service(name + 8, transport); - } else if (!strncmp(name, "disable-verity:", 15)) { + } else if (name.starts_with("reverse:")) { + name.remove_prefix(strlen("reverse:")); + return reverse_service(name, transport); + } else if (name.starts_with("disable-verity:")) { return create_service_thread("verity-on", std::bind(set_verity_enabled_state_service, std::placeholders::_1, false)); - } else if (!strncmp(name, "enable-verity:", 15)) { + } else if (name.starts_with("enable-verity:")) { return create_service_thread("verity-off", std::bind(set_verity_enabled_state_service, std::placeholders::_1, true)); - } else if (!strcmp(name, "reconnect")) { + } else if (name == "reconnect") { return create_service_thread( "reconnect", std::bind(reconnect_service, std::placeholders::_1, transport)); - } else if (!strcmp(name, "spin")) { + } else if (name == "spin") { return create_service_thread("spin", spin_service); } diff --git a/adb/daemon/shell_service.cpp b/adb/daemon/shell_service.cpp index 8805fc1bc..595d5c6a3 100644 --- a/adb/daemon/shell_service.cpp +++ b/adb/daemon/shell_service.cpp @@ -140,8 +140,8 @@ bool CreateSocketpair(unique_fd* fd1, unique_fd* fd2) { class Subprocess { public: - Subprocess(const std::string& command, const char* terminal_type, - SubprocessType type, SubprocessProtocol protocol); + Subprocess(std::string command, const char* terminal_type, SubprocessType type, + SubprocessProtocol protocol); ~Subprocess(); const std::string& command() const { return command_; } @@ -191,9 +191,9 @@ class Subprocess { DISALLOW_COPY_AND_ASSIGN(Subprocess); }; -Subprocess::Subprocess(const std::string& command, const char* terminal_type, - SubprocessType type, SubprocessProtocol protocol) - : command_(command), +Subprocess::Subprocess(std::string command, const char* terminal_type, SubprocessType type, + SubprocessProtocol protocol) + : command_(std::move(command)), terminal_type_(terminal_type ? terminal_type : ""), type_(type), protocol_(protocol) { @@ -745,14 +745,13 @@ static unique_fd ReportError(SubprocessProtocol protocol, const std::string& mes return read; } -unique_fd StartSubprocess(const char* name, const char* terminal_type, SubprocessType type, +unique_fd StartSubprocess(std::string name, const char* terminal_type, SubprocessType type, SubprocessProtocol protocol) { D("starting %s subprocess (protocol=%s, TERM=%s): '%s'", type == SubprocessType::kRaw ? "raw" : "PTY", - protocol == SubprocessProtocol::kNone ? "none" : "shell", - terminal_type, name); + protocol == SubprocessProtocol::kNone ? "none" : "shell", terminal_type, name.c_str()); - auto subprocess = std::make_unique(name, terminal_type, type, protocol); + auto subprocess = std::make_unique(std::move(name), terminal_type, type, protocol); if (!subprocess) { LOG(ERROR) << "failed to allocate new subprocess"; return ReportError(protocol, "failed to allocate new subprocess"); diff --git a/adb/daemon/shell_service.h b/adb/daemon/shell_service.h index 2a48923dd..421d61f87 100644 --- a/adb/daemon/shell_service.h +++ b/adb/daemon/shell_service.h @@ -16,6 +16,8 @@ #pragma once +#include + #include "adb_unique_fd.h" enum class SubprocessType { @@ -32,5 +34,5 @@ enum class SubprocessProtocol { // shell is started, otherwise |name| is executed non-interactively. // // Returns an open FD connected to the subprocess or -1 on failure. -unique_fd StartSubprocess(const char* name, const char* terminal_type, SubprocessType type, +unique_fd StartSubprocess(std::string name, const char* terminal_type, SubprocessType type, SubprocessProtocol protocol); diff --git a/adb/services.cpp b/adb/services.cpp index 4b033bd20..863665774 100644 --- a/adb/services.cpp +++ b/adb/services.cpp @@ -71,7 +71,7 @@ unique_fd create_service_thread(const char* service_name, std::function +#include #include #include @@ -29,7 +30,8 @@ #include "adb.h" #include "sysdeps.h" -using android::base::StartsWith; +using namespace std::string_literals; + using android::base::StringPrintf; #if defined(__linux__) @@ -64,10 +66,11 @@ static auto& kLocalSocketTypes = *new std::unordered_map // Returns true if the argument starts with a plausible socket prefix. -bool is_socket_spec(const std::string& spec); -bool is_local_socket_spec(const std::string& spec); +bool is_socket_spec(std::string_view spec); +bool is_local_socket_spec(std::string_view spec); -int socket_spec_connect(const std::string& spec, std::string* error); -int socket_spec_listen(const std::string& spec, std::string* error, - int* resolved_tcp_port = nullptr); +int socket_spec_connect(std::string_view spec, std::string* error); +int socket_spec_listen(std::string_view spec, std::string* error, int* resolved_tcp_port = nullptr); // Exposed for testing. -bool parse_tcp_socket_spec(const std::string& spec, std::string* hostname, int* port, +bool parse_tcp_socket_spec(std::string_view spec, std::string* hostname, int* port, std::string* error); diff --git a/adb/sockets.cpp b/adb/sockets.cpp index 1bd57c1b2..cb8cd16ff 100644 --- a/adb/sockets.cpp +++ b/adb/sockets.cpp @@ -346,7 +346,7 @@ asocket* create_local_socket(int fd) { return s; } -asocket* create_local_service_socket(const char* name, atransport* transport) { +asocket* create_local_service_socket(std::string_view name, atransport* transport) { #if !ADB_HOST if (asocket* s = daemon_service_to_socket(name); s) { return s; @@ -358,13 +358,12 @@ asocket* create_local_service_socket(const char* name, atransport* transport) { } asocket* s = create_local_socket(fd); - D("LS(%d): bound to '%s' via %d", s->id, name, fd); + LOG(VERBOSE) << "LS(" << s->id << "): bound to '" << name << "' via " << fd; #if !ADB_HOST - if ((!strncmp(name, "root:", 5) && getuid() != 0 && __android_log_is_debuggable()) || - (!strncmp(name, "unroot:", 7) && getuid() == 0) || - !strncmp(name, "usb:", 4) || - !strncmp(name, "tcpip:", 6)) { + if ((name.starts_with("root:") && getuid() != 0 && __android_log_is_debuggable()) || + (name.starts_with("unroot:") && getuid() == 0) || name.starts_with("usb:") || + name.starts_with("tcpip:")) { D("LS(%d): enabling exit_on_close", s->id); s->exit_on_close = 1; } diff --git a/adb/sysdeps.h b/adb/sysdeps.h index b8d7e06b0..15247e722 100644 --- a/adb/sysdeps.h +++ b/adb/sysdeps.h @@ -27,6 +27,7 @@ #include #include +#include #include // Include this before open/close/unlink are defined as macros below. @@ -139,7 +140,7 @@ static __inline__ int adb_open_mode(const char* path, int options, int mode) } // See the comments for the !defined(_WIN32) version of unix_open(). -extern int unix_open(const char* path, int options, ...); +extern int unix_open(std::string_view path, int options, ...); #define open ___xxx_unix_open // Checks if |fd| corresponds to a console. @@ -357,20 +358,17 @@ static __inline__ void close_on_exec(int fd) // by unix_read(), unix_write(), unix_close()). Also, the C Runtime has // configurable CR/LF translation which defaults to text mode, but is settable // with _setmode(). -static __inline__ int unix_open(const char* path, int options,...) -{ - if ((options & O_CREAT) == 0) - { - return TEMP_FAILURE_RETRY( open(path, options) ); - } - else - { - int mode; - va_list args; - va_start( args, options ); - mode = va_arg( args, int ); - va_end( args ); - return TEMP_FAILURE_RETRY( open( path, options, mode ) ); +static __inline__ int unix_open(std::string_view path, int options, ...) { + std::string zero_terminated(path.begin(), path.end()); + if ((options & O_CREAT) == 0) { + return TEMP_FAILURE_RETRY(open(zero_terminated.c_str(), options)); + } else { + int mode; + va_list args; + va_start(args, options); + mode = va_arg(args, int); + va_end(args); + return TEMP_FAILURE_RETRY(open(zero_terminated.c_str(), options, mode)); } } diff --git a/adb/sysdeps_win32.cpp b/adb/sysdeps_win32.cpp index 8a6541d60..dbc892064 100644 --- a/adb/sysdeps_win32.cpp +++ b/adb/sysdeps_win32.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -2203,15 +2204,15 @@ NarrowArgs::~NarrowArgs() { } } -int unix_open(const char* path, int options, ...) { +int unix_open(std::string_view path, int options, ...) { std::wstring path_wide; - if (!android::base::UTF8ToWide(path, &path_wide)) { + if (!android::base::UTF8ToWide(path.data(), path.size(), &path_wide)) { return -1; } if ((options & O_CREAT) == 0) { return _wopen(path_wide.c_str(), options); } else { - int mode; + int mode; va_list args; va_start(args, options); mode = va_arg(args, int);