diff --git a/src/meson.build b/src/meson.build index 45c2f67..80f32c2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -87,6 +87,8 @@ foreach protocol : protocols protocol_deps += lib endforeach +inc = include_directories('.') + lib = static_library( 'wl-clipboard', [ @@ -124,9 +126,18 @@ lib = static_library( 'types/registry.c', 'types/copy-action.h', 'types/copy-action.c', + 'types/buffer.h', + 'types/buffer.c', + 'types/copy-source-argv.h', + 'types/copy-source-argv.c', + 'types/copy-source-buffer.h', + 'types/copy-source-buffer.c', + 'types/sensitive-section.h', + 'types/sensitive-section.c', ], dependencies: wayland, - link_with: protocol_deps + link_with: protocol_deps, + include_directories: [inc] ) executable( @@ -134,6 +145,7 @@ executable( ['wl-copy.c', protocol_headers], dependencies: wayland, link_with: lib, + include_directories: [inc], install: true ) executable( @@ -141,5 +153,6 @@ executable( ['wl-paste.c', protocol_headers], dependencies: wayland, link_with: lib, + include_directories: [inc], install: true ) diff --git a/src/types/buffer.c b/src/types/buffer.c new file mode 100644 index 0000000..48840a7 --- /dev/null +++ b/src/types/buffer.c @@ -0,0 +1,33 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include + +#include "buffer.h" + +static void noop(struct buffer*) { + /* intentionally left blank */ +} + +void buffer_steal(struct buffer* dest, struct buffer* src) { + memcpy(dest, src, sizeof(*src)); + src->destroy = noop; + src->ptr = NULL; + src->len = 0; +} diff --git a/src/types/buffer.h b/src/types/buffer.h new file mode 100644 index 0000000..ff5be08 --- /dev/null +++ b/src/types/buffer.h @@ -0,0 +1,38 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_OWNED_SLICE_H +#define TYPES_OWNED_SLICE_H + +#include + +struct buffer { + void (*destroy)(struct buffer*); + + char* ptr; + size_t len; +}; + +/// @brief Steal buffer to dest from src, leaving the latter empty +/// @param[out] dest Uninitialised (or empty) buffer +/// @param[in] src Initialised and valid buffer +/// @note It is safe to call .destroy on src after stealing +/// @warning Does not check anything +void buffer_steal(struct buffer* dest, struct buffer* src); + +#endif /* UTIL_FILES_H */ diff --git a/src/types/copy-action.c b/src/types/copy-action.c index f661fc3..9ee9b70 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -68,66 +68,10 @@ static void on_focus( static void do_send(struct source *source, const char *mime_type, int fd) { struct copy_action *self = source->data; - /* Unset O_NONBLOCK */ + /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); - if (self->file_to_copy != NULL) { - /* Copy the file to the given file descriptor - * by spawning an appropriate cat process. - */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - close(fd); - return; - } - if (pid == 0) { - dup2(fd, STDOUT_FILENO); - close(fd); - execlp("cat", "cat", self->file_to_copy, NULL); - perror("exec cat"); - exit(1); - } - close(fd); - /* Wait for the cat process to exit. This effectively - * means waiting for the other side to read the whole - * file. In theory, a malicious client could perform a - * denial-of-serivice attack against us. Perhaps we - * should switch to an asynchronous child waiting scheme - * instead. - */ - wait(NULL); - } else { - /* We'll perform the copy ourselves */ - FILE *f = fdopen(fd, "w"); - if (f == NULL) { - perror("fdopen"); - close(fd); - return; - } - - if (self->data_to_copy.ptr != NULL) { - /* Just copy the given chunk of data */ - fwrite(self->data_to_copy.ptr, 1, self->data_to_copy.len, f); - } else if (self->argv_to_copy != NULL) { - /* Copy an argv-style string array, - * inserting spaces between items. - */ - int is_first = 1; - for (argv_t word = self->argv_to_copy; *word != NULL; word++) { - if (!is_first) { - fwrite(" ", 1, 1, f); - } - is_first = 0; - fwrite(*word, 1, strlen(*word), f); - } - } else { - bail("Unreachable: nothing to copy"); - } - - fclose(f); - } - + self->src->copy(fd, self->src); if (self->pasted_callback != NULL) { self->pasted_callback(self); @@ -141,7 +85,8 @@ static void forward_cancel(struct source *source) { } } -void copy_action_init(struct copy_action *self) { +void copy_action_init(struct copy_action *self, struct copy_source* src) { + self->src = src; if (self->source != NULL) { self->source->send_callback = do_send; self->source->cancelled_callback = forward_cancel; diff --git a/src/types/copy-action.h b/src/types/copy-action.h index c0a1290..e462ae6 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -19,6 +19,7 @@ #ifndef TYPES_COPY_ACTION_H #define TYPES_COPY_ACTION_H +#include #include "util/string.h" #include @@ -38,17 +39,10 @@ struct copy_action { void (*pasted_callback)(struct copy_action *self); void (*cancelled_callback)(struct copy_action *self); - /* Exactly one of these fields must be non-null if the source - * is non-null, otherwise all these fields must be null. - */ - const char *file_to_copy; - argv_t argv_to_copy; - struct { - const char *ptr; - size_t len; - } data_to_copy; + struct copy_source* src; }; -void copy_action_init(struct copy_action *self); +/// @brief Initialise copy action from specified copy source +void copy_action_init(struct copy_action *self, struct copy_source* src); #endif /* TYPES_COPY_ACTION_H */ diff --git a/src/types/copy-source-argv.c b/src/types/copy-source-argv.c new file mode 100644 index 0000000..bac72b8 --- /dev/null +++ b/src/types/copy-source-argv.c @@ -0,0 +1,57 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include +#include +#include + +#include + +static void copy(int fd, struct copy_source* self) { + struct copy_source_argv* self2 = (struct copy_source_argv*)self; + + FILE* file = fdopen(fd, "w"); + if (!file) { + perror("copy_source_argv/copy: fdopen"); + close(fd); + return; + } + + argv_t word = self2->argv; + if (*word) { + fwrite(*word, 1, strlen(*word), file); + ++word; + } + for (; *word; ++word) { + fputc(' ', file); + fwrite(*word, 1, strlen(*word), file); + } + fclose(file); +} + + +int copy_source_argv_init(struct copy_source_argv* self, argv_t argv) { + self->impl.copy = copy; + if (!argv) { + return -1; + } + self->argv = argv; + return 0; +} + diff --git a/src/types/copy-source-argv.h b/src/types/copy-source-argv.h new file mode 100644 index 0000000..6ab11fc --- /dev/null +++ b/src/types/copy-source-argv.h @@ -0,0 +1,41 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_COPY_SOURCE_ARGV_H +#define TYPES_COPY_SOURCE_ARGV_H + +#include +#include + +#include + +/// @brief Copy source, serving data from the argv-like array of strings +struct copy_source_argv { + struct copy_source impl; + + argv_t argv; +}; + +/// @brief Initialise copy source from argv-like array of (unowned) array of strings, +/// terminated with empty string +/// @param[out] self Uninitialised copy source +/// @param[in] argv argv-like array of strings +/// @return 0 on success, negative value otherwise +int copy_source_argv_init(struct copy_source_argv* self, argv_t argv); + +#endif /* TYPES_COPY_ACTION_H */ diff --git a/src/types/copy-source-buffer.c b/src/types/copy-source-buffer.c new file mode 100644 index 0000000..407efe7 --- /dev/null +++ b/src/types/copy-source-buffer.c @@ -0,0 +1,67 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include + +static void copy(int fd, struct copy_source* self) { + struct copy_source_buffer* self2 = (struct copy_source_buffer*)self; + + const char* ptr = self2->slice.ptr; + size_t len = self2->slice.len; + + for (;;) { + ssize_t bytes = write(fd, ptr, len); + if (bytes < 0) { + perror("copy_source_slice/copy: write"); + close(fd); + exit(1); + } + len -= bytes; + if (!bytes || !len) { + break; + } + ptr += bytes; + } + + if (close(fd)) { + perror("copy_source_slice/copy: close"); + } +} + +static void destroy(struct copy_source* self) { + struct copy_source_buffer* self2 = (struct copy_source_buffer*)self; + self2->slice.destroy(&self2->slice); +} + + +int copy_source_buffer_init(struct copy_source_buffer* self, struct buffer* slice) { + if (!slice || !slice->ptr || !slice->len) { + return -1; + } + + self->impl.copy = copy; + self->impl.destroy = destroy; + + buffer_steal(&self->slice, slice); + return 0; +} + diff --git a/src/types/copy-source-buffer.h b/src/types/copy-source-buffer.h new file mode 100644 index 0000000..3a3ec28 --- /dev/null +++ b/src/types/copy-source-buffer.h @@ -0,0 +1,40 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_COPY_SOURCE_SLICE_H +#define TYPES_COPY_SOURCE_SLICE_H + +#include +#include + +#include + +/// @brief Copy source, serving data from the provided buffer +struct copy_source_buffer { + struct copy_source impl; + + struct buffer slice; +}; + +/// @brief Initialise copy source from buffer, stealing it from provided ptr +/// @param[out] self +/// @param[in] src +/// @return 0 on success, negative value otherwise +int copy_source_buffer_init(struct copy_source_buffer* self, struct buffer* src); + +#endif /* TYPES_COPY_ACTION_H */ diff --git a/src/types/copy-source.h b/src/types/copy-source.h new file mode 100644 index 0000000..b5f1739 --- /dev/null +++ b/src/types/copy-source.h @@ -0,0 +1,32 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_COPY_SOURCE_H +#define TYPES_COPY_SOURCE_H + +#include + +struct copy_source; +typedef void (*copy_source_copy_t)(int, struct copy_source*); + +struct copy_source { + void (*copy)(int, struct copy_source*); + void (*destroy)(struct copy_source*); +}; + +#endif /* TYPES_COPY_ACTION_H */ diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 1aad115..5e768f6 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -96,15 +96,18 @@ void popup_surface_init(struct popup_surface *self) { int size = stride * height; /* Open an anonymous file and write some zero bytes to it */ - int fd = create_anonymous_file(); - ftruncate(fd, size); + struct anonfile anon; + if (create_anonymous_file(&anon)) { + bail("Failed to create anonymous file"); + } + ftruncate(anon.fd, size); /* Create a shared memory pool */ struct wl_shm *wl_shm = self->registry->wl_shm; if (wl_shm == NULL) { bail("Missing the shm"); } - struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, fd, size); + struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, anon.fd, size); /* Allocate the buffer in that pool */ struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer( diff --git a/src/types/sensitive-section.c b/src/types/sensitive-section.c new file mode 100644 index 0000000..85685c0 --- /dev/null +++ b/src/types/sensitive-section.c @@ -0,0 +1,73 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +/* "Good enough" signal handler. It's not hard to break, but it works for the most common cases */ + +static struct sensitive* current_handler = NULL; + +static const int sigs[] = { SIGINT }; + +static void handler_impl(int signal) { + (void)signal; + + if (current_handler) { + current_handler->cleanup(current_handler); + } +} + +int sensitive_begin(struct sensitive* handler) { + // we don't use multithreading, so we don't care about races + if (current_handler) { + return -1; + } + current_handler = handler; + + const int* sig = sigs; + const int* const end = sigs + sizeof( sigs ) / sizeof( *sigs ); + for (; sig != end; ++sig) { + if (signal(*sig, handler_impl) == SIG_ERR) { + perror("signal"); + goto restore; + } + } + + return 0; + +restore: + --sig; + for (; sig != (sigs - 1); --sig) { + signal(*sig, SIG_DFL); + } + return -1; +} + +/// @brief End "sensitive" section +/// @param[in] handler Emergency handler +int sensitive_end(struct sensitive* handler) { + (void)handler; + + signal(SIGKILL, SIG_DFL); + signal(SIGINT, SIG_DFL); + + current_handler = NULL; + return 0; +} diff --git a/src/types/sensitive-section.h b/src/types/sensitive-section.h new file mode 100644 index 0000000..37ffacb --- /dev/null +++ b/src/types/sensitive-section.h @@ -0,0 +1,36 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_SENSITIVE_SECTION_H +#define TYPES_SENSITIVE_SECTION_H + +struct sensitive { + void (*cleanup)(struct sensitive*); +}; + +/// @brief Begin "sensitive" section +/// @param[in] handler Emergency handler, invoked on signal +/// @return 0 on success, negative value otherwise +int sensitive_begin(struct sensitive* handler); + +/// @brief End "sensitive" section +/// @param[in] handler Emergency handler, invoked on signal +/// @return 0 on success, negative value otherwise +int sensitive_end(struct sensitive* handler); + +#endif diff --git a/src/util/files.c b/src/util/files.c index 750aa0b..45a438c 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -#include "util/files.h" -#include "util/string.h" -#include "util/misc.h" +#include +#include +#include #include "config.h" @@ -28,11 +28,13 @@ #include #include #include // open +#include // sendfile #include // open #include // open #include // exit #include // basename #include +#include #ifdef HAVE_MEMFD # include // syscall, SYS_memfd_create @@ -40,24 +42,62 @@ #ifdef HAVE_SHM_ANON # include // shm_open, SHM_ANON #endif +#ifdef HAVE_CLONE3 +# include +#endif + + +static void do_close(struct anonfile* file) { + if (close(file->fd)) { + perror("tmpfile/destroy: close"); + } +} + +static void do_fclose(struct anonfile* file) { + FILE* ctx = file->ctx; + if (fclose(ctx)) { + perror("tmpfile/destroy: fclose"); + } +} -int create_anonymous_file() { +int create_anonymous_file(struct anonfile* file) { int res; #ifdef HAVE_MEMFD res = syscall(SYS_memfd_create, "buffer", 0); if (res >= 0) { - return res; + file->destroy = do_close; + file->ctx = NULL; + file->fd = res; + return 0; } #endif #ifdef HAVE_SHM_ANON res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); if (res >= 0) { - return res; + file->destroy = do_close; + file->ctx = NULL; + file->fd = res; + return 0; } #endif - (void) res; - return fileno(tmpfile()); + + FILE* tmp = tmpfile(); + if (!tmp) { + perror("tmpfile"); + return -1; + } + + res = fileno(tmp); + if (res < 0) { + fclose(tmp); + perror("fileno"); + return res; + } + file->destroy = do_fclose; + file->ctx = tmp; + file->fd = res; + return 0; } void trim_trailing_newline(const char *file_path) { @@ -200,13 +240,82 @@ char *infer_mime_type_from_name(const char *file_path) { return NULL; } +static int dump_stdin_to_file_using_cat(const char* res_path) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + if (pid == 0) { + int fd = open(res_path, O_RDWR | O_EXCL | O_CREAT, S_IRUSR | S_IWUSR); + if (fd < 0) { + perror("open"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + execlp("cat", "cat", NULL); + perror("exec cat"); + exit(1); + } + + int wstatus; + wait(&wstatus); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { + return -1; + } + return 0; +} + +static int dump_stdin_to_file_using_mmap(int fd) { + struct buffer mmapped; + int err = buffer_mmap_file(&mmapped, STDIN_FILENO); + if (err) { + return err; + } + + char* ptr = mmapped.ptr; + size_t len = mmapped.len; + while (len) { + ssize_t written = write(fd, ptr, len); + if (written < 0) { + perror("dump_stdin_to_file_using_mmap: write"); + return -1; + } + len -= written; + ptr += written; + } + return 0; +} + +static int dump_stdin_to_file_using_sendfile(int fd) { + ssize_t bytes = sendfile(fd, STDIN_FILENO, NULL, 4096); + if (bytes < 0) { + int err = errno; + if (err != EINVAL) { + perror("sendfile"); + return -1; + } + return 1; + } + while (bytes > 0) { + bytes = sendfile(fd, STDIN_FILENO, NULL, 4096); + } + if (bytes < 0) { + perror("sendfile"); + return -1; + } + + return 0; +} + /* Returns the name of a new file */ -char *dump_stdin_into_a_temp_file() { +int dump_stdin_into_a_temp_file(int* fildes, char** path) { /* Create a temp directory to host out file */ char dirpath[] = "/tmp/wl-copy-buffer-XXXXXX"; if (mkdtemp(dirpath) != dirpath) { perror("mkdtemp"); - exit(1); + return -1; } /* Pick a name for the file we'll be @@ -223,32 +332,147 @@ char *dump_stdin_into_a_temp_file() { res_path[sizeof(dirpath) - 1] = '/'; strcpy(res_path + sizeof(dirpath), name); - /* Spawn cat to perform the copy */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - exit(1); + int fd = open(res_path, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + perror("open"); + return -1; } - if (pid == 0) { - int fd = creat(res_path, S_IRUSR | S_IWUSR); - if (fd < 0) { - perror("creat"); - exit(1); - } - dup2(fd, STDOUT_FILENO); - close(fd); - execlp("cat", "cat", NULL); - perror("exec cat"); - exit(1); + + // Try hard to do less, forking is much more expensive anyway + int err = dump_stdin_to_file_using_mmap(fd); + switch (err) { + case 0: goto done; + case -1: goto fail; } - int wstatus; - wait(&wstatus); + err = dump_stdin_to_file_using_sendfile(fd); + switch (err) { + case 0: goto done; + case -1: goto fail; + } + + err = dump_stdin_to_file_using_cat(res_path); + switch (err) { + case 0: goto done; + case -1: goto fail; + } + +fail: + close(fd); + unlink(res_path); + rmdir(dirpath); + return -1; + +done: if (original_path != NULL) { free(original_path); } - if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { - bail("Failed to copy the file"); + *fildes = fd; + *path = res_path; + return 0; +} + + +static void do_munmap(struct buffer* self) { + if (munmap(self->ptr, self->len) ) { + perror("owned_slice/destroy: do_munmap"); + } + memset(self, 0, sizeof(*self)); +} + + +int buffer_mmap_file(struct buffer* self, int fd) { + struct stat stat; + if (fstat(fd, &stat)) { + perror("owned_slice_mmap_file: fstat"); + return -1; + } + + if (!S_ISREG(stat.st_mode)) { + return 1; } - return res_path; + + char* ptr = mmap(NULL, stat.st_blksize, MAP_PRIVATE, PROT_READ, fd, 0); + if (ptr == MAP_FAILED) { + return 1; + } + + self->destroy = do_munmap; + self->ptr = ptr; + self->len = stat.st_blksize; + return 0; } + + + +static void do_free(struct buffer* self) { + free(self->ptr); +} + +enum { + BUFFER_SIZE = 4096 +}; + +int copy_stdin_to_mem(struct buffer* slice) { + // opportunistic mmap in case if it's a mmappable (e.g. wl-copy < some_file) + switch (buffer_mmap_file(slice, STDIN_FILENO)) { + case -1: return -1; + case 0: return 0; + case 1: break; + } + + char* begin = malloc(BUFFER_SIZE); + if (!begin) { + goto fail; + } + char* ptr = begin; + char* end = begin + BUFFER_SIZE; + + for (;;) { + ssize_t bytes = read(STDIN_FILENO, ptr, end - ptr); + if (bytes < 0) { + perror("copy_stdin_to_mem: read"); + goto fail; + } + if (bytes == 0) { + break; + } + + ptr += bytes; + if (ptr == end) { + size_t len = end - begin; + begin = realloc(begin, len + BUFFER_SIZE); + if (!begin) { + goto fail; + } + ptr = begin + len; + end = begin + len + BUFFER_SIZE; + } + } + + slice->destroy = do_free; + slice->ptr = begin; + slice->len = ptr - begin; + + return 0; + +fail: + if (begin) { + free(begin); + } + return -1; +} + + +static void sensitive_do_close(struct sensitive* self) { + struct sensitive_fd* self2 = (struct sensitive_fd*)self; + int fd = self2->fd; + self2->fd = -1; + close(fd); +} + +void sensitive_fd_init(struct sensitive_fd* handler, int fd) { + handler->impl.cleanup = sensitive_do_close; + handler->fd = fd; +} + diff --git a/src/util/files.h b/src/util/files.h index 2a2d4b6..1b7de98 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -19,7 +19,21 @@ #ifndef UTIL_FILES_H #define UTIL_FILES_H -int create_anonymous_file(void); +#include +#include +#include + +struct anonfile { + void (*destroy)(struct anonfile*); + void* ctx; + + int fd; +}; + +/// @brief Create anonymous file +/// @param[out] file +/// @return 0 on success, negative value otherwise +int create_anonymous_file(struct anonfile* file); void trim_trailing_newline(const char *file_path); @@ -31,7 +45,34 @@ char *path_for_fd(int fd); char *infer_mime_type_from_contents(const char *file_path); char *infer_mime_type_from_name(const char *file_path); -/* Returns the name of a new file */ -char *dump_stdin_into_a_temp_file(void); +/// @brief Creates a temporary file and writes stdin contents into it +/// @param[out] fd Temporary file descriptor +/// @param[out] path Path to created file +/// @return 0 on success, negative value otherwise +/// @note Use `free` to free `path`. +int dump_stdin_into_a_temp_file(int* fd, char** path); + +/// @brief Copy stdin contents into memory +/// @param[out] slice Uninitialised valid buffer +/// @return 0 on success, -1 on failure +/// @note Attempts to mmap stdin, falls back to plain `read(2)` if it's not allowed +int copy_stdin_to_mem(struct buffer* slice); + +/// @brief Attempts to mmap file `fd`. +/// @param[out] self Uninitialised valid buffer +/// @param[in] fd Valid file descriptor +/// @return 0 on success, -1 on failure and 1 if the fd is not mmappable +int buffer_mmap_file(struct buffer* self, int fd); + +struct sensitive_fd { + struct sensitive impl; + + int fd; +}; + +/// @brief When used in sensitive section, close provided fd +/// @param[out] handler +/// @param[in] fd +void sensitive_fd_init(struct sensitive_fd* handler, int fd); #endif /* UTIL_FILES_H */ diff --git a/src/util/string.c b/src/util/string.c index f39db31..5af418c 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -#include "util/string.h" +#include -#include #include +#include int mime_type_is_text(const char *mime_type) { /* A heuristic to detect plain text mime types */ diff --git a/src/wl-copy.c b/src/wl-copy.c index f08cf17..2521440 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -16,16 +16,19 @@ * along with this program. If not, see . */ -#include "types/copy-action.h" -#include "types/source.h" -#include "types/device.h" -#include "types/device-manager.h" -#include "types/registry.h" -#include "types/popup-surface.h" +#include +#include +#include +#include +#include +#include -#include "util/files.h" -#include "util/string.h" -#include "util/misc.h" +#include +#include + +#include +#include +#include #include #include @@ -85,15 +88,8 @@ static void cleanup_and_exit(struct copy_action *copy_action, int code) { /* We're done copying! * All that's left to do now is to * clean up after ourselves and exit.*/ - char *temp_file = (char *) copy_action->file_to_copy; - if (temp_file != NULL) { - /* Clean up our temporary file */ - execlp("rm", "rm", "-r", dirname(temp_file), NULL); - perror("exec rm"); - exit(1); - } else { - exit(code); - } + copy_action->src->destroy(copy_action->src); + exit(code); } static void cancelled_callback(struct copy_action *copy_action) { @@ -196,6 +192,106 @@ static void parse_options(int argc, argv_t argv) { } } + +static struct copy_source* copy_source_from_args(int argc, argv_t argv) { + if (optind < argc) { + struct copy_source_argv* src = malloc(sizeof(struct copy_source_argv)); + if (!src) { + perror("malloc"); + return NULL; + } + if (copy_source_argv_init(src, &argv[optind])) { + free(src); + return NULL; + } + + return (struct copy_source*)src; + } + + if (options.mime_type) { + struct buffer slice; + if (copy_stdin_to_mem(&slice)) { + return NULL; + } + + struct copy_source_buffer* src = malloc(sizeof(struct copy_source_buffer)); + if (!src) { + perror("malloc"); + slice.destroy(&slice); + return NULL; + } + + if (copy_source_buffer_init(src, &slice)) { + free(src); + slice.destroy(&slice); + return NULL; + } + + return (struct copy_source*)src; + } + + struct sensitive_fd sensitive_handler; + sensitive_fd_init(&sensitive_handler, -1); + + // begin sensitive region + if (sensitive_begin((struct sensitive*)&sensitive_handler)) { + return NULL; + } + + char* temp_file; + if (dump_stdin_into_a_temp_file(&sensitive_handler.fd, &temp_file)) { + return NULL; + } + int fd = sensitive_handler.fd; + + if (options.trim_newline) { + trim_trailing_newline(temp_file); + } + options.mime_type = infer_mime_type_from_contents(temp_file); + + // end sensitive region, preserve error checking for later + int failed = sensitive_end((struct sensitive*)&sensitive_handler); + + // unlink only removes the name from fs, but the file is still open + unlink(temp_file); + char* dir = dirname(temp_file); + if (dir) { + // we can't handle failure sensibly, let's atleast not fail + rmdir(dir); + } + free(temp_file); + + // return if signalled + if (failed || sensitive_handler.fd < 0) { + return NULL; + } + + // mmap file + struct buffer map; + if (buffer_mmap_file(&map, fd)) { + close(fd); + return NULL; + } + // close fd, the file still exists as long as it's mmapped + close(fd); + + // we don't need to clean up the buffer, we'll be exiting anyway + struct copy_source_buffer* src = malloc(sizeof(struct copy_source_buffer)); + if (!src) { + perror("malloc"); + return NULL; + } + + if (copy_source_buffer_init(src, &map)) { + free(src); + return NULL; + } + + return (struct copy_source*)src; +} + + + int main(int argc, argv_t argv) { parse_options(argc, argv); @@ -238,26 +334,12 @@ int main(int argc, argv_t argv) { copy_action->device = device; copy_action->primary = options.primary; + struct copy_source* copy_source = NULL; + if (!options.clear) { - if (optind < argc) { - /* Copy our command-line arguments */ - copy_action->argv_to_copy = &argv[optind]; - } else { - /* Copy data from our stdin. - * It's important that we only do this - * after going through the initial stages - * that are likely to result in errors, - * so that we don't forget to clean up - * the temp file. - */ - char *temp_file = dump_stdin_into_a_temp_file(); - if (options.trim_newline) { - trim_trailing_newline(temp_file); - } - if (options.mime_type == NULL) { - options.mime_type = infer_mime_type_from_contents(temp_file); - } - copy_action->file_to_copy = temp_file; + copy_source = copy_source_from_args(argc, argv); + if (!copy_source) { + return EXIT_FAILURE; } /* Create the source */ @@ -286,7 +368,7 @@ int main(int argc, argv_t argv) { copy_action->did_set_selection_callback = did_set_selection_callback; copy_action->pasted_callback = pasted_callback; copy_action->cancelled_callback = cancelled_callback; - copy_action_init(copy_action); + copy_action_init(copy_action, copy_source); while (wl_display_dispatch(wl_display) >= 0);