Compare commits

..

12 Commits

Author SHA1 Message Date
Romain Vimont
c7e46583b8 Improve process API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:47 +01:00
Romain Vimont
848db078a5 Factorize common impl of process_execute()
Both implementations are the same. Move them to the common process.c.
2021-11-11 18:08:41 +01:00
Romain Vimont
b197372dd3 Remove duplicate function declaration
The function process_terminate() was declared twice.
2021-11-11 18:08:41 +01:00
Romain Vimont
66f21cf680 Improve file API
Prefix symbols and constants names and improve documentation.
2021-11-11 18:08:35 +01:00
Romain Vimont
f197c1181e Move functions from process to file
Move filesystem-related functions from process.[ch] to file.[ch].
2021-11-11 16:27:12 +01:00
Romain Vimont
7a733328bc Adapt help to terminal size
If the error stream is a terminal, and we can retrieve the terminal
size, wrap the help content according to the terminal width.
2021-11-11 15:22:39 +01:00
Romain Vimont
38332f683c Add util function to get terminal size 2021-11-11 15:22:39 +01:00
Romain Vimont
3f51a2ab43 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 14:59:34 +01:00
Romain Vimont
74ab0a4df8 Structure shortcuts help 2021-11-11 14:59:34 +01:00
Romain Vimont
f59e9e3cb0 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-11 14:59:34 +01:00
Romain Vimont
9ec3406568 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-11 14:55:53 +01:00
Romain Vimont
6dba1922c1 Add string buffer util
This will help to build strings incrementally.
2021-11-11 14:55:52 +01:00
22 changed files with 543 additions and 420 deletions

View File

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

View File

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

View File

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

View File

