Compare commits

..

7 Commits

Author SHA1 Message Date
Romain Vimont
d9dbee9993 Adapt help to terminal size
If the error stream is a terminal, and we can retrieve the terminal
size, wrap the lines at terminal boundaries.
2021-11-11 11:34:39 +01:00
Romain Vimont
740445f425 Add util function to get terminal size 2021-11-11 11:34:35 +01:00
Romain Vimont
f849e85e9f Generate getopt params from option structures
Use the option descriptions to generate the optstring and longopts
parameters for the getopt_long() command.

That way, the options are completely described in a single place.
2021-11-11 11:34:35 +01:00
Romain Vimont
628b131741 Structure shortcuts help 2021-11-11 11:34:35 +01:00
Romain Vimont
fb3d6c319b Structure command line options help
It will allow to correctly print the help for the current terminal size
(even if for now it is hardcoded to 80 columns).
2021-11-07 23:08:09 +01:00
Romain Vimont
04b8a6dcda Add line wrapper
Add a tool to wrap lines at words boundaries (spaces) to fit in a
specified number of columns, left-indented by a specified number of
spaces.
2021-11-07 23:08:08 +01:00
Romain Vimont
0c462c1a3f Add string buffer util
This will help to build strings incrementally.
2021-11-07 21:35:57 +01:00
22 changed files with 426 additions and 549 deletions

View File

@@ -24,7 +24,6 @@ src = [
'src/server.c', 'src/server.c',
'src/stream.c', 'src/stream.c',
'src/video_buffer.c', 'src/video_buffer.c',
'src/util/file.c',
'src/util/log.c', 'src/util/log.c',
'src/util/net.c', 'src/util/net.c',
'src/util/process.c', 'src/util/process.c',
@@ -36,15 +35,9 @@ src = [
] ]
if host_machine.system() == 'windows' if host_machine.system() == 'windows'
src += [ src += [ 'src/sys/win/process.c' ]
'src/sys/win/file.c',
'src/sys/win/process.c',
]
else else
src += [ src += [ 'src/sys/unix/process.c' ]
'src/sys/unix/file.c',
'src/sys/unix/process.c',
]
endif endif
v4l2_support = host_machine.system() == 'linux' v4l2_support = host_machine.system() == 'linux'

View File

