/* wl-clipboard * * Copyright © 2018-2026 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 "util/files.h" #include "util/string.h" #include "util/misc.h" #include "config.h" #include #include #include #include #include #include #include #include // open #include // open #include // exit #include // basename #include #include #include #ifdef HAVE_MEMFD # include // syscall, SYS_memfd_create #endif #ifdef HAVE_SHM_ANON # include // shm_open, SHM_ANON #endif #include // wl_display_get_fd void complain_about_closed_stdio(struct wl_display *wl_display) { const char *message = "wl-clipboard has been launched with a closed" " standard file descriptor. This is a bug in the software that" " has launched wl-clipboard. Aborting."; /* See if we can write to stderr */ if (wl_display_get_fd(wl_display) < STDERR_FILENO) { int rc = fcntl(STDERR_FILENO, F_GETFL); if (rc > 0) { rc &= O_ACCMODE; if (rc == O_WRONLY || rc == O_RDWR) { /* Yes, we can! */ fprintf(stderr, "%s\n", message); fflush(stderr); abort(); } } } /* Maybe there is a tty we could write to? */ FILE *tty = fopen("/dev/tty", "w"); if (tty != NULL) { fprintf(tty, "%s\n", message); fflush(tty); abort(); } /* As a last resort, try syslog */ openlog("wl-clipboard", LOG_CONS | LOG_PID, LOG_USER); syslog(LOG_ERR, "%s", message); closelog(); abort(); } int create_anonymous_file() { int res; #ifdef HAVE_MEMFD res = syscall(SYS_memfd_create, "buffer", 0); if (res >= 0) { return res; } #endif #ifdef HAVE_SHM_ANON res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); if (res >= 0) { return res; } #endif (void) res; return fileno(tmpfile()); } void trim_trailing_newline(const char *file_path) { int fd = open(file_path, O_RDWR); if (fd < 0) { perror("open file for trimming"); return; } int seek_res = lseek(fd, -1, SEEK_END); if (seek_res < 0 && errno == EINVAL) { /* It was an empty file */ goto out; } else if (seek_res < 0) { perror("lseek"); goto out; } /* If the seek was successful, seek_res is the * new file size after trimming the newline. */ char last_char; int read_res = read(fd, &last_char, 1); if (read_res != 1) { perror("read"); goto out; } if (last_char != '\n') { goto out; } int rc = ftruncate(fd, seek_res); if (rc < 0) { perror("ftruncate"); } out: close(fd); } char *path_for_fd(int fd) { char fdpath[64]; snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd); return realpath(fdpath, NULL); } char *infer_mime_type_from_contents(const char *file_path) { /* Spawn xdg-mime query filetype */ int pipefd[2]; int rc = pipe(pipefd); if (rc < 0) { perror("pipe"); return NULL; } pid_t pid = fork(); if (pid < 0) { perror("fork"); close(pipefd[0]); close(pipefd[1]); return NULL; } if (pid == 0) { dup2(pipefd[1], STDOUT_FILENO); close(pipefd[0]); close(pipefd[1]); int devnull = open("/dev/null", O_RDONLY); if (devnull >= 0) { dup2(devnull, STDIN_FILENO); close(devnull); } else { /* If we cannot open /dev/null, just close stdin */ close(STDIN_FILENO); } signal(SIGHUP, SIG_DFL); signal(SIGPIPE, SIG_DFL); execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); exit(1); } close(pipefd[1]); int wstatus; waitpid(pid, &wstatus, 0); /* See if that worked */ if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { close(pipefd[0]); return NULL; } /* Read the result */ char *res = malloc(256); ssize_t len = read(pipefd[0], res, 256); close(pipefd[0]); if (len <= 0) { free(res); return NULL; } /* Trim the newline */ len--; res[len] = 0; if (str_has_prefix(res, "inode/")) { free(res); return NULL; } return res; } static char *search_mime_dot_types_for_ext(const char *ext) { if (ext == NULL) { return NULL; } FILE *f = fopen("/etc/mime.types", "r"); if (f == NULL) { f = fopen("/usr/local/etc/mime.types", "r"); } if (f == NULL) { return NULL; } for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { /* Skip comments and blank lines */ if (line[0] == '#' || line[0] == '\n') { continue; } /* Each line consists of a mime type and a list of extensions */ char mime_type[200]; int consumed; if (sscanf(line, "%199s%n", mime_type, &consumed) != 1) { /* A malformed line, perhaps? */ fprintf(stderr, "malformed mime.types line: %s\n", line); continue; } char *lineptr = line + consumed; for (char ext_pattern[200]; sscanf(lineptr, "%199s%n", ext_pattern, &consumed) == 1;) { if (strcmp(ext_pattern, ext) == 0) { fclose(f); return strdup(mime_type); } lineptr += consumed; } } fclose(f); return NULL; } static char *search_shared_mime_info_globs_for_filename(const char *filename) { FILE *f = fopen("/usr/share/mime/globs2", "r"); if (f == NULL) { f = fopen("/usr/local/share/mime/globs2", "r"); } if (f == NULL) { return NULL; } for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { /* Skip comments and blank lines */ if (line[0] == '#' || line[0] == '\n') { continue; } /* Each line consists of colon-separated * weight, mime type, and glob pattern. * We ignore the weight. */ char mime_type[200]; char filename_glob[200]; if (sscanf(line, "%*d:%199[^:]:%199s\n", mime_type, filename_glob) != 2) { /* A malformed line, perhaps? */ fprintf(stderr, "malformed globs2 line: %s\n", line); continue; } if (fnmatch(filename_glob, filename, 0) == 0) { fclose(f); return strdup(mime_type); } } fclose(f); return NULL; } char *infer_mime_type_from_name(const char *file_path) { const char *ext = get_file_extension(file_path); char *file_path_dup = strdup(file_path); const char *filename = basename(file_path_dup); char *mime_type = search_shared_mime_info_globs_for_filename(filename); if (!mime_type) { mime_type = search_mime_dot_types_for_ext(ext); } free(file_path_dup); return mime_type; } /* Returns the name of a new file */ char *dump_stdin_into_a_temp_file() { /* Pick a name for the file we'll be * creating inside that directory. We * try to preserve the original name for * the mime type inference to work. */ char *original_path = path_for_fd(STDIN_FILENO); char *name = original_path != NULL ? basename(original_path) : "stdin"; /* Create a temp directory to host out file */ const char *tmpdir = getenv("TMPDIR"); if (tmpdir == NULL) { tmpdir = "/tmp"; } size_t tmpdir_len = strlen(tmpdir); static const char dir_template[] = "wl-copy-buffer-XXXXXX"; char *path = malloc( tmpdir_len + 1 + strlen(dir_template) + 1 + strlen(name) + 1 ); memcpy(path, tmpdir, tmpdir_len); path[tmpdir_len] = '/'; memcpy(path + tmpdir_len + 1, dir_template, strlen(dir_template)); size_t prefix_len = tmpdir_len + 1 + strlen(dir_template); path[prefix_len] = 0; if (mkdtemp(path) != path) { perror("mkdtemp"); exit(1); } path[prefix_len] = '/'; strcpy(path + prefix_len + 1, name); /* Spawn cat to perform the copy */ pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid == 0) { int fd = creat(path, S_IRUSR | S_IWUSR); if (fd < 0) { perror("creat"); exit(1); } dup2(fd, STDOUT_FILENO); close(fd); signal(SIGHUP, SIG_DFL); signal(SIGPIPE, SIG_DFL); execlp("cat", "cat", NULL); perror("exec cat"); exit(1); } int wstatus; waitpid(pid, &wstatus, 0); free(original_path); if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { bail("Failed to copy the file"); } return path; }