@@ -635,7 +635,6 @@ print_option_usage_header(const struct sc_option *opt) {
ok = sc_strbuf_append_char(&buf, '-');
assert(ok);
ok = sc_strbuf_append_char(&buf, opt->shortopt);
assert(ok);
@@ -682,7 +681,7 @@ error:
static void
print_option_usage(const struct sc_option *opt, unsigned cols) {
assert(cols > 8); // wrap_lines() requires indent < columns
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
if (!opt->text) {
// Option not documented in help (for example because it is deprecated)
@@ -691,7 +690,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
print_option_usage_header(opt);
char *text = wrap_lines(opt->text, cols, 8);
char *text = sc_str_wrap_lines(opt->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@@ -703,7 +702,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
static void
print_shortcuts_intro(unsigned cols) {
char *intro = wrap_lines(
char *intro = sc_str_wrap_lines(
"In the following list, MOD is the shortcut modifier. By default, it's "
"(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4);
@@ -718,7 +717,7 @@ print_shortcuts_intro(unsigned cols) {
static void
print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
assert(cols > 8); // wrap_lines() requires indent < columns
assert(cols > 8); // sc_str_wrap_lines() requires indent < columns
assert(shortcut->shortcuts[0]); // At least one shortcut
assert(shortcut->text);
@@ -730,7 +729,7 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
++i;
};
char *text = wrap_lines(shortcut->text, cols, 8);
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@@ -746,12 +745,17 @@ scrcpy_print_usage(const char *arg0) {
unsigned cols;
if (!isatty(STDERR_FILENO)) {
// Not a tty, use a default value
// Not a tty
cols = SC_TERM_COLS_DEFAULT;
} else {
bool ok = sc_term_get_size(NULL, &cols);
if (!ok) {
cols = SC_TERM_COLS_DEFAULT; // default value
// Could not get the terminal size
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->stopped = false;
file_handler->current_process = PROCESS_NONE;
file_handler->current_process = SC_PROCESS_NONE;
file_handler->push_target = push_target ? push_target : DEFAULT_PUSH_TARGET;
@@ -65,12 +65,12 @@ file_handler_destroy(struct file_handler *file_handler) {
}
}
static process_t
static sc_pid
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static process_t
static sc_pid
push_file(const char *serial, const char *file, const char *push_target) {
return adb_push(serial, file, push_target);
}
@@ -109,7 +109,7 @@ run_file_handler(void *data) {
for (;;) {
sc_mutex_lock(&file_handler->mutex);
file_handler->current_process = PROCESS_NONE;
file_handler->current_process = SC_PROCESS_NONE;
while (!file_handler->stopped && cbuf_is_empty(&file_handler->queue)) {
sc_cond_wait(&file_handler->event_cond, &file_handler->mutex);
}
@@ -123,26 +123,26 @@ run_file_handler(void *data) {
assert(non_empty);
(void) non_empty;
process_t process;
sc_pid pid;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
process = install_apk(file_handler->serial, req.file);
pid = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = process;
file_handler->current_process = pid;
sc_mutex_unlock(&file_handler->mutex);
if (req.action == ACTION_INSTALL_APK) {
if (process_check_success(process, "adb install", false)) {
if (sc_process_check_success(pid, "adb install", false)) {
LOGI("%s successfully installed", req.file);
} else {
LOGE("Failed to install %s", req.file);
}
} else {
if (process_check_success(process, "adb push", false)) {
if (sc_process_check_success(pid, "adb push", false)) {
LOGI("%s successfully pushed to %s", req.file,
file_handler->push_target);
} else {
@@ -152,11 +152,11 @@ run_file_handler(void *data) {
}
sc_mutex_lock(&file_handler->mutex);
// Close the process (it is necessary already terminated)
// Close the process (it is necessarily already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
process_close(file_handler->current_process);
file_handler->current_process = PROCESS_NONE;
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
sc_mutex_unlock(&file_handler->mutex);
file_handler_request_destroy(&req);
@@ -183,8 +183,8 @@ file_handler_stop(struct file_handler *file_handler) {
sc_mutex_lock(&file_handler->mutex);
file_handler->stopped = true;
sc_cond_signal(&file_handler->event_cond);
if (file_handler->current_process != PROCESS_NONE) {
if (!process_terminate(file_handler->current_process)) {
if (file_handler->current_process != SC_PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) {
LOGW("Could not terminate push/install process");
}
}

View File

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

View File

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

View File

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

View File

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

75
app/src/sys/unix/file.c Normal file
View File

@@ -0,0 +1,75 @@
#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,56 +3,16 @@
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "util/log.h"
bool
search_executable(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;
}
enum process_result
process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
int *pipe_stdout, int *pipe_stderr) {
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
int *pin, int *pout, int *perr) {
int in[2];
int out[2];
int err[2];
@@ -60,44 +20,44 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (pipe(internal) == -1) {
perror("pipe");
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
if (pipe_stdin) {
if (pin) {
if (pipe(in) == -1) {
perror("pipe");
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
if (pipe_stdout) {
if (pout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
if (pipe_stderr) {
if (perr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pipe_stdout) {
if (pout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
}
@@ -105,39 +65,39 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
if (*pid == -1) {
perror("fork");
// clean up
if (pipe_stderr) {
if (perr) {
close(err[0]);
close(err[1]);
}
if (pipe_stdout) {
if (pout) {
close(out[0]);
close(out[1]);
}
if (pipe_stdin) {
if (pin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return PROCESS_ERROR_GENERIC;
return SC_PROCESS_ERROR_GENERIC;
}
if (*pid == 0) {
if (pipe_stdin) {
if (pin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
close(in[1]);
}
if (pipe_stdout) {
if (pout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
if (pipe_stderr) {
if (perr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
@@ -145,15 +105,15 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
close(err[0]);
}
close(internal[0]);
enum process_result err;
enum sc_process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: PROCESS_ERROR_GENERIC;
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC;
} else {
perror("fcntl");
err = PROCESS_ERROR_GENERIC;
err = SC_PROCESS_ERROR_GENERIC;
}
// send err to the parent
if (write(internal[1], &err, sizeof(err)) == -1) {
@@ -168,38 +128,33 @@ process_execute_redirect(const char *const argv[], pid_t *pid, int *pipe_stdin,
close(internal[1]);
enum process_result res = PROCESS_SUCCESS;
enum sc_process_result res = SC_PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = PROCESS_ERROR_GENERIC;
res = SC_PROCESS_ERROR_GENERIC;
}
close(internal[0]);
if (pipe_stdin) {
if (pin) {
close(in[0]);
*pipe_stdin = in[1];
*pin = in[1];
}
if (pipe_stdout) {
*pipe_stdout = out[0];
if (pout) {
*pout = out[0];
close(out[1]);
}
if (pipe_stderr) {
*pipe_stderr = err[0];
if (perr) {
*perr = err[0];
close(err[1]);
}
return res;
}
enum process_result
process_execute(const char *const argv[], pid_t *pid) {
return process_execute_redirect(argv, pid, NULL, NULL, NULL);
}
bool
process_terminate(pid_t pid) {
sc_process_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
@@ -208,8 +163,8 @@ process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1;
}
exit_code_t
process_wait(pid_t pid, bool close) {
sc_exit_code
sc_process_wait(pid_t pid, bool close) {
int code;
int options = WEXITED;
if (!close) {
@@ -220,7 +175,7 @@ process_wait(pid_t pid, bool close) {
int r = waitid(P_PID, pid, &info, options);
if (r == -1 || info.si_code != CLD_EXITED) {
// could not wait, or exited unexpectedly, probably by a signal
code = NO_EXIT_CODE;
code = SC_EXIT_CODE_NONE;
} else {
code = info.si_status;
}
@@ -228,48 +183,17 @@ process_wait(pid_t pid, bool close) {
}
void
process_close(pid_t pid) {
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);
sc_process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code
}
ssize_t
read_pipe(int pipe, char *data, size_t len) {
sc_pipe_read(int pipe, char *data, size_t len) {
return read(pipe, data, len);
}
void
close_pipe(int pipe) {
sc_pipe_close(int pipe) {
if (close(pipe)) {
perror("close pipe");
}

43
app/src/sys/win/file.c Normal file
View File

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

48
app/src/util/file.c Normal file
View File

@@ -0,0 +1,48 @@
#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;
}

49
app/src/util/file.h Normal file
View File

@@ -0,0 +1,49 @@
#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,16 +3,22 @@
#include <libgen.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
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
sc_process_check_success(sc_pid pid, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
return false;
}
exit_code_t exit_code = process_wait(proc, close);
sc_exit_code exit_code = sc_process_wait(pid, close);
if (exit_code) {
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
if (exit_code != SC_EXIT_CODE_NONE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
@@ -21,52 +27,11 @@ process_check_success(process_t proc, const char *name, bool close) {
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
read_pipe_all(pipe_t pipe, char *data, size_t len) {
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len) {
size_t copied = 0;
while (len > 0) {
ssize_t r = read_pipe(pipe, data, len);
ssize_t r = sc_pipe_read(pipe, data, len);
if (r <= 0) {
return copied ? (ssize_t) copied : r;
}

View File

@@ -10,96 +10,100 @@
// not needed here, but winsock2.h must never be included AFTER windows.h
# include <winsock2.h>
# include <windows.h>
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
# define SC_PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# define PRIsizet "Iu"
# define PROCESS_NONE NULL
# define NO_EXIT_CODE -1u // max value as unsigned
typedef HANDLE process_t;
typedef DWORD exit_code_t;
typedef HANDLE pipe_t;
# define SC_PRIsizet "Iu"
# define SC_PROCESS_NONE NULL
# define SC_EXIT_CODE_NONE -1u // max value as unsigned
typedef HANDLE sc_pid;
typedef DWORD sc_exit_code;
typedef HANDLE sc_pipe;
#else
# include <sys/types.h>
# define PATH_SEPARATOR '/'
# define PRIsizet "zu"
# define PRIexitcode "d"
# define PROCESS_NONE -1
# define NO_EXIT_CODE -1
typedef pid_t process_t;
typedef int exit_code_t;
typedef int pipe_t;
# define SC_PRIsizet "zu"
# define SC_PRIexitcode "d"
# define SC_PROCESS_NONE -1
# define SC_EXIT_CODE_NONE -1
typedef pid_t sc_pid;
typedef int sc_exit_code;
typedef int sc_pipe;
#endif
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
enum sc_process_result {
SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY,
};
// execute the command and write the result to the output parameter "process"
enum process_result
process_execute(const char *const argv[], process_t *process);
/**
* Execute the command and write the process id to `pid`
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
enum process_result
process_execute_redirect(const char *const argv[], process_t *process,
pipe_t *pipe_stdin, pipe_t *pipe_stdout,
pipe_t *pipe_stderr);
/**
* Execute the command and write the process id to `pid`
*
* If not NULL, provide a pipe for stdin (`pin`), stdout (`pout`) and 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
process_terminate(process_t pid);
sc_process_terminate(sc_pid pid);
// kill the process
bool
process_terminate(process_t pid);
/**
* 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);
// wait and close the process (like waitpid())
// the "close" flag indicates if the process must be "closed" (reaped)
// (passing false is equivalent to enable WNOWAIT in waitid())
exit_code_t
process_wait(process_t pid, bool close);
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
/**
* Close (reap) the process
*
* Semantically:
* sc_process_wait(close) = sc_process_wait(noclose) + sc_process_close()
*/
void
process_close(process_t pid);
sc_process_close(sc_pid pid);
// convenience function to wait for a successful process execution
// automatically log process errors with the provided process name
/**
* 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);
sc_process_check_success(sc_pid pid, const char *name, bool close);
/**
* Read from the pipe
*
* Same semantic as read().
*/
ssize_t
read_pipe(pipe_t pipe, char *data, size_t len);
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
/**
* Read exactly `len` chars from a pipe (unless EOF)
*/
ssize_t
read_pipe_all(pipe_t pipe, char *data, size_t len);
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
void
close_pipe(pipe_t pipe);
sc_pipe_close(sc_pipe pipe);
#endif

View File

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

View File

@@ -62,8 +62,12 @@ char *
utf8_from_wide_char(const wchar_t *s);
#endif
// Wrap input lines at words boundaries (spaces) so that they fit in 'columns'
// columns, left-indented by 'indent' spaces
char *wrap_lines(const char *input, unsigned columns, unsigned indent);
/**
* Wrap input lines to fit in `columns` columns
*
* 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

View File

@@ -1,6 +1,8 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
@@ -11,29 +13,60 @@ struct sc_strbuf {
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
sc_strbuf_init(struct sc_strbuf *buf, size_t init_cap);
/**
* Append a string
*
* Append `len` characters from `s` to the buffer.
*/
bool
sc_strbuf_append(struct sc_strbuf *buf, const char *s, size_t len);
/**
* Append a char
*
* Append a single character to the buffer.
*/
bool
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
sc_strbuf_append_n(struct sc_strbuf *buf, const char c, size_t n);
/**
* Append a NUL-terminated string
*/
static inline bool
sc_strbuf_append_str(struct sc_strbuf *buf, const char *s) {
return sc_strbuf_append(buf, s, strlen(s));
}
// Append static string (i.e. the string size is known at compile time, for
// example a string literal)
/**
* Append a static string
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
#define sc_strbuf_append_staticstr(BUF, S) \
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
sc_strbuf_shrink(struct sc_strbuf *buf);

View File

@@ -5,6 +5,16 @@
#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
sc_term_get_size(unsigned *rows, unsigned *cols);

View File

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