@@ -5,7 +5,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -69,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"}, {"pacman", "pacman -S android-tools"},
}; };
for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) { for (size_t i = 0; i < ARRAY_LEN(pkg_managers); ++i) {
if (sc_file_executable_exists(pkg_managers[i].binary)) { if (search_executable(pkg_managers[i].binary)) {
LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command); LOGI("You may install 'adb' by \"%s\"", pkg_managers[i].command);
return; return;
} }
@@ -81,7 +80,7 @@ show_adb_installation_msg() {
} }
static void static void
show_adb_err_msg(enum sc_process_result err, const char *const argv[]) { show_adb_err_msg(enum process_result err, const char *const argv[]) {
#define MAX_COMMAND_STRING_LEN 1024 #define MAX_COMMAND_STRING_LEN 1024
char *buf = malloc(MAX_COMMAND_STRING_LEN); char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) { if (!buf) {
@@ -90,18 +89,18 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
} }
switch (err) { switch (err) {
case SC_PROCESS_ERROR_GENERIC: case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf); LOGE("Failed to execute: %s", buf);
break; break;
case SC_PROCESS_ERROR_MISSING_BINARY: case PROCESS_ERROR_MISSING_BINARY:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN); argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Command not found: %s", buf); LOGE("Command not found: %s", buf);
LOGE("(make 'adb' accessible from your PATH or define its full" LOGE("(make 'adb' accessible from your PATH or define its full"
"path in the ADB environment variable)"); "path in the ADB environment variable)");
show_adb_installation_msg(); show_adb_installation_msg();
break; break;
case SC_PROCESS_SUCCESS: case PROCESS_SUCCESS:
// do nothing // do nothing
break; break;
} }
@@ -109,15 +108,16 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf); free(buf);
} }
sc_pid process_t
adb_execute_p(const char *serial, const char *const adb_cmd[], adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr) { size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr) {
int i; int i;
sc_pid pid; process_t process;
const char **argv = malloc((len + 4) * sizeof(*argv)); const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) { if (!argv) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
argv[0] = get_adb_command(); argv[0] = get_adb_command();
@@ -131,23 +131,24 @@ adb_execute_p(const char *serial, const char *const adb_cmd[],
memcpy(&argv[i], adb_cmd, len * sizeof(const char *)); memcpy(&argv[i], adb_cmd, len * sizeof(const char *));
argv[len + i] = NULL; argv[len + i] = NULL;
enum sc_process_result r = enum process_result r =
sc_process_execute_p(argv, &pid, pin, pout, perr); process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
if (r != SC_PROCESS_SUCCESS) { pipe_stderr);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, argv); show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE; process = PROCESS_NONE;
} }
free(argv); free(argv);
return pid; return process;
} }
sc_pid process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len) { adb_execute(const char *serial, const char *const adb_cmd[], size_t len) {
return adb_execute_p(serial, adb_cmd, len, NULL, NULL, NULL); return adb_execute_redirect(serial, adb_cmd, len, NULL, NULL, NULL);
} }
sc_pid process_t
adb_forward(const char *serial, uint16_t local_port, adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name) { const char *device_socket_name) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
@@ -158,7 +159,7 @@ adb_forward(const char *serial, uint16_t local_port,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
sc_pid process_t
adb_forward_remove(const char *serial, uint16_t local_port) { adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
@@ -166,7 +167,7 @@ adb_forward_remove(const char *serial, uint16_t local_port) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
sc_pid process_t
adb_reverse(const char *serial, const char *device_socket_name, adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port) { uint16_t local_port) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
@@ -177,7 +178,7 @@ adb_reverse(const char *serial, const char *device_socket_name,
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
sc_pid process_t
adb_reverse_remove(const char *serial, const char *device_socket_name) { adb_reverse_remove(const char *serial, const char *device_socket_name) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
@@ -185,65 +186,66 @@ adb_reverse_remove(const char *serial, const char *device_socket_name) {
return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); return adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
} }
sc_pid process_t
adb_push(const char *serial, const char *local, const char *remote) { adb_push(const char *serial, const char *local, const char *remote) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the paths must be quoted // Windows will parse the string, so the paths must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = strquote(local); local = strquote(local);
if (!local) { if (!local) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
remote = strquote(remote); remote = strquote(remote);
if (!remote) { if (!remote) {
free((void *) local); free((void *) local);
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"push", local, remote}; const char *const adb_cmd[] = {"push", local, remote};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) remote); free((void *) remote);
free((void *) local); free((void *) local);
#endif #endif
return pid; return proc;
} }
sc_pid process_t
adb_install(const char *serial, const char *local) { adb_install(const char *serial, const char *local) {
#ifdef __WINDOWS__ #ifdef __WINDOWS__
// Windows will parse the string, so the local name must be quoted // Windows will parse the string, so the local name must be quoted
// (see sys/win/command.c) // (see sys/win/command.c)
local = strquote(local); local = strquote(local);
if (!local) { if (!local) {
return SC_PROCESS_NONE; return PROCESS_NONE;
} }
#endif #endif
const char *const adb_cmd[] = {"install", "-r", local}; const char *const adb_cmd[] = {"install", "-r", local};
sc_pid pid = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd)); process_t proc = adb_execute(serial, adb_cmd, ARRAY_LEN(adb_cmd));
#ifdef __WINDOWS__ #ifdef __WINDOWS__
free((void *) local); free((void *) local);
#endif #endif
return pid; return proc;
} }
static ssize_t static ssize_t
adb_execute_for_output(const char *serial, const char *const adb_cmd[], adb_execute_for_output(const char *serial, const char *const adb_cmd[],
size_t adb_cmd_len, char *buf, size_t buf_len, size_t adb_cmd_len, char *buf, size_t buf_len,
const char *name) { const char *name) {
sc_pipe pout; pipe_t pipe_stdout;
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL); process_t proc = adb_execute_redirect(serial, adb_cmd, adb_cmd_len, NULL,
&pipe_stdout, NULL);
ssize_t r = sc_pipe_read_all(pout, buf, buf_len); ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
sc_pipe_close(pout); close_pipe(pipe_stdout);
if (!sc_process_check_success(pid, name, true)) { if (!process_check_success(proc, name, true)) {
return -1; return -1;
} }

View File

@@ -8,31 +8,32 @@
#include "util/process.h" #include "util/process.h"
sc_pid process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len); adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
sc_pid process_t
adb_execute_p(const char *serial, const char *const adb_cmd[], adb_execute_redirect(const char *serial, const char *const adb_cmd[],
size_t len, sc_pipe *pin, sc_pipe *pout, sc_pipe *perr); size_t len, pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
sc_pid process_t
adb_forward(const char *serial, uint16_t local_port, adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name); const char *device_socket_name);
sc_pid process_t
adb_forward_remove(const char *serial, uint16_t local_port); adb_forward_remove(const char *serial, uint16_t local_port);
sc_pid process_t
adb_reverse(const char *serial, const char *device_socket_name, adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port); uint16_t local_port);
sc_pid process_t
adb_reverse_remove(const char *serial, const char *device_socket_name); adb_reverse_remove(const char *serial, const char *device_socket_name);
sc_pid process_t
adb_push(const char *serial, const char *local, const char *remote); adb_push(const char *serial, const char *local, const char *remote);
sc_pid process_t
adb_install(const char *serial, const char *local); adb_install(const char *serial, const char *local);
// Return the result of "adb get-serialno". // Return the result of "adb get-serialno".

View File

