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

View File

@@ -5,7 +5,6 @@
#include <stdlib.h>
#include <string.h>
#include "util/file.h"
#include "util/log.h"
#include "util/str_util.h"
@@ -69,7 +68,7 @@ show_adb_installation_msg() {
{"pacman", "pacman -S android-tools"},
};
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);
return;
}
@@ -81,7 +80,7 @@ show_adb_installation_msg() {
}
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
char *buf = malloc(MAX_COMMAND_STRING_LEN);
if (!buf) {
@@ -90,18 +89,18 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
}
switch (err) {
case SC_PROCESS_ERROR_GENERIC:
case PROCESS_ERROR_GENERIC:
argv_to_string(argv, buf, MAX_COMMAND_STRING_LEN);
LOGE("Failed to execute: %s", buf);
break;
case SC_PROCESS_ERROR_MISSING_BINARY:
case 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 SC_PROCESS_SUCCESS:
case PROCESS_SUCCESS:
// do nothing
break;
}
@@ -109,15 +108,16 @@ show_adb_err_msg(enum sc_process_result err, const char *const argv[]) {
free(buf);
}
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
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) {
int i;
sc_pid pid;
process_t process;
const char **argv = malloc((len + 4) * sizeof(*argv));
if (!argv) {
return SC_PROCESS_NONE;
return PROCESS_NONE;
}
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 *));
argv[len + i] = NULL;
enum sc_process_result r =
sc_process_execute_p(argv, &pid, pin, pout, perr);
if (r != SC_PROCESS_SUCCESS) {
enum process_result r =
process_execute_redirect(argv, &process, pipe_stdin, pipe_stdout,
pipe_stderr);
if (r != PROCESS_SUCCESS) {
show_adb_err_msg(r, argv);
pid = SC_PROCESS_NONE;
process = PROCESS_NONE;
}
free(argv);
return pid;
return process;
}
sc_pid
process_t
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,
const char *device_socket_name) {
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));
}
sc_pid
process_t
adb_forward_remove(const char *serial, uint16_t local_port) {
char local[4 + 5 + 1]; // tcp: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));
}
sc_pid
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_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));
}
sc_pid
process_t
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);
@@ -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));
}
sc_pid
process_t
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 SC_PROCESS_NONE;
return PROCESS_NONE;
}
remote = strquote(remote);
if (!remote) {
free((void *) local);
return SC_PROCESS_NONE;
return PROCESS_NONE;
}
#endif
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__
free((void *) remote);
free((void *) local);
#endif
return pid;
return proc;
}
sc_pid
process_t
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 SC_PROCESS_NONE;
return PROCESS_NONE;
}
#endif
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__
free((void *) local);
#endif
return pid;
return proc;
}
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) {
sc_pipe pout;
sc_pid pid = adb_execute_p(serial, adb_cmd, adb_cmd_len, NULL, &pout, NULL);
pipe_t pipe_stdout;
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);
sc_pipe_close(pout);
ssize_t r = read_pipe_all(pipe_stdout, buf, buf_len);
close_pipe(pipe_stdout);
if (!sc_process_check_success(pid, name, true)) {
if (!process_check_success(proc, name, true)) {
return -1;
}

View File

@@ -8,31 +8,32 @@
#include "util/process.h"
sc_pid
process_t
adb_execute(const char *serial, const char *const adb_cmd[], size_t len);
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
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
process_t
adb_forward(const char *serial, uint16_t local_port,
const char *device_socket_name);
sc_pid
process_t
adb_forward_remove(const char *serial, uint16_t local_port);
sc_pid
process_t
adb_reverse(const char *serial, const char *device_socket_name,
uint16_t local_port);
sc_pid
process_t
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);
sc_pid
process_t
adb_install(const char *serial, const char *local);
// 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, '-');
assert(ok);
ok = sc_strbuf_append_char(&buf, opt->shortopt);
assert(ok);
@@ -681,7 +682,7 @@ error:
static void
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) {
// 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);
char *text = sc_str_wrap_lines(opt->text, cols, 8);
char *text = wrap_lines(opt->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@@ -702,7 +703,7 @@ print_option_usage(const struct sc_option *opt, unsigned cols) {
static void
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 "
"(left) Alt or (left) Super, but it can be configured by "
"--shortcut-mod (see above).", cols, 4);
@@ -717,7 +718,7 @@ print_shortcuts_intro(unsigned cols) {
static void
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->text);
@@ -729,7 +730,7 @@ print_shortcut(const struct sc_shortcut *shortcut, unsigned cols) {
++i;
};
char *text = sc_str_wrap_lines(shortcut->text, cols, 8);
char *text = wrap_lines(shortcut->text, cols, 8);
if (!text) {
fprintf(stderr, "<ERROR>\n");
return;
@@ -745,17 +746,12 @@ scrcpy_print_usage(const char *arg0) {
unsigned cols;
if (!isatty(STDERR_FILENO)) {
// Not a tty
// Not a tty, use a default value
cols = SC_TERM_COLS_DEFAULT;
} else {
bool ok = sc_term_get_size(NULL, &cols);
if (!ok) {
// Could not get the terminal size
cols = SC_TERM_COLS_DEFAULT;
}
if (cols < 20) {
// Do not accept a too small value
cols = 20;
cols = SC_TERM_COLS_DEFAULT; // default value
}
}

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 = SC_PROCESS_NONE;
file_handler->current_process = 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 sc_pid
static process_t
install_apk(const char *serial, const char *file) {
return adb_install(serial, file);
}
static sc_pid
static process_t
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 = SC_PROCESS_NONE;
file_handler->current_process = 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;
sc_pid pid;
process_t process;
if (req.action == ACTION_INSTALL_APK) {
LOGI("Installing %s...", req.file);
pid = install_apk(file_handler->serial, req.file);
process = install_apk(file_handler->serial, req.file);
} else {
LOGI("Pushing %s...", req.file);
pid = push_file(file_handler->serial, req.file,
file_handler->push_target);
process = push_file(file_handler->serial, req.file,
file_handler->push_target);
}
file_handler->current_process = pid;
file_handler->current_process = process;
sc_mutex_unlock(&file_handler->mutex);
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);
} else {
LOGE("Failed to install %s", req.file);
}
} 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,
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 necessarily already terminated)
// Close the process (it is necessary already terminated)
// Execute this call with mutex locked to avoid race conditions with
// file_handler_stop()
sc_process_close(file_handler->current_process);
file_handler->current_process = SC_PROCESS_NONE;
process_close(file_handler->current_process);
file_handler->current_process = 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 != SC_PROCESS_NONE) {
if (!sc_process_terminate(file_handler->current_process)) {
if (file_handler->current_process != PROCESS_NONE) {
if (!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;
sc_pid current_process;
process_t 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 = sc_file_get_local_path(SCRCPY_PORTABLE_ICON_FILENAME);
char *icon_path = get_local_file_path(SCRCPY_PORTABLE_ICON_FILENAME);
if (!icon_path) {
LOGE("Could not get icon path");
return NULL;

View File

@@ -8,7 +8,6 @@
#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"
@@ -49,7 +48,7 @@ get_server_path(void) {
return NULL;
}
#else
char *server_path = sc_file_get_local_path(SERVER_FILENAME);
char *server_path = get_local_file_path(SERVER_FILENAME);
if (!server_path) {
LOGE("Could not get local file path, "
"using " SERVER_FILENAME " from current directory");
@@ -68,38 +67,38 @@ push_server(const char *serial) {
if (!server_path) {
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);
free(server_path);
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);
return sc_process_check_success(pid, "adb push", true);
return process_check_success(process, "adb push", true);
}
static bool
enable_tunnel_reverse(const char *serial, uint16_t local_port) {
sc_pid pid = adb_reverse(serial, SOCKET_NAME, local_port);
return sc_process_check_success(pid, "adb reverse", true);
process_t process = adb_reverse(serial, SOCKET_NAME, local_port);
return process_check_success(process, "adb reverse", true);
}
static bool
disable_tunnel_reverse(const char *serial) {
sc_pid pid = adb_reverse_remove(serial, SOCKET_NAME);
return sc_process_check_success(pid, "adb reverse --remove", true);
process_t process = adb_reverse_remove(serial, SOCKET_NAME);
return process_check_success(process, "adb reverse --remove", true);
}
static bool
enable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward(serial, local_port, SOCKET_NAME);
return sc_process_check_success(pid, "adb forward", true);
process_t process = adb_forward(serial, local_port, SOCKET_NAME);
return process_check_success(process, "adb forward", true);
}
static bool
disable_tunnel_forward(const char *serial, uint16_t local_port) {
sc_pid pid = adb_forward_remove(serial, local_port);
return sc_process_check_success(pid, "adb forward --remove", true);
process_t process = adb_forward_remove(serial, local_port);
return process_check_success(process, "adb forward --remove", true);
}
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) {
char max_size_string[6];
char bit_rate_string[11];
@@ -327,7 +326,7 @@ connect_to_server(uint16_t port, uint32_t attempts, uint32_t delay) {
bool
server_init(struct server *server) {
server->serial = NULL;
server->process = SC_PROCESS_NONE;
server->process = PROCESS_NONE;
bool ok = sc_mutex_init(&server->mutex);
if (!ok) {
@@ -357,7 +356,7 @@ server_init(struct server *server) {
static int
run_wait_server(void *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);
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->process = execute_server(server, params);
if (server->process == SC_PROCESS_NONE) {
if (server->process == PROCESS_NONE) {
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,
"wait-server", server);
if (!ok) {
sc_process_terminate(server->process);
sc_process_wait(server->process, true); // ignore exit code
process_terminate(server->process);
process_wait(server->process, true); // ignore exit code
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) {
// ignore failure
@@ -533,11 +532,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...");
sc_process_terminate(server->process);
process_terminate(server->process);
}
sc_thread_join(&server->wait_server_thread, NULL);
sc_process_close(server->process);
process_close(server->process);
}
void

View File

@@ -22,7 +22,7 @@ struct server_info {
struct server {
char *serial;
sc_pid process;
process_t process;
sc_thread wait_server_thread;
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 <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"
enum sc_process_result
sc_process_execute_p(const char *const argv[], sc_pid *pid,
int *pin, int *pout, int *perr) {
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) {
int in[2];
int out[2];
int err[2];
@@ -20,44 +60,44 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
if (pipe(internal) == -1) {
perror("pipe");
return SC_PROCESS_ERROR_GENERIC;
return PROCESS_ERROR_GENERIC;
}
if (pin) {
if (pipe_stdin) {
if (pipe(in) == -1) {
perror("pipe");
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
return PROCESS_ERROR_GENERIC;
}
}
if (pout) {
if (pipe_stdout) {
if (pipe(out) == -1) {
perror("pipe");
// clean up
if (pin) {
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
return PROCESS_ERROR_GENERIC;
}
}
if (perr) {
if (pipe_stderr) {
if (pipe(err) == -1) {
perror("pipe");
// clean up
if (pout) {
if (pipe_stdout) {
close(out[0]);
close(out[1]);
}
if (pin) {
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
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) {
perror("fork");
// clean up
if (perr) {
if (pipe_stderr) {
close(err[0]);
close(err[1]);
}
if (pout) {
if (pipe_stdout) {
close(out[0]);
close(out[1]);
}
if (pin) {
if (pipe_stdin) {
close(in[0]);
close(in[1]);
}
close(internal[0]);
close(internal[1]);
return SC_PROCESS_ERROR_GENERIC;
return PROCESS_ERROR_GENERIC;
}
if (*pid == 0) {
if (pin) {
if (pipe_stdin) {
if (in[0] != STDIN_FILENO) {
dup2(in[0], STDIN_FILENO);
close(in[0]);
}
close(in[1]);
}
if (pout) {
if (pipe_stdout) {
if (out[1] != STDOUT_FILENO) {
dup2(out[1], STDOUT_FILENO);
close(out[1]);
}
close(out[0]);
}
if (perr) {
if (pipe_stderr) {
if (err[1] != STDERR_FILENO) {
dup2(err[1], STDERR_FILENO);
close(err[1]);
@@ -105,15 +145,15 @@ sc_process_execute_p(const char *const argv[], sc_pid *pid,
close(err[0]);
}
close(internal[0]);
enum sc_process_result err;
enum process_result err;
if (fcntl(internal[1], F_SETFD, FD_CLOEXEC) == 0) {
execvp(argv[0], (char *const *) argv);
perror("exec");
err = errno == ENOENT ? SC_PROCESS_ERROR_MISSING_BINARY
: SC_PROCESS_ERROR_GENERIC;
err = errno == ENOENT ? PROCESS_ERROR_MISSING_BINARY
: PROCESS_ERROR_GENERIC;
} else {
perror("fcntl");
err = SC_PROCESS_ERROR_GENERIC;
err = PROCESS_ERROR_GENERIC;
}
// send err to the parent
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]);
enum sc_process_result res = SC_PROCESS_SUCCESS;
enum process_result res = PROCESS_SUCCESS;
// wait for EOF or receive err from child
if (read(internal[0], &res, sizeof(res)) == -1) {
perror("read");
res = SC_PROCESS_ERROR_GENERIC;
res = PROCESS_ERROR_GENERIC;
}
close(internal[0]);
if (pin) {
if (pipe_stdin) {
close(in[0]);
*pin = in[1];
*pipe_stdin = in[1];
}
if (pout) {
*pout = out[0];
if (pipe_stdout) {
*pipe_stdout = out[0];
close(out[1]);
}
if (perr) {
*perr = err[0];
if (pipe_stderr) {
*pipe_stderr = 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
sc_process_terminate(pid_t pid) {
process_terminate(pid_t pid) {
if (pid <= 0) {
LOGC("Requested to kill %d, this is an error. Please report the bug.\n",
(int) pid);
@@ -163,8 +208,8 @@ sc_process_terminate(pid_t pid) {
return kill(pid, SIGKILL) != -1;
}
sc_exit_code
sc_process_wait(pid_t pid, bool close) {
exit_code_t
process_wait(pid_t pid, bool close) {
int code;
int options = WEXITED;
if (!close) {
@@ -175,7 +220,7 @@ sc_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 = SC_EXIT_CODE_NONE;
code = NO_EXIT_CODE;
} else {
code = info.si_status;
}
@@ -183,17 +228,48 @@ sc_process_wait(pid_t pid, bool close) {
}
void
sc_process_close(pid_t pid) {
sc_process_wait(pid, true); // ignore exit code
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);
}
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);
}
void
sc_pipe_close(int pipe) {
close_pipe(int pipe) {
if (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 <assert.h>
#include <sys/stat.h>
#include "util/log.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)
size_t ret = xstrjoin(cmd, argv, ' ', len);
if (ret >= len) {
LOGE("Command too long (%" SC_PRIsizet " chars)", len - 1);
LOGE("Command too long (%" PRIsizet " chars)", len - 1);
return false;
}
return true;
}
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;
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;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
@@ -34,32 +36,32 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
HANDLE stdin_read_handle;
HANDLE stdout_write_handle;
HANDLE stderr_write_handle;
if (pin) {
if (!CreatePipe(&stdin_read_handle, pin, &sa, 0)) {
if (pipe_stdin) {
if (!CreatePipe(&stdin_read_handle, pipe_stdin, &sa, 0)) {
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");
goto error_close_stdin;
}
}
if (pout) {
if (!CreatePipe(pout, &stdout_write_handle, &sa, 0)) {
if (pipe_stdout) {
if (!CreatePipe(pipe_stdout, &stdout_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdin;
}
if (!SetHandleInformation(*pout, HANDLE_FLAG_INHERIT, 0)) {
if (!SetHandleInformation(*pipe_stdout, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stdout failed");
goto error_close_stdout;
}
}
if (perr) {
if (!CreatePipe(perr, &stderr_write_handle, &sa, 0)) {
if (pipe_stderr) {
if (!CreatePipe(pipe_stderr, &stderr_write_handle, &sa, 0)) {
perror("pipe");
goto error_close_stdout;
}
if (!SetHandleInformation(*perr, HANDLE_FLAG_INHERIT, 0)) {
if (!SetHandleInformation(*pipe_stderr, HANDLE_FLAG_INHERIT, 0)) {
LOGE("SetHandleInformation stderr failed");
goto error_close_stderr;
}
@@ -69,15 +71,15 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
PROCESS_INFORMATION pi;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
if (pin || pout || perr) {
if (pipe_stdin || pipe_stdout || pipe_stderr) {
si.dwFlags = STARTF_USESTDHANDLES;
if (pin) {
if (pipe_stdin) {
si.hStdInput = stdin_read_handle;
}
if (pout) {
if (pipe_stdout) {
si.hStdOutput = stdout_write_handle;
}
if (perr) {
if (pipe_stderr) {
si.hStdError = stderr_write_handle;
}
}
@@ -101,58 +103,63 @@ sc_process_execute_p(const char *const argv[], HANDLE *handle,
*handle = NULL;
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
ret = SC_PROCESS_ERROR_MISSING_BINARY;
ret = PROCESS_ERROR_MISSING_BINARY;
}
goto error_close_stderr;
}
// These handles are used by the child process, close them for this process
if (pin) {
if (pipe_stdin) {
CloseHandle(stdin_read_handle);
}
if (pout) {
if (pipe_stdout) {
CloseHandle(stdout_write_handle);
}
if (perr) {
if (pipe_stderr) {
CloseHandle(stderr_write_handle);
}
free(wide);
*handle = pi.hProcess;
return SC_PROCESS_SUCCESS;
return PROCESS_SUCCESS;
error_close_stderr:
if (perr) {
CloseHandle(*perr);
if (pipe_stderr) {
CloseHandle(*pipe_stderr);
CloseHandle(stderr_write_handle);
}
error_close_stdout:
if (pout) {
CloseHandle(*pout);
if (pipe_stdout) {
CloseHandle(*pipe_stdout);
CloseHandle(stdout_write_handle);
}
error_close_stdin:
if (pin) {
CloseHandle(*pin);
if (pipe_stdin) {
CloseHandle(*pipe_stdin);
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
sc_process_terminate(HANDLE handle) {
process_terminate(HANDLE handle) {
return TerminateProcess(handle, 1);
}
sc_exit_code
sc_process_wait(HANDLE handle, bool close) {
exit_code_t
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 = SC_EXIT_CODE_NONE;
code = NO_EXIT_CODE; // max value, it's unsigned
}
if (close) {
CloseHandle(handle);
@@ -161,14 +168,48 @@ sc_process_wait(HANDLE handle, bool close) {
}
void
sc_process_close(HANDLE handle) {
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
sc_read_pipe(HANDLE pipe, char *data, size_t len) {
read_pipe(HANDLE pipe, char *data, size_t len) {
DWORD r;
if (!ReadFile(pipe, data, len, &r, NULL)) {
return -1;
@@ -177,7 +218,7 @@ sc_read_pipe(HANDLE pipe, char *data, size_t len) {
}
void
sc_close_pipe(HANDLE pipe) {
close_pipe(HANDLE pipe) {
if (!CloseHandle(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 "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
sc_process_check_success(sc_pid pid, const char *name, bool close) {
if (pid == SC_PROCESS_NONE) {
process_check_success(process_t proc, const char *name, bool close) {
if (proc == PROCESS_NONE) {
LOGE("Could not execute \"%s\"", name);
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 != SC_EXIT_CODE_NONE) {
LOGE("\"%s\" returned with value %" SC_PRIexitcode, name,
exit_code);
if (exit_code != NO_EXIT_CODE) {
LOGE("\"%s\" returned with value %" PRIexitcode, name, exit_code);
} else {
LOGE("\"%s\" exited unexpectedly", name);
}
@@ -27,11 +21,52 @@ sc_process_check_success(sc_pid pid, 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
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;
while (len > 0) {
ssize_t r = sc_pipe_read(pipe, data, len);
ssize_t r = read_pipe(pipe, data, len);
if (r <= 0) {
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
# include <winsock2.h>
# include <windows.h>
# define SC_PRIexitcode "lu"
# define PATH_SEPARATOR '\\'
# define PRIexitcode "lu"
// <https://stackoverflow.com/a/44383330/1987178>
# 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;
# 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;
#else
# include <sys/types.h>
# 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;
# 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;
#endif
enum sc_process_result {
SC_PROCESS_SUCCESS,
SC_PROCESS_ERROR_GENERIC,
SC_PROCESS_ERROR_MISSING_BINARY,
enum process_result {
PROCESS_SUCCESS,
PROCESS_ERROR_GENERIC,
PROCESS_ERROR_MISSING_BINARY,
};
/**
* Execute the command and write the process id to `pid`
*/
enum sc_process_result
sc_process_execute(const char *const argv[], sc_pid *pid);
// 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`
*
* 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);
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);
/**
* Kill the process
*/
bool
sc_process_terminate(sc_pid pid);
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);
/**
* 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.
*/
// kill the process
bool
sc_process_check_success(sc_pid pid, const char *name, bool close);
process_terminate(process_t pid);
/**
* Read from the pipe
*
* Same semantic as read().
*/
ssize_t
sc_pipe_read(sc_pipe pipe, char *data, size_t len);
// 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);
/**
* Read exactly `len` chars from a pipe (unless EOF)
*/
ssize_t
sc_pipe_read_all(sc_pipe pipe, char *data, size_t len);
/**
* Close the pipe
*/
// close the process
//
// Semantically, process_wait(close) = process_wait(noclose) + process_close
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

View File

@@ -212,14 +212,14 @@ utf8_from_wide_char(const wchar_t *ws) {
#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);
struct sc_strbuf buf;
// 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
// 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
size_t cap = strlen(input) * 3 / 2;
if (!sc_strbuf_init(&buf, cap)) {

View File

@@ -62,12 +62,8 @@ char *
utf8_from_wide_char(const wchar_t *s);
#endif
/**
* 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);
// 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);
#endif

View File

@@ -1,8 +1,6 @@
#ifndef SC_STRBUF_H
#define SC_STRBUF_H
#include "common.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
@@ -13,60 +11,29 @@ struct sc_strbuf {
size_t cap;
};
/**
* Initialize the string buffer
*
* `buf->s` must be manually freed by the caller.
*/
// 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 a static string
*
* Append a string whose size is known at compile time (for
* example a string literal).
*/
// Append static string (i.e. the string 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,16 +5,6 @@
#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 = sc_str_wrap_lines(s, 24, 4);
char *formatted = wrap_lines(s, 24, 4);
assert(formatted);
assert(!strcmp(formatted, expected));