diff --git a/docs/changelog.rst b/docs/changelog.rst index 4e4f65cf0..d3e04eab4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -87,6 +87,8 @@ Detailed list of changes - Fix a regression that broke writing to the clipboard using the OSC 5522 protocol (:iss:`7858`) +- macOS: Fix a regression in the previous release that caused kitty to fail to run after an unclean shutdown/crash (:iss:`7846`) + 0.36.2 [2024-09-06] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/launcher/main.c b/kitty/launcher/main.c index 9330ef627..f10e40366 100644 --- a/kitty/launcher/main.c +++ b/kitty/launcher/main.c @@ -217,6 +217,7 @@ run_embedded(RunData *run_data) { fail: PyConfig_Clear(&config); if (PyStatus_IsExit(status)) return status.exitcode; + single_instance_main(-1, NULL, NULL); Py_ExitStatusException(status); } @@ -462,5 +463,6 @@ int main(int argc, char *argv[], char* envp[]) { if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; } RunData run_data = {.exe = exe, .exe_dir = exe_dir, .lib_dir = lib, .argc = argc, .argv = argv, .lc_ctype = lc_ctype}; ret = run_embedded(&run_data); + single_instance_main(-1, NULL, NULL); return ret; } diff --git a/kitty/launcher/single-instance.c b/kitty/launcher/single-instance.c index b43340939..0e06a804c 100644 --- a/kitty/launcher/single-instance.c +++ b/kitty/launcher/single-instance.c @@ -29,7 +29,7 @@ #define NO_SINGLE_BYTE_CHARSETS #include "../charsets.c" -#define fail_on_errno(msg) { perror(msg); exit(1); } +#define fail_on_errno(msg) { perror(msg); do_exit(1); } void log_error(const char *fmt, ...) { @@ -39,6 +39,37 @@ log_error(const char *fmt, ...) { va_end(ar); } +typedef struct cleanup_data { + int fd1, fd2; + bool close_fd1, close_fd2; + char path1[sizeof(struct sockaddr_un) + 16], path2[sizeof(struct sockaddr_un) + 16]; +} cleanup_data; + +struct { + cleanup_data si, notify; +} cleanup_entries = {0}; + +static void +do_cleanup(cleanup_data *d) { + if (d->path1[0]) unlink(d->path1); + if (d->path2[0]) unlink(d->path2); + if (d->close_fd1) safe_close(d->fd1, __FILE__, __LINE__); + if (d->close_fd2) safe_close(d->fd2, __FILE__, __LINE__); +} + +static void +cleanup(void) { + do_cleanup(&cleanup_entries.notify); + do_cleanup(&cleanup_entries.si); +} + +static void +do_exit(int code) { + cleanup(); + exit(code); +} + + #ifndef __APPLE__ static bool is_ok_tmpdir(const char *x) { @@ -79,11 +110,10 @@ get_socket_dir(char *output, size_t output_capacity) { } static void -set_single_instance_socket(int fd, const char *socket_path) { +set_single_instance_socket(int fd) { if (listen(fd, 5) != 0) fail_on_errno("Failed to listen on single instance socket"); char buf[256]; - if (socket_path && socket_path[0]) snprintf(buf, sizeof(buf), "%d:%s", fd, socket_path); - else snprintf(buf, sizeof(buf), "%d", fd); + snprintf(buf, sizeof(buf), "%d", fd); setenv("KITTY_SI_DATA", buf, 1); } @@ -159,8 +189,9 @@ read_till_eof(FILE *f, membuf *m) { fclose(f); } + static bool -bind_unix_socket(int s, const char *basename, struct sockaddr_un *addr) { +bind_unix_socket(int s, const char *basename, struct sockaddr_un *addr, cleanup_data *cleanup) { addr->sun_family = AF_UNIX; const size_t blen = strlen(basename); // First try abstract socket @@ -173,9 +204,28 @@ bind_unix_socket(int s, const char *basename, struct sockaddr_un *addr) { const size_t dlen = strlen(addr->sun_path); if (snprintf(addr->sun_path + dlen, sizeof(addr->sun_path) - dlen, "/%s", basename) < blen + 1) { fprintf(stderr, "Socket directory has path too long for single instance socket file %s\n", addr->sun_path); - exit(1); + do_exit(1); + } + // First lock the socket file using a separate lock file + char lock_file_path[sizeof(addr->sun_path) + 16]; + snprintf(lock_file_path, sizeof(lock_file_path), "%s.lock", addr->sun_path); + int fd = safe_open(lock_file_path, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); + if (fd == -1) return false; + cleanup->close_fd2 = true; cleanup->fd2 = fd; + snprintf(cleanup->path2, sizeof(cleanup->path2), "%s", lock_file_path); + if (safe_lockf(fd, F_TLOCK, 0) != 0) { + int saved_errno = errno; + safe_close(fd, __FILE__, __LINE__); + errno = saved_errno; + if (errno == EAGAIN || errno == EACCES) errno = EADDRINUSE; // client + return false; + } + // First unlink the socket file and then try to bind it. + if (unlink(addr->sun_path) != 0 && errno != ENOENT) return false; + if (safe_bind(s, (struct sockaddr*)addr, sizeof(*addr)) > -1) { + snprintf(cleanup->path1, sizeof(cleanup->path1), "%s", addr->sun_path); + return true; } - if (safe_bind(s, (struct sockaddr*)addr, sizeof(*addr)) > -1) return true; return false; } @@ -193,6 +243,7 @@ extern char **environ; static void talk_to_instance(int s, struct sockaddr_un *server_addr, int argc, char *argv[], const CLIOptions *opts) { + cleanup_entries.si.path2[0] = 0; cleanup_entries.si.path1[0] = 0; membuf session_data = {0}; if (opts->session && opts->session[0]) { if (strcmp(opts->session, "none") == 0) { @@ -235,10 +286,11 @@ talk_to_instance(int s, struct sockaddr_un *server_addr, int argc, char *argv[], int notify_socket = -1; if (opts->wait_for_single_instance_window_close) { notify_socket = create_unix_socket(); + cleanup_entries.notify.fd1 = notify_socket; cleanup_entries.notify.close_fd1 = true; struct sockaddr_un server_addr; char addr[128]; snprintf(addr, sizeof(addr), "kitty-os-window-close-notify-%d-%d", getpid(), geteuid()); - if (!bind_unix_socket(notify_socket, addr, &server_addr)) fail_on_errno("Failed to bind notification socket"); + if (!bind_unix_socket(notify_socket, addr, &server_addr, &cleanup_entries.notify)) fail_on_errno("Failed to bind notification socket"); size_t len = strlen(server_addr.sun_path); if (len == 0) len = 1 + strlen(server_addr.sun_path +1); if (listen(notify_socket, 5) != 0) fail_on_errno("Failed to listen on notify socket"); @@ -282,15 +334,17 @@ talk_to_instance(int s, struct sockaddr_un *server_addr, int argc, char *argv[], void single_instance_main(int argc, char *argv[], const CLIOptions *opts) { + if (argc == -1) { cleanup(); return; } struct sockaddr_un server_addr; char addr_buf[sizeof(server_addr.sun_path)-1]; if (opts->instance_group) snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d-%s", geteuid(), opts->instance_group); else snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d", geteuid()); int s = create_unix_socket(); - if (!bind_unix_socket(s, addr_buf, &server_addr)) { - if (errno == EADDRINUSE) { talk_to_instance(s, &server_addr, argc, argv, opts); exit(0); } + cleanup_entries.si.fd1 = s; cleanup_entries.si.close_fd1 = true; + if (!bind_unix_socket(s, addr_buf, &server_addr, &cleanup_entries.si)) { + if (errno == EADDRINUSE) { talk_to_instance(s, &server_addr, argc, argv, opts); do_exit(0); } else fail_on_errno("Failed to bind single instance socket"); - } else set_single_instance_socket(s, server_addr.sun_path); + } else set_single_instance_socket(s); } diff --git a/kitty/main.py b/kitty/main.py index bb93cec90..71ddeafca 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -485,16 +485,7 @@ def _main() -> None: if cli_opts.single_instance: si_data = os.environ.pop('KITTY_SI_DATA', '') if si_data: - import atexit - fdnum, sep, socket_path = si_data.partition(':') - talk_fd = int(fdnum) - def cleanup_si() -> None: - with suppress(OSError): - os.close(talk_fd) - with suppress(OSError): - if sep and socket_path: - os.unlink(socket_path) - atexit.register(cleanup_si) + talk_fd = int(si_data) bad_lines: list[BadLine] = [] opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines) setup_environment(opts, cli_opts) diff --git a/kitty/safe-wrappers.h b/kitty/safe-wrappers.h index 00188aa75..604c5e2ff 100644 --- a/kitty/safe-wrappers.h +++ b/kitty/safe-wrappers.h @@ -10,6 +10,16 @@ #include #include #include +#include + +static inline int +safe_lockf(int fd, int function, off_t size) { + while (true) { + int ret = lockf(fd, function, size); + if (ret != 0 && (errno == EINTR)) continue; + return ret; + } +} static inline int safe_connect(int socket_fd, struct sockaddr *addr, socklen_t addrlen) {