@@ -635,6 +635,7 @@ print_option_usage_header(const struct sc_option *opt) {
ok = sc_strbuf_append_char(&buf, '-'); ok = sc_strbuf_append_char(&buf, '-');
assert(ok); assert(ok);
ok = sc_strbuf_append_char(&buf, opt->shortopt); ok = sc_strbuf_append_char(&buf, opt->shortopt);
assert(ok); assert(ok);
@@ -681,7 +682,7 @@ error:
static void static void
print_option_usage(const struct sc_option *opt, unsigned cols) { print_option_usage(const struct sc_option *opt, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns assert(cols > 8); // wrap_lines() requires indent < columns
if (!opt->text) { if (!opt->text) {
// Option not documented in help (for example because it is deprecated) // Option not documented in help (for example because it is deprecated)
@@ -690,7 +691,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
print_option_usage_header(opt); print_option_usage_header(opt);
char *text = sc_str_wrap_lines(opt->text, cols, 8); char *text = wrap_lines(opt->text, cols, 8);
if (!text) { if (!text) {
fprintf(stderr, "<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
return; return;
@@ -702,7 +703,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
static void static void
print_shortcuts_intro(unsigned cols) { print_shortcuts_intro(unsigned cols) {
char *intro = sc_str_wrap_lines( char *intro = wrap_lines(
"In the following list, MOD is the shortcut modifier. By default, it's " "In the following list, MOD is the shortcut modifier. By default, it's "
"(left) Alt or (left) Super, but it can be configured by " "(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4); "--shortcut-mod (see above).", cols, 4);
@@ -717,7 +718,7 @@ print_shortcuts_intro(unsigned cols) {
static void static void
print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) { print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns assert(cols > 8); // wrap_lines() requires indent < columns
assert(shortcut->shortcuts[0]); // At least one shortcut assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text); assert(shortcut->text);
@@ -729,7 +730,7 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
++i; ++i;
}; };
char *text = sc_str_wrap_lines(shortcut->text, cols, 8); char *text = wrap_lines(shortcut->text, cols, 8);
if (!text) { if (!text) {
fprintf(stderr, "<ERROR>\n"); fprintf(stderr, "<ERROR>\n");
return; return;
@@ -745,17 +746,12 @@ scrcpy_print_usage(const char *arg0) {
unsigned cols; unsigned cols;
if (!isatty(STDERR_FILENO)) { if (!isatty(STDERR_FILENO)) {
// Not a tty // Not a tty, use a default value
cols = SC_TERM_COLS_DEFAULT; cols = SC_TERM_COLS_DEFAULT;
} else { } else {
bool ok = sc_term_get_size(NULL, &cols); bool ok = sc_term_get_size(NULL, &cols);
if (!ok) { if (!ok) {
// Could not get the terminal size cols = SC_TERM_COLS_DEFAULT; // default value
cols = SC_TERM_COLS_DEFAULT;
}
if (cols < 20) {
// Do not accept a too small value
cols = 20;
} }
} }

View File

@@ -46,7 +46,7 @@ file_handler_init(struct file_handler *file_handler, const char *serial,
file_handler->initialized = false; file_handler->initialized = false;
file_handler->stopped = false; file_handler->stopped = false;
file_handler->current_process = SC_PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET; file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@@ -65,12 +65,12 @@ file_handler_destroy(struct file_handler *file_handler) {
} }
} }
static sc_pid static process_t
install_apk(const char *serial, const char *file) { install_apk(const char *serial, const char *file) {
return adb_install(serial, file); return adb_install(serial, file);
} }
static sc_pid static process_t
push_file(const char *serial, const char *file, const char *push_target) { push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target); return adb_push(serial, file, push_target);
} }
@@ -109,7 +109,7 @@ run_file_handler(void *data) {
for (;;) { for (;;) {
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = SC_PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) { while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex); sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
} }
@@ -123,26 +123,26 @@ run_file_handler(void *data) {
assert(non_empty); assert(non_empty);
(void) non_empty; (void) non_empty;
sc_pid pid; process_t process;
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file); LOGI("Installing %s...", req.file);
pid = install_apk(file_handler->serial, req.file); process = install_apk(file_handler->serial, req.file);
} else { } else {
LOGI("Pushing %s...", req.file); LOGI("Pushing %s...", req.file);
pid = push_file(file_handler->serial, req.file, process = push_file(file_handler->serial, req.file,
file_handler->push_target); file_handler->push_target);
} }
file_handler->current_process = pid; file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) { if (req.action == ACTION_INSTALL_APK) {
if (sc_process_check_success(pid, "adb install", false)) { if (process_check_success(process, "adb install", false)) {
LOGI("%s successfully installed", req.file); LOGI("%s successfully installed", req.file);
} else { } else {
LOGE("Failed to install %s", req.file); LOGE("Failed to install %s", req.file);
} }
} else { } else {
if (sc_process_check_success(pid, "adb push", false)) { if (process_check_success(process, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file, LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target); file_handler->push_target);
} else { } else {
@@ -152,11 +152,11 @@ run_file_handler(void *data) {
} }
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessarily already terminated) // Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with // Execute this call with mutex locked to avoid race conditions with
// file_handler_stop() // file_handler_stop()
sc_process_close(file_handler->current_process); process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE; file_handler->current_process = PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex); sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req); file_handler_request_destroy(&req);
@@ -183,8 +183,8 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex); sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true; file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond); sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != SC_PROCESS_NONE) { if (file_handler->current_process != PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) { if (!process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process"); LOGW("Could not terminate push/install process");
} }
} }

View File

@@ -29,7 +29,7 @@ struct file_handler {
sc_cond event_cond; sc_cond event_cond;
bool stopped; bool stopped;
bool initialized; bool initialized;
sc_pid current_process; process_t current_process;
struct file_handler_request_queue queue; struct file_handler_request_queue queue;
}; };

View File

@@ -8,8 +8,8 @@
#include "config.h" #include "config.h"
#include "compat.h" #include "compat.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/process.h"
#include "util/str_util.h" #include "util/str_util.h"
#define SCRCPY_PORTABLE_ICON_FILENAME "icon.png" #define SCRCPY_PORTABLE_ICON_FILENAME "icon.png"
@@ -46,7 +46,7 @@ get_icon_path(void) {
return NULL; return NULL;
} }
#else #else
char *icon_path = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME); char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) { if (!icon_path) {
LOGE("Could not get icon path"); LOGE("Could not get icon path");
return NULL; return NULL;

View File

@@ -8,7 +8,6 @@
#include <SDL2/SDL_platform.h> #include <SDL2/SDL_platform.h>
#include "adb.h" #include "adb.h"
#include "util/file.h"
#include "util/log.h" #include "util/log.h"
#include "util/net.h" #include "util/net.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -49,7 +48,7 @@ get_server_path(void) {
return NULL; return NULL;
} }
#else #else
char *server_path = sc_file_get_local_path(SERVER_FILENAME); char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) { if (!server_path) {
LOGE("Could not get local file path, " LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory"); "using " SERVER_FILENAME " from current directory");
@@ -68,38 +67,38 @@ push_server(const char *serial) {
if (!server_path) { if (!server_path) {
return false; return false;
} }
if (!sc_file_is_regular(server_path)) { if (!is_regular_file(server_path)) {
LOGE("'%s' does not exist or is not a regular file\n", server_path); LOGE("'%s' does not exist or is not a regular file\n", server_path);
free(server_path); free(server_path);
return false; return false;
} }
sc_pid pid = adb_push(serial, server_path, DEVICE_SERVER_PATH); process_t process = adb_push(serial, server_path, DEVICE_SERVER_PATH);
free(server_path); free(server_path);
return sc_process_check_success(pid, "adb push", true); return process_check_success(process, "adb push", true);
} }
static bool static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) { enable_tunnel_reverse(const char *serial, uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port); process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return sc_process_check_success(pid, "adb reverse", true); return process_check_success(process, "adb reverse", true);
} }
static bool static bool
disable_tunnel_reverse(const char *serial) { disable_tunnel_reverse(const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME); process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return sc_process_check_success(pid, "adb reverse --remove", true); return process_check_success(process, "adb reverse --remove", true);
} }
static bool static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) { enable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME); process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return sc_process_check_success(pid, "adb forward", true); return process_check_success(process, "adb forward", true);
} }
static bool static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) { disable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port); process_t process = adb_forward_remove(serial, local_port);
return sc_process_check_success(pid, "adb forward --remove", true); return process_check_success(process, "adb forward --remove", true);
} }
static bool static bool
@@ -228,7 +227,7 @@ log_level_to_server_string(enum sc_log_level level) {
} }
} }
static sc_pid static process_t
execute_server(struct server *server, const struct server_params *params) { execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
@@ -327,7 +326,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
bool bool
server_init(struct server *server) { server_init(struct server *server) {
server->serial = NULL; server->serial = NULL;
server->process = SC_PROCESS_NONE; server->process = PROCESS_NONE;
bool ok = sc_mutex_init(&server->mutex); bool ok = sc_mutex_init(&server->mutex);
if (!ok) { if (!ok) {
@@ -357,7 +356,7 @@ server_init(struct server *server) {
static int static int
run_wait_server(void *data) { run_wait_server(void *data) {
struct server *server = data; struct server *server = data;
sc_process_wait(server->process, false); // ignore exit code process_wait(server->process, false); // ignore exit code
sc_mutex_lock(&server->mutex); sc_mutex_lock(&server->mutex);
server->process_terminated = true; server->process_terminated = true;
@@ -396,7 +395,7 @@ server_start(struct server *server, const struct server_params *params) {
// server will connect to our server socket // server will connect to our server socket
server->process = execute_server(server, params); server->process = execute_server(server, params);
if (server->process == SC_PROCESS_NONE) { if (server->process == PROCESS_NONE) {
goto error; goto error;
} }
@@ -409,8 +408,8 @@ server_start(struct server *server, const struct server_params *params) {
bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server, bool ok = sc_thread_create(&server->wait_server_thread, run_wait_server,
"wait-server", server); "wait-server", server);
if (!ok) { if (!ok) {
sc_process_terminate(server->process); process_terminate(server->process);
sc_process_wait(server->process, true); // ignore exit code process_wait(server->process, true); // ignore exit code
goto error; goto error;
} }
@@ -508,7 +507,7 @@ server_stop(struct server *server) {
} }
} }
assert(server->process != SC_PROCESS_NONE); assert(server->process != PROCESS_NONE);
if (server->tunnel_enabled) { if (server->tunnel_enabled) {
// ignore failure // ignore failure
@@ -533,11 +532,11 @@ server_stop(struct server *server) {
// The process is terminated, but not reaped (closed) yet, so its PID // The process is terminated, but not reaped (closed) yet, so its PID
// is still valid. // is still valid.
LOGW("Killing the server..."); LOGW("Killing the server...");
sc_process_terminate(server->process); process_terminate(server->process);
} }
sc_thread_join(&server->wait_server_thread, NULL); sc_thread_join(&server->wait_server_thread, NULL);
sc_process_close(server->process); process_close(server->process);
} }
void void

View File

@@ -22,7 +22,7 @@ struct server_info {
struct server { struct server {
char *serial; char *serial;
sc_pid process; process_t process;
sc_thread wait_server_thread; sc_thread wait_server_thread;
sc_mutex mutex; sc_mutex mutex;

View File

@@ -1,75 +0,0 @@
#include "util/file.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool
sc_file_executable_exists(const char *file) {
char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
sc_file_is_regular(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@@ -3,16 +3,56 @@
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <limits.h>
#include <signal.h> #include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include "util/log.h" #include "util/log.h"
enum sc_process_result bool
sc_process_execute_p(const char *const argv[], sc_pid *pid, search_executable(const char *file) {
int *pin, int *pout, int *perr) { char *path = getenv("PATH");
if (!path)
return false;
path = strdup(path);
if (!path)
return false;
bool ret = false;
size_t file_len = strlen(file);
char *saveptr;
for (char *dir = strtok_r(path, ":", &saveptr); dir;
dir = strtok_r(NULL, ":", &saveptr)) {
size_t dir_len = strlen(dir);
char *fullpath = malloc(dir_len + file_len + 2);
if (!fullpath)
continue;
memcpy(fullpath, dir, dir_len);
fullpath[dir_len] = '/';
memcpy(fullpath + dir_len + 1, file, file_len + 1);
struct stat sb;
bool fullpath_executable = stat(fullpath, &sb) == 0 &&
sb.st_mode & S_IXUSR;
free(fullpath);
if (fullpath_executable) {
ret = true;
break;
}
}
free(path);
return ret;
}
enum process_result
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
int in[2]; int in[2];
int out[2]; int out[2];
int err[2]; int err[2];
@@ -20,44 +60,44 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
if (pipe(internal) == -1) { if (pipe(internal) == -1) {
perror("pipe"); perror("pipe");
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (pin) { if (pipe_stdin) {
if (pipe(in) == -1) { if (pipe(in) == -1) {
perror("pipe"); perror("pipe");
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (pout) { if (pipe_stdout) {
if (pipe(out) == -1) { if (pipe(out) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
if (perr) { if (pipe_stderr) {
if (pipe(err) == -1) { if (pipe(err) == -1) {
perror("pipe"); perror("pipe");
// clean up // clean up
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
} }
@@ -65,39 +105,39 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
if (*pid == -1) { if (*pid == -1) {
perror("fork"); perror("fork");
// clean up // clean up
if (perr) { if (pipe_stderr) {
close(err[0]); close(err[0]);
close(err[1]); close(err[1]);
} }
if (pout) { if (pipe_stdout) {
close(out[0]); close(out[0]);
close(out[1]); close(out[1]);
} }
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
close(in[1]); close(in[1]);
} }
close(internal[0]); close(internal[0]);
close(internal[1]); close(internal[1]);
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (*pid == 0) { if (*pid == 0) {
if (pin) { if (pipe_stdin) {
if (in[0] != STDIN_FILENO) { if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO); dup2(in[0], STDIN_FILENO);
close(in[0]); close(in[0]);
} }
close(in[1]); close(in[1]);
} }
if (pout) { if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) { if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO); dup2(out[1], STDOUT_FILENO);
close(out[1]); close(out[1]);
} }
close(out[0]); close(out[0]);
} }
if (perr) { if (pipe_stderr) {
if (err[1] != STDERR_FILENO) { if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO); dup2(err[1], STDERR_FILENO);
close(err[1]); close(err[1]);
@@ -105,15 +145,15 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
close(err[0]); close(err[0]);
} }
close(internal[0]); close(internal[0]);
enum sc_process_result err; enum process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) { if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv); execvp(argv[0], (char *const *) argv);
perror("exec"); perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC; : PROCESS_ERROR_GENERIC;
} else { } else {
perror("fcntl"); perror("fcntl");
err = SC_PROCESS_ERROR_GENERIC; err = PROCESS_ERROR_GENERIC;
} }
// send err to the parent // send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) { if (write(internal[1], &err, sizeof(err)) == -1) {
@@ -128,33 +168,38 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
close(internal[1]); close(internal[1]);
enum sc_process_result res = SC_PROCESS_SUCCESS; enum process_result res = PROCESS_SUCCESS;
// wait for EOF or receive err from child // wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) { if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read"); perror("read");
res = SC_PROCESS_ERROR_GENERIC; res = PROCESS_ERROR_GENERIC;
} }
close(internal[0]); close(internal[0]);
if (pin) { if (pipe_stdin) {
close(in[0]); close(in[0]);
*pin = in[1]; *pipe_stdin = in[1];
} }
if (pout) { if (pipe_stdout) {
*pout = out[0]; *pipe_stdout = out[0];
close(out[1]); close(out[1]);
} }
if (perr) { if (pipe_stderr) {
*perr = err[0]; *pipe_stderr = err[0];
close(err[1]); close(err[1]);
} }
return res; return res;
} }
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool bool
sc_process_terminate(pid_t pid) { process_terminate(pid_t pid) {
if (pid <= 0) { if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n", LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid); (int) pid);
@@ -163,8 +208,8 @@ sc_process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1; return kill(pid, SIGKILL) != -1;
} }
sc_exit_code exit_code_t
sc_process_wait(pid_t pid, bool close) { process_wait(pid_t pid, bool close) {
int code; int code;
int options = WEXITED; int options = WEXITED;
if (!close) { if (!close) {
@@ -175,7 +220,7 @@ sc_process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options); int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) { if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal // could not wait, or exited unexpectedly, probably by a signal
code = SC_EXIT_CODE_NONE; code = NO_EXIT_CODE;
} else { } else {
code = info.si_status; code = info.si_status;
} }
@@ -183,17 +228,48 @@ sc_process_wait(pid_t pid, bool close) {
} }
void void
sc_process_close(pid_t pid) { process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code process_wait(pid, true); // ignore exit code
}
char *
get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
char buf[PATH_MAX + 1]; // +1 for the null byte
ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
if (len == -1) {
perror("readlink");
return NULL;
}
buf[len] = '\0';
return strdup(buf);
#else
// in practice, we only need this feature for portable builds, only used on
// Windows, so we don't care implementing it for every platform
// (it's useful to have a working version on Linux for debugging though)
return NULL;
#endif
}
bool
is_regular_file(const char *path) {
struct stat path_stat;
if (stat(path, &path_stat)) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
} }
ssize_t ssize_t
sc_pipe_read(int pipe, char *data, size_t len) { read_pipe(int pipe, char *data, size_t len) {
return read(pipe, data, len); return read(pipe, data, len);
} }
void void
sc_pipe_close(int pipe) { close_pipe(int pipe) {
if (close(pipe)) { if (close(pipe)) {
perror("close pipe"); perror("close pipe");
} }

View File

@@ -1,43 +0,0 @@
#include "util/file.h"
#include <windows.h>
#include <sys/stat.h>
#include "util/log.h"
#include "util/str_util.h"
char *
sc_file_get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}
bool
sc_file_is_regular(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}

View File

@@ -1,6 +1,7 @@
#include "util/process.h" #include "util/process.h"
#include <assert.h> #include <assert.h>
#include <sys/stat.h>
#include "util/log.h" #include "util/log.h"
#include "util/str_util.h" #include "util/str_util.h"
@@ -15,16 +16,17 @@ build_cmd(char *cmd, size_t len, const char *const argv[]) {
// (don't handle escaping nor quotes) // (don't handle escaping nor quotes)
size_t ret = xstrjoin(cmd, argv, ' ', len); size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) { if (ret >= len) {
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1); LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return false; return false;
} }
return true; return true;
} }
enum sc_process_result enum process_result
sc_process_execute_p(const char *const argv[], HANDLE *handle, process_execute_redirect(const char *const argv[], HANDLE *handle,
HANDLE *pin, HANDLE *pout, HANDLE *perr) { HANDLE *pipe_stdin, HANDLE *pipe_stdout,
enum sc_process_result ret = SC_PROCESS_ERROR_GENERIC; HANDLE *pipe_stderr) {
enum process_result ret = PROCESS_ERROR_GENERIC;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@@ -34,32 +36,32 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
HANDLE stdin_read_handle; HANDLE stdin_read_handle;
HANDLE stdout_write_handle; HANDLE stdout_write_handle;
HANDLE stderr_write_handle; HANDLE stderr_write_handle;
if (pin) { if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) { if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
perror("pipe"); perror("pipe");
return SC_PROCESS_ERROR_GENERIC; return PROCESS_ERROR_GENERIC;
} }
if (!SetHandleInformation(*pin, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stdin, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdin failed"); LOGE("SetHandleInformation stdin failed");
goto error_close_stdin; goto error_close_stdin;
} }
} }
if (pout) { if (pipe_stdout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) { if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdin; goto error_close_stdin;
} }
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed"); LOGE("SetHandleInformation stdout failed");
goto error_close_stdout; goto error_close_stdout;
} }
} }
if (perr) { if (pipe_stderr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) { if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe"); perror("pipe");
goto error_close_stdout; goto error_close_stdout;
} }
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) { if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed"); LOGE("SetHandleInformation stderr failed");
goto error_close_stderr; goto error_close_stderr;
} }
@@ -69,15 +71,15 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
if (pin || pout || perr) { if (pipe_stdin || pipe_stdout || pipe_stderr) {
si.dwFlags = STARTF_USESTDHANDLES; si.dwFlags = STARTF_USESTDHANDLES;
if (pin) { if (pipe_stdin) {
si.hStdInput = stdin_read_handle; si.hStdInput = stdin_read_handle;
} }
if (pout) { if (pipe_stdout) {
si.hStdOutput = stdout_write_handle; si.hStdOutput = stdout_write_handle;
} }
if (perr) { if (pipe_stderr) {
si.hStdError = stderr_write_handle; si.hStdError = stderr_write_handle;
} }
} }
@@ -101,58 +103,63 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
*handle = NULL; *handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) { if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY; ret = PROCESS_ERROR_MISSING_BINARY;
} }
goto error_close_stderr; goto error_close_stderr;
} }
// These handles are used by the child process, close them for this process // These handles are used by the child process, close them for this process
if (pin) { if (pipe_stdin) {
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
if (pout) { if (pipe_stdout) {
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
if (perr) { if (pipe_stderr) {
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
free(wide); free(wide);
*handle = pi.hProcess; *handle = pi.hProcess;
return SC_PROCESS_SUCCESS; return PROCESS_SUCCESS;
error_close_stderr: error_close_stderr:
if (perr) { if (pipe_stderr) {
CloseHandle(*perr); CloseHandle(*pipe_stderr);
CloseHandle(stderr_write_handle); CloseHandle(stderr_write_handle);
} }
error_close_stdout: error_close_stdout:
if (pout) { if (pipe_stdout) {
CloseHandle(*pout); CloseHandle(*pipe_stdout);
CloseHandle(stdout_write_handle); CloseHandle(stdout_write_handle);
} }
error_close_stdin: error_close_stdin:
if (pin) { if (pipe_stdin) {
CloseHandle(*pin); CloseHandle(*pipe_stdin);
CloseHandle(stdin_read_handle); CloseHandle(stdin_read_handle);
} }
return ret; return ret;
} }
enum process_result
process_execute(const char *const argv[], HANDLE *handle) {
return process_execute_redirect(argv, handle, NULL, NULL, NULL);
}
bool bool
sc_process_terminate(HANDLE handle) { process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1); return TerminateProcess(handle, 1);
} }
sc_exit_code exit_code_t
sc_process_wait(HANDLE handle, bool close) { process_wait(HANDLE handle, bool close) {
DWORD code; DWORD code;
if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 if (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0
|| !GetExitCodeProcess(handle, &code)) { || !GetExitCodeProcess(handle, &code)) {
// could not wait or retrieve the exit code // could not wait or retrieve the exit code
code = SC_EXIT_CODE_NONE; code = NO_EXIT_CODE; // max value, it's unsigned
} }
if (close) { if (close) {
CloseHandle(handle); CloseHandle(handle);
@@ -161,14 +168,48 @@ sc_process_wait(HANDLE handle, bool close) {
} }
void void
sc_process_close(HANDLE handle) { process_close(HANDLE handle) {
bool closed = CloseHandle(handle); bool closed = CloseHandle(handle);
assert(closed); assert(closed);
(void) closed; (void) closed;
} }
char *
get_executable_path(void) {
HMODULE hModule = GetModuleHandleW(NULL);
if (!hModule) {
return NULL;
}
WCHAR buf[MAX_PATH + 1]; // +1 for the null byte
int len = GetModuleFileNameW(hModule, buf, MAX_PATH);
if (!len) {
return NULL;
}
buf[len] = '\0';
return utf8_from_wide_char(buf);
}
bool
is_regular_file(const char *path) {
wchar_t *wide_path = utf8_to_wide_char(path);
if (!wide_path) {
LOGC("Could not allocate wide char string");
return false;
}
struct _stat path_stat;
int r = _wstat(wide_path, &path_stat);
free(wide_path);
if (r) {
perror("stat");
return false;
}
return S_ISREG(path_stat.st_mode);
}
ssize_t ssize_t
sc_read_pipe(HANDLE pipe, char *data, size_t len) { read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r; DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) { if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1; return -1;
@@ -177,7 +218,7 @@ sc_read_pipe(HANDLE pipe, char *data, size_t len) {
} }
void void
sc_close_pipe(HANDLE pipe) { close_pipe(HANDLE pipe) {
if (!CloseHandle(pipe)) { if (!CloseHandle(pipe)) {
LOGW("Cannot close pipe"); LOGW("Cannot close pipe");
} }

View File

@@ -1,48 +0,0 @@
#include "file.h"
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
char *
sc_file_get_local_path(const char *name) {
char *executable_path = sc_file_get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, SC_PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, SC_PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = SC_PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}

View File

@@ -1,49 +0,0 @@
#ifndef SC_FILE_H
#define SC_FILE_H
#include "common.h"
#include <stdbool.h>
#ifdef _WIN32
# define SC_PATH_SEPARATOR '\\'
#else
# define SC_PATH_SEPARATOR '/'
#endif
#ifndef _WIN32
/**
* Indicate if an executable exists using $PATH
*
* In practice, it is only used to know if a package manager is available on
* the system. It is only implemented on Linux.
*/
bool
sc_file_executable_exists(const char *file);
#endif
/**
* Return the absolute path of the executable (the scrcpy binary)
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_executable_path(void);
/**
* Return the absolute path of a file in the same directory as the executable
*
* The result must be freed by the caller using free(). It may return NULL on
* error.
*/
char *
sc_file_get_local_path(const char *name);
/**
* Indicate if the file exists and is not a directory
*/
bool
sc_file_is_regular(const char *path);
#endif

View File

@@ -3,22 +3,16 @@
#include <libgen.h> #include <libgen.h>
#include "log.h" #include "log.h"
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid) {
return sc_process_execute_p(argv, pid, NULL, NULL, NULL);
}
bool bool
sc_process_check_success(sc_pid pid, const char *name, bool close) { process_check_success(process_t proc, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) { if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name); LOGE("Could not execute \"%s\"", name);
return false; return false;
} }
sc_exit_code exit_code = sc_process_wait(pid, close); exit_code_t exit_code = process_wait(proc, close);
if (exit_code) { if (exit_code) {
if (exit_code != SC_EXIT_CODE_NONE) { if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name, LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
exit_code);
} else { } else {
LOGE("\"%s\" exited unexpectedly", name); LOGE("\"%s\" exited unexpectedly", name);
} }
@@ -27,11 +21,52 @@ sc_process_check_success(sc_pid pid, const char *name, bool close) {
return true; return true;
} }
char *
get_local_file_path(const char *name) {
char *executable_path = get_executable_path();
if (!executable_path) {
return NULL;
}
// dirname() does not work correctly everywhere, so get the parent
// directory manually.
// See <https://github.com/Genymobile/scrcpy/issues/2619>
char *p = strrchr(executable_path, PATH_SEPARATOR);
if (!p) {
LOGE("Unexpected executable path: \"%s\" (it should contain a '%c')",
executable_path, PATH_SEPARATOR);
free(executable_path);
return NULL;
}
*p = '\0'; // modify executable_path in place
char *dir = executable_path;
size_t dirlen = strlen(dir);
size_t namelen = strlen(name);
size_t len = dirlen + namelen + 2; // +2: '/' and '\0'
char *file_path = malloc(len);
if (!file_path) {
LOGE("Could not alloc path");
free(executable_path);
return NULL;
}
memcpy(file_path, dir, dirlen);
file_path[dirlen] = PATH_SEPARATOR;
// namelen + 1 to copy the final '\0'
memcpy(&file_path[dirlen + 1], name, namelen + 1);
free(executable_path);
return file_path;
}
ssize_t ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) { read_pipe_all(pipe_t pipe, char *data, size_t len) {
size_t copied = 0; size_t copied = 0;
while (len > 0) { while (len > 0) {
ssize_t r = sc_pipe_read(pipe, data, len); ssize_t r = read_pipe(pipe, data, len);
if (r <= 0) { if (r <= 0) {
return copied ? (ssize_t) copied : r; return copied ? (ssize_t) copied : r;
} }

View File

@@ -10,100 +10,96 @@
// not needed here, but winsock2.h must never be included AFTER windows.h // not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h> # include <winsock2.h>
# include <windows.h> # include <windows.h>
# define SC_PRIexitcode "lu" # define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178> // <https://stackoverflow.com/a/44383330/1987178>
# define SC_PRIsizet "Iu" # define PRIsizet "Iu"
# define SC_PROCESS_NONE NULL # define PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned # define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE sc_pid; typedef HANDLE process_t;
typedef DWORD sc_exit_code; typedef DWORD exit_code_t;
typedef HANDLE sc_pipe; typedef HANDLE pipe_t;
#else #else
# include <sys/types.h> # include <sys/types.h>
# define SC_PRIsizet "zu" # define PATH_SEPARATOR '/'
# define SC_PRIexitcode "d" # define PRIsizet "zu"
# define SC_PROCESS_NONE -1 # define PRIexitcode "d"
# define SC_EXIT_CODE_NONE -1 # define PROCESS_NONE -1
typedef pid_t sc_pid; # define NO_EXIT_CODE -1
typedef int sc_exit_code; typedef pid_t process_t;
typedef int sc_pipe; typedef int exit_code_t;
typedef int pipe_t;
#endif #endif
enum sc_process_result { enum process_result {
SC_PROCESS_SUCCESS, PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC, PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY, PROCESS_ERROR_MISSING_BINARY,
}; };
/** // execute the command and write the result to the output parameter "process"
* Execute the command and write the process id to `pid` enum process_result
*/ process_execute(const char *const argv[], process_t *process);
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
/** enum process_result
* Execute the command and write the process id to `pid` process_execute_redirect(const char *const argv[], process_t *process,
* pipe_t *pipe_stdin, pipe_t *pipe_stdout,
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and stderr pipe_t *pipe_stderr);
* (`perr`).
*/
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
sc_pipe *pin, sc_pipe *pout, sc_pipe *perr);
/**
* Kill the process
*/
bool bool
sc_process_terminate(sc_pid pid); process_terminate(process_t pid);
/** // kill the process
* Wait and close the process (similar to waitpid())
*
* The `close` flag indicates if the process must be _closed_ (reaped) (passing
* false is equivalent to enable WNOWAIT in waitid()).
*/
sc_exit_code
sc_process_wait(sc_pid pid, bool close);
/**
* Close (reap) the process
*
* Semantically:
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
*/
void
sc_process_close(sc_pid pid);
/**
* Convenience function to wait for a successful process execution
*
* Automatically log process errors with the provided process name.
*/
bool bool
sc_process_check_success(sc_pid pid, const char *name, bool close); process_terminate(process_t pid);
/** // wait and close the process (like waitpid())
* Read from the pipe // the "close" flag indicates if the process must be "closed" (reaped)
* // (passing false is equivalent to enable WNOWAIT in waitid())
* Same semantic as read(). exit_code_t
*/ process_wait(process_t pid, bool close);
ssize_t
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
/** // close the process
* Read exactly `len` chars from a pipe (unless EOF) //
*/ // Semantically, process_wait(close) = process_wait(noclose) + process_close
ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
void void
sc_pipe_close(sc_pipe pipe); process_close(process_t pid);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
bool
process_check_success(process_t proc, const char *name, bool close);
#ifndef _WIN32
// only used to find package manager, not implemented for Windows
bool
search_executable(const char *file);
#endif
// return the absolute path of the executable (the scrcpy binary)
// may be NULL on error; to be freed by free()
char *
get_executable_path(void);
// Return the absolute path of a file in the same directory as he executable.
// May be NULL on error. To be freed by free().
char *
get_local_file_path(const char *name);
// returns true if the file exists and is not a directory
bool
is_regular_file(const char *path);
ssize_t
read_pipe(pipe_t pipe, char *data, size_t len);
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len);
void
close_pipe(pipe_t pipe);
#endif #endif

View File

@@ -212,14 +212,14 @@ utf8_from_wide_char(const wchar_t *ws) {
#endif #endif
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent) { char *wrap_lines(const char *input, unsigned columns, unsigned indent) {
assert(indent < columns); assert(indent < columns);
struct sc_strbuf buf; struct sc_strbuf buf;
// The output string should not be much longer than the input string (just // The output string should not be a lot longer than the input string (just
// a few '\n' added), so this initial capacity should hopefully almost // a few '\n' added), so this initial capacity should almost always avoid
// always avoid internal realloc() in string buffer // internal realloc() in string buffer
size_t cap = strlen(input) * 3 / 2; size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) { if (!sc_strbuf_init(&buf, cap)) {

View File

@@ -62,12 +62,8 @@ char *
utf8_from_wide_char(const wchar_t *s); utf8_from_wide_char(const wchar_t *s);
#endif #endif
/** // Wrap input lines at words boundaries (spaces) so that they fit in 'columns'
* Wrap input lines to fit in `columns` columns // columns, left-indented by 'indent' spaces
* char *wrap_lines(const char *input, unsigned columns, unsigned indent);
* Break input lines at word boundaries (spaces) so that they fit in `columns`
* columns, left-indented by `indent` spaces.
*/
char *sc_str_wrap_lines(const char *input, unsigned columns, unsigned indent);
#endif #endif

View File

@@ -1,8 +1,6 @@
#ifndef SC_STRBUF_H #ifndef SC_STRBUF_H
#define SC_STRBUF_H #define SC_STRBUF_H
#include "common.h"
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include <string.h> #include <string.h>
@@ -13,60 +11,29 @@ struct sc_strbuf {
size_t cap; size_t cap;
}; };
/** // buf->s must be manually freed by the caller
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
bool bool
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap); sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
/**
* Append a string
*
* Append `len` characters from `s` to the buffer.
*/
bool bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len); sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
/**
* Append a char
*
* Append a single character to the buffer.
*/
bool bool
sc_strbuf_append_char(struct sc_strbuf *buf, const char c); sc_strbuf_append_char(struct sc_strbuf *buf, const char c);
/**
* Append a char `n` times
*
* Append the same characters `n` times to the buffer.
*/
bool bool
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n); sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
/**
* Append a NUL-terminated string
*/
static inline bool static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) { sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s)); return sc_strbuf_append(buf, s, strlen(s));
} }
/** // Append static string (i.e. the string size is known at compile time, for
* Append a static string // example a string literal)
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
#define sc_strbuf_append_staticstr(BUF, S) \ #define sc_strbuf_append_staticstr(BUF, S) \
sc_strbuf_append(BUF, S, sizeof(S) - 1) sc_strbuf_append(BUF, S, sizeof(S) - 1)
/**
* Shrink the buffer capacity to its current length
*
* This resizes `buf->s` to fit the content.
*/
void void
sc_strbuf_shrink(struct sc_strbuf *buf); sc_strbuf_shrink(struct sc_strbuf *buf);

View File

@@ -5,16 +5,6 @@
#include <stdbool.h> #include <stdbool.h>
/**
* Return the terminal dimensions
*
* Return false if the dimensions could not be retrieved.
*
* Otherwise, return true, and:
* - if `rows` is not NULL, then the number of rows is written to `*rows`.
* - if `columns` is not NULL, then the number of columns is written to
* `*columns`.
*/
bool bool
sc_term_get_size(unsigned *rows, unsigned *cols); sc_term_get_size(unsigned *rows, unsigned *cols);

View File

@@ -329,7 +329,7 @@ static void test_wrap_lines(void) {
" | |\n" " | |\n"
" +----+\n"; " +----+\n";
char *formatted = sc_str_wrap_lines(s, 24, 4); char *formatted = wrap_lines(s, 24, 4);
assert(formatted); assert(formatted);
assert(!strcmp(formatted, expected)); assert(!strcmp(formatted, expected));