Compare commits

...

5 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
16 changed files with 473 additions and 401 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

@ -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