From 2e0b3dc11cd827c992e620f765fd0889d4c9f519 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Tue, 3 Mar 2026 18:38:12 +0530 Subject: [PATCH 01/22] Include glib.h in backend_helper.c --- src/backend_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 37c3491..ecf5df8 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -7,7 +7,7 @@ #include #include #include - +#include #define MAX_ADDRESSES 10 From 72ad60ad2fee76b5b9a9f7386f1f5cefbc708970 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 5 Mar 2026 23:32:58 +0530 Subject: [PATCH 02/22] Add workflow_dispatch trigger to build.yml --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef83f9c..af0256e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,8 @@ on: pull_request: branches: - '**' - + workflow_dispatch: + jobs: build-linux-run-tests: @@ -42,4 +43,4 @@ jobs: - name: make run: make - name: Run Tests - run: make check || cat src/*.log \ No newline at end of file + run: make check || cat src/*.log From 7d792532c1bb48fa1dbbb37285dcc0984fd81378 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 5 Mar 2026 23:37:09 +0530 Subject: [PATCH 03/22] Add workflow_dispatch trigger to CodeQL workflow --- .github/workflows/codeql.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7fa518c..3acb04a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -7,7 +7,8 @@ on: branches: [master] schedule: - cron: '0 7 * * 6' - + workflow_dispatch: + jobs: analyze: name: Analyze From eabf8ed0d44a6309b216d74f1fd0a896f7c10d33 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Mon, 9 Mar 2026 21:24:02 +0530 Subject: [PATCH 04/22] Removed redundent glib.h --- src/backend_helper.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index b318b65..7dda878 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -7,7 +7,6 @@ #include #include #include -#include #define MAX_ADDRESSES 10 #define _CUPS_NO_DEPRECATED 1 From c6bcee12827b1262a4089383b615a7dadd914ec2 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Tue, 17 Mar 2026 19:45:23 +0000 Subject: [PATCH 05/22] Add error handling for missing HOME environment variable in print_socket and add cupsFreeOptions and g_free for failure return paths --- src/backend_helper.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/backend_helper.c b/src/backend_helper.c index 7dda878..ff8e460 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1454,6 +1454,12 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *jo // Create base directories char *home = getenv("HOME"); + if (home == NULL) { + logwarn("HOME environment variable not set\n"); + close(socket_fd); + cupsFreeOptions(num_options, options); + return; + } char base_dir[256]; char socket_dir[512]; @@ -1492,11 +1498,13 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *jo if (bind(socket_fd, (struct sockaddr *)&server_addr , sizeof(server_addr)) == -1){ perror("Bind failed"); close(socket_fd); + cupsFreeOptions(num_options, options); return; } if(listen(socket_fd, 1) == -1) { perror("listen failed"); close(socket_fd); + cupsFreeOptions(num_options, options); return; } @@ -1505,6 +1513,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *jo num_options, options, 1) != HTTP_STATUS_CONTINUE) { logwarn("could not start document: %s\n", cupsLastErrorString()); close(socket_fd); + cupsFreeOptions(num_options, options); return; } @@ -1520,6 +1529,8 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *jo if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) { perror("Error creating thread"); close(socket_fd); + cupsFreeOptions(num_options, options); + g_free(thread_data); } else { // Detach the thread to allow it to run independently pthread_detach(thread); From ac98a12ed0862f8930a84f4d0f961ec518cc5566 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 19 Mar 2026 14:37:28 +0000 Subject: [PATCH 06/22] Add error message handling in print_socket and update print_backend_cups for better job creation feedback --- src/backend_helper.c | 6 ++++-- src/print_backend_cups.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 607c294..13dd673 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1413,8 +1413,9 @@ const char *get_printer_state(PrinterCUPS *p) } - -void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title) +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, + char *job_id_str, char *socket_path, const char *title, + char *error_msg, int error_msg_len) { ensure_printer_connection(p); int num_options = 0; @@ -1479,6 +1480,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *jo if(cupsCreateDestJob(p->http, p->dest, p->dinfo, &job_id, title, num_options, options) != IPP_STATUS_OK) { logwarn("job not created: %s\n" , cupsLastErrorString()); + snprintf(error_msg, error_msg_len, "job not created: %s", cupsLastErrorString()); close(socket_fd); return; } diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 7d7c8b3..30d70e9 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -532,7 +532,7 @@ static gboolean on_handle_print_socket(PrintBackend *interface, g_dbus_method_invocation_return_error(invocation, G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to create print job"); + "%s", error_msg[0] ? error_msg : "Failed to create print job"); return TRUE; } From 70541c467de77a58acd482d4cccd68e6f8320a7f Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 19 Mar 2026 14:48:24 +0000 Subject: [PATCH 07/22] Fix print_socket call --- src/print_backend_cups.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 30d70e9..708a31c 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -523,7 +523,7 @@ static gboolean on_handle_print_socket(PrintBackend *interface, jobid[0] = '\0'; // prevent garbage being sent over D-Bus on failure socket[0] = '\0'; // used below to detect if print_socket succeeded - print_socket(p, num_settings, settings, jobid, socket, title); + print_socket(p, num_settings, settings, jobid, socket, title, error_msg, sizeof(error_msg)); /* If socket_path is empty, print_socket failed before creating the job. * Return a D-Bus error so the frontend doesn't hang waiting for a reply. */ From 11325013859f3e31c938110aa408eef674785d79 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 19 Mar 2026 14:55:27 +0000 Subject: [PATCH 08/22] Add error message handling to print_socket function with proper declaration --- src/backend_helper.c | 2 +- src/backend_helper.h | 5 +++-- src/print_backend_cups.c | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 13dd673..740655b 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1420,7 +1420,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, ensure_printer_connection(p); int num_options = 0; cups_option_t *options; - + error_msg[0] = '\0'; GVariantIter *iter; g_variant_get(settings, "a(ss)", &iter); diff --git a/src/backend_helper.h b/src/backend_helper.h index de32060..473ac90 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -241,8 +241,9 @@ int get_all_options(PrinterCUPS *p, Option **options); int get_all_media(PrinterCUPS *p, Media **medias); int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option **options, int count); -void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title); - +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, + char *job_id_str, char *socket_path, const char *title, + char *error_msg, int error_msg_len); gboolean checkRemote(const char *uri); char *extractHostFromURI(const char *uri); diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 708a31c..8a38906 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -520,6 +520,7 @@ static gboolean on_handle_print_socket(PrintBackend *interface, // Call the renamed function char jobid[JOB_ID_BUFLEN]; char socket[SOCKET_PATH_BUFLEN]; + char error_msg[256] = ""; jobid[0] = '\0'; // prevent garbage being sent over D-Bus on failure socket[0] = '\0'; // used below to detect if print_socket succeeded From f908daab342cd101d6032eb22d87afacb51cef3d Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sun, 22 Mar 2026 22:11:27 +0530 Subject: [PATCH 09/22] updated print data thread structure --- src/backend_helper.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend_helper.h b/src/backend_helper.h index 473ac90..d866e33 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -107,11 +107,12 @@ typedef struct _Media } Media; typedef struct _PrintDataThreadData { - PrinterCUPS *printer; - int num_options; + cups_dest_t *dest; + int job_id; + int num_options; cups_option_t *options; - int socket_fd; - struct sockaddr_un server_addr; + int socket_fd; + char title[256]; } PrintDataThreadData; typedef struct _AddressList { @@ -241,9 +242,8 @@ int get_all_options(PrinterCUPS *p, Option **options); int get_all_media(PrinterCUPS *p, Media **medias); int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option **options, int count); -void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, - char *job_id_str, char *socket_path, const char *title, - char *error_msg, int error_msg_len); +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title); + gboolean checkRemote(const char *uri); char *extractHostFromURI(const char *uri); From ba7aee5054281e844f21074a03babe8feafb39ec Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sun, 22 Mar 2026 23:28:39 +0530 Subject: [PATCH 10/22] fix auth failure when printing to authenticated CUPS policy cupsCreateDestJob() was called with a manually managed http_t* from cupsConnectDest(). This creates a TCP connection to the printer queue where Local peer credential auth is impossible and Kerberos is unavailable, causing a 401 Unauthorized when the CUPS policy requires authentication. Fix by using CUPS_HTTP_DEFAULT for all CUPS job I/O. This causes the CUPS library to connect to cupsd via the Unix domain socket, where the kernel automatically vouches for the caller's UID via peer credentials (SO_PEERCRED), satisfying the authenticated policy without a password. Also fix a thread safety bug: cupsStartDestDocument was called on the main thread but cupsWriteRequestData and cupsFinishDestDocument were called on the worker thread using the same http_t*. Since CUPS_HTTP_DEFAULT is per-thread (stored in _cups_globals_t), all three calls must be on the same thread. Move cupsStartDestDocument and cupsCopyDestInfo into the worker thread alongside the data transfer calls. --- src/backend_helper.c | 89 +++++++++++++++++++++++++++----------------- src/backend_helper.h | 5 ++- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 740655b..c995c1b 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1477,8 +1477,8 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, // Create the CUPS JOB int job_id = 0; - if(cupsCreateDestJob(p->http, p->dest, p->dinfo, &job_id, title, - num_options, options) != IPP_STATUS_OK) { + if(cupsCreateDestJob(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, &job_id, title, + num_options, options) != IPP_STATUS_OK) { logwarn("job not created: %s\n" , cupsLastErrorString()); snprintf(error_msg, error_msg_len, "job not created: %s", cupsLastErrorString()); close(socket_fd); @@ -1510,21 +1510,14 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, return; } - // start cups document - if(cupsStartDestDocument(p->http, p->dest, p->dinfo, job_id, title, CUPS_FORMAT_AUTO, - num_options, options, 1) != HTTP_STATUS_CONTINUE) { - logwarn("could not start document: %s\n", cupsLastErrorString()); - close(socket_fd); - cupsFreeOptions(num_options, options); - return; - } - - // Create a struct to pass data to the thread + // Create a struct to pass data to the thread PrintDataThreadData *thread_data = g_malloc(sizeof(PrintDataThreadData)); - thread_data->printer = p; + cupsCopyDest(p->dest, 0, &thread_data->dest); + thread_data->job_id = job_id; thread_data->num_options = num_options; - thread_data->options = options; - thread_data->socket_fd = socket_fd; + thread_data->options = options; + thread_data->socket_fd = socket_fd; + snprintf(thread_data->title, sizeof(thread_data->title), "%s", title); // Create a thread for handling data transfer to CUPS pthread_t thread; @@ -1532,6 +1525,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, logwarn("Error creating thread"); close(socket_fd); cupsFreeOptions(num_options, options); + cupsFreeDests(1, thread_data->dest); g_free(thread_data); } else { // Detach the thread to allow it to run independently @@ -1543,40 +1537,67 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, static void *print_data_thread(void *data) { PrintDataThreadData *thread_data = (PrintDataThreadData *)data; + char *buffer = g_malloc(65536); + + // Use CUPS_HTTP_DEFAULT here so CUPS uses this thread's own connection + // to cupsd via the Unix domain socket. Peer credential auth (Local) + // then succeeds automatically with our UID. Using p->http here would + // fail because that is a TCP connection where peer auth is impossible. + cups_dinfo_t *dinfo = cupsCopyDestInfo(CUPS_HTTP_DEFAULT, thread_data->dest); + if (dinfo == NULL) { + logerror("print_data_thread: could not get dest info: %s\n", + cupsLastErrorString()); + goto cleanup; + } + + // cupsStartDestDocument begins a chunked HTTP POST that must be + // continued by cupsWriteRequestData and cupsFinishDestDocument on + // this same thread using the same CUPS_HTTP_DEFAULT connection. + if (cupsStartDestDocument(CUPS_HTTP_DEFAULT, + thread_data->dest, dinfo, + thread_data->job_id, + thread_data->title, + CUPS_FORMAT_AUTO, + thread_data->num_options, + thread_data->options, + 1) != HTTP_STATUS_CONTINUE) { + logerror("print_data_thread: could not start document: %s\n", + cupsLastErrorString()); + cupsFreeDestInfo(dinfo); + goto cleanup; + } - // Allocate dynamic memory for the buffer within the thread - char *buffer = g_malloc(1024); - - //Accept incoming connections int client_fd = accept(thread_data->socket_fd, NULL, NULL); if (client_fd == -1) { - logwarn("accept failed"); - close(thread_data->socket_fd); - g_free(thread_data); - return NULL; + logwarn("print_data_thread: accept failed\n"); + cupsFreeDestInfo(dinfo); + goto cleanup; } - // Placeholder logic for reading data from the socket - ssize_t bytesRead; - while ((bytesRead = read(client_fd, buffer, 1024)) > 0) { - // Send data to CUPS using cupsWriteRequestData - http_status_t http_status = cupsWriteRequestData(thread_data->printer->http, buffer, bytesRead); - if (http_status != HTTP_STATUS_CONTINUE) { - logerror("Error writing print data to server.\n"); + ssize_t bytes_read; + while ((bytes_read = read(client_fd, buffer, 65536)) > 0) { + if (cupsWriteRequestData(CUPS_HTTP_DEFAULT, buffer, bytes_read) + != HTTP_STATUS_CONTINUE) { + logerror("print_data_thread: error writing print data\n"); break; } } + close(client_fd); - // Cleanup and free resources - close(thread_data->socket_fd); - if (cupsFinishDestDocument(thread_data->printer->http, thread_data->printer->dest, thread_data->printer->dinfo) == IPP_STATUS_OK) + if (cupsFinishDestDocument(CUPS_HTTP_DEFAULT, + thread_data->dest, dinfo) == IPP_STATUS_OK) logdebug("Document send succeeded.\n"); else logerror("Document send failed: %s\n", cupsLastErrorString()); + + cupsFreeDestInfo(dinfo); + +cleanup: + close(thread_data->socket_fd); cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); g_free(thread_data); g_free(buffer); - return NULL; } diff --git a/src/backend_helper.h b/src/backend_helper.h index d866e33..a45326f 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -242,8 +242,9 @@ int get_all_options(PrinterCUPS *p, Option **options); int get_all_media(PrinterCUPS *p, Media **medias); int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option **options, int count); -void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title); - +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, + char *job_id_str, char *socket_path, const char *title, + char *error_msg, int error_msg_len); gboolean checkRemote(const char *uri); char *extractHostFromURI(const char *uri); From d8296edca4374df7871afc8ef297a114e9052051 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Sun, 22 Mar 2026 23:35:41 +0530 Subject: [PATCH 11/22] refactor print_data_thread --- src/backend_helper.c | 51 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index c995c1b..9123f11 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1539,20 +1539,34 @@ static void *print_data_thread(void *data) { PrintDataThreadData *thread_data = (PrintDataThreadData *)data; char *buffer = g_malloc(65536); - // Use CUPS_HTTP_DEFAULT here so CUPS uses this thread's own connection - // to cupsd via the Unix domain socket. Peer credential auth (Local) - // then succeeds automatically with our UID. Using p->http here would - // fail because that is a TCP connection where peer auth is impossible. + /* + * Use CUPS_HTTP_DEFAULT so the CUPS library connects to cupsd via the + * Unix domain socket on this thread. The kernel then automatically + * vouches for our UID via peer credentials (SO_PEERCRED), satisfying + * the authenticated CUPS policy without needing a password or Kerberos + * ticket. Using p->http here would fail because that is a TCP connection + * where peer credential auth is impossible. + */ cups_dinfo_t *dinfo = cupsCopyDestInfo(CUPS_HTTP_DEFAULT, thread_data->dest); if (dinfo == NULL) { logerror("print_data_thread: could not get dest info: %s\n", cupsLastErrorString()); - goto cleanup; + close(thread_data->socket_fd); + cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); + g_free(buffer); + g_free(thread_data); + return NULL; } - // cupsStartDestDocument begins a chunked HTTP POST that must be - // continued by cupsWriteRequestData and cupsFinishDestDocument on - // this same thread using the same CUPS_HTTP_DEFAULT connection. + /* + * cupsStartDestDocument begins a chunked HTTP POST on this thread's + * CUPS_HTTP_DEFAULT connection. cupsWriteRequestData and + * cupsFinishDestDocument must be called on the same thread to continue + * and finish that same HTTP POST — CUPS_HTTP_DEFAULT is per-thread + * (stored in _cups_globals_t), so mixing threads here would use a + * different connection and corrupt the stream. + */ if (cupsStartDestDocument(CUPS_HTTP_DEFAULT, thread_data->dest, dinfo, thread_data->job_id, @@ -1564,16 +1578,28 @@ static void *print_data_thread(void *data) { logerror("print_data_thread: could not start document: %s\n", cupsLastErrorString()); cupsFreeDestInfo(dinfo); - goto cleanup; + close(thread_data->socket_fd); + cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); + g_free(buffer); + g_free(thread_data); + return NULL; } + /* Wait for the frontend to connect and start sending print data */ int client_fd = accept(thread_data->socket_fd, NULL, NULL); if (client_fd == -1) { logwarn("print_data_thread: accept failed\n"); cupsFreeDestInfo(dinfo); - goto cleanup; + close(thread_data->socket_fd); + cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); + g_free(buffer); + g_free(thread_data); + return NULL; } + /* Read print data from the socket and forward it to CUPS */ ssize_t bytes_read; while ((bytes_read = read(client_fd, buffer, 65536)) > 0) { if (cupsWriteRequestData(CUPS_HTTP_DEFAULT, buffer, bytes_read) @@ -1584,6 +1610,7 @@ static void *print_data_thread(void *data) { } close(client_fd); + /* Finalise the document and report the result */ if (cupsFinishDestDocument(CUPS_HTTP_DEFAULT, thread_data->dest, dinfo) == IPP_STATUS_OK) logdebug("Document send succeeded.\n"); @@ -1591,13 +1618,11 @@ static void *print_data_thread(void *data) { logerror("Document send failed: %s\n", cupsLastErrorString()); cupsFreeDestInfo(dinfo); - -cleanup: close(thread_data->socket_fd); cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); - g_free(thread_data); g_free(buffer); + g_free(thread_data); return NULL; } From c978f13d022eff265bc48cc4ca1f7a270bdadbc3 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Thu, 26 Mar 2026 18:35:25 +0530 Subject: [PATCH 12/22] stop manually managing http_t, instead use CUPS_HTTP_DEFAULT everywhere Remove p->http from PrinterCUPS and replace ensure_printer_connection() with ensure_dest_info() which only manages p->dinfo. All CUPS API calls now use CUPS_HTTP_DEFAULT, letting the library manage its own per-thread Unix domain socket connection to cupsd. Also fix a memory leak in cups_get_Resolution which never freed its dinfo or closed its http_t. --- src/backend_helper.c | 72 +++++++++++++++++++++----------------------- src/backend_helper.h | 7 ++--- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 9123f11..853a124 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1,4 +1,5 @@ #include "backend_helper.h" +#include #include #include #include @@ -580,7 +581,6 @@ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest) } p->dest = dest_copy; p->name = get_printer_name_for_cups_dest(dest_copy); - p->http = NULL; p->dinfo = NULL; p->stream_socket_path = NULL; @@ -598,27 +598,26 @@ void free_PrinterCUPS(PrinterCUPS *p) } } -gboolean ensure_printer_connection(PrinterCUPS *p) +gboolean ensure_dest_info(PrinterCUPS *p) { - if (p->http) + if (p->dinfo) return TRUE; - int temp = FALSE; - if (cups_is_temporary(p->dest)) temp = TRUE; - - p->http = cupsConnectDest(p->dest, CUPS_DEST_FLAGS_NONE, 300, NULL, NULL, 0, NULL, NULL); - if (p->http == NULL) - return FALSE; - - // update dest after temporary CUPS queue has been created - if (temp) + /* + * For temporary destinations (discovered but not yet set up as a local + * queue), we need to resolve them first so that printer-uri-supported + * is populated and cupsCopyDestInfo can query the printer capabilities. + */ + if (cups_is_temporary(p->dest)) { - cups_dest_t *new_dest = cupsGetNamedDest(p->http, p->name, NULL); + cups_dest_t *new_dest = cupsGetNamedDest(CUPS_HTTP_DEFAULT, p->name, NULL); + if (new_dest == NULL) + return FALSE; cupsFreeDests(1, p->dest); p->dest = new_dest; } - p->dinfo = cupsCopyDestInfo(p->http, p->dest); + p->dinfo = cupsCopyDestInfo(CUPS_HTTP_DEFAULT, p->dest); if (p->dinfo == NULL) return FALSE; @@ -628,9 +627,9 @@ gboolean ensure_printer_connection(PrinterCUPS *p) int get_supported(PrinterCUPS *p, char ***supported_values, const char *option_name) { char **values; - ensure_printer_connection(p); + ensure_dest_info(p); ipp_attribute_t *attrs = - cupsFindDestSupported(p->http, p->dest, p->dinfo, option_name); + cupsFindDestSupported(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, option_name); int i, count = ippGetCount(attrs); if (!count) { @@ -661,10 +660,10 @@ char *get_orientation_default(PrinterCUPS *p) return g_strdup(ippEnumString(CUPS_ORIENTATION, atoi(def_value))); } } - ensure_printer_connection(p); + ensure_dest_info(p); ipp_attribute_t *attr = NULL; - attr = cupsFindDestDefault(p->http, p->dest, p->dinfo, CUPS_ORIENTATION); + attr = cupsFindDestDefault(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, CUPS_ORIENTATION); if (!attr) return g_strdup("NA"); @@ -686,8 +685,8 @@ char *get_default(PrinterCUPS *p, char *option_name) return get_orientation_default(p); /** Generic cases next **/ - ensure_printer_connection(p); - ipp_attribute_t *def_attr = cupsFindDestDefault(p->http, p->dest, p->dinfo, option_name); + ensure_dest_info(p); + ipp_attribute_t *def_attr = cupsFindDestDefault(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, option_name); const char *def_value = cupsGetOption(option_name, p->dest->num_options, p->dest->options); /** First check the option is already there in p->dest->options **/ @@ -799,7 +798,7 @@ GVariant *pack_media(const Media *media) } int get_all_options(PrinterCUPS *p, Option **options) { - ensure_printer_connection(p); + ensure_dest_info(p); char **option_names; int num_options = get_job_creation_attributes(p, &option_names); /** number of options to be returned**/ @@ -843,7 +842,7 @@ int get_all_options(PrinterCUPS *p, Option **options) continue; opts[optsIndex].option_name = option_names[i]; - vals = cupsFindDestSupported(p->http, p->dest, p->dinfo, option_names[i]); + vals = cupsFindDestSupported(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, option_names[i]); if (vals) opts[optsIndex].num_supported = ippGetCount(vals); else @@ -1151,7 +1150,7 @@ int get_all_options(PrinterCUPS *p, Option **options) } int get_all_media(PrinterCUPS *p, Media **medias) { - ensure_printer_connection(p); + ensure_dest_info(p); ipp_t *request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); const char *uri = cupsGetOption("printer-uri-supported", p->dest->num_options, @@ -1163,7 +1162,7 @@ int get_all_media(PrinterCUPS *p, Media **medias) "requested-attributes", 1, NULL, requested_attributes); - ipp_t *response = cupsDoRequest(p->http, request, "/"); + ipp_t *response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ @@ -1313,7 +1312,7 @@ int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option } /** Add custom_min and custom_max media if they exist **/ - vals = cupsFindDestSupported(p->http, p->dest, p->dinfo, "media"); + vals = cupsFindDestSupported(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, "media"); if (vals) num_media = ippGetCount(vals); else @@ -1342,11 +1341,11 @@ int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option char def[16]; char *attrs[] = {"media-left-margin", "media-bottom-margin", "media-top-margin", "media-right-margin"}; - default_val = cupsFindDestDefault(p->http, p->dest, p->dinfo, "media-col"); + default_val = cupsFindDestDefault(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, "media-col"); for (i = 0; i < 4; i++) // for each attr in attrs { - vals = cupsFindDestSupported(p->http, p->dest, p->dinfo, attrs[i]); + vals = cupsFindDestSupported(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, attrs[i]); opts[optsIndex].option_name = g_strdup(attrs[i]); if (vals) opts[optsIndex].num_supported = ippGetCount(vals); @@ -1382,7 +1381,7 @@ int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option const char *get_printer_state(PrinterCUPS *p) { const char *str = NULL; - ensure_printer_connection(p); + ensure_dest_info(p); ipp_t *request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); const char *uri = cupsGetOption("printer-uri-supported", p->dest->num_options, @@ -1394,7 +1393,7 @@ const char *get_printer_state(PrinterCUPS *p) "requested-attributes", 1, NULL, requested_attributes); - ipp_t *response = cupsDoRequest(p->http, request, "/"); + ipp_t *response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ @@ -1417,7 +1416,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title, char *error_msg, int error_msg_len) { - ensure_printer_connection(p); + ensure_dest_info(p); int num_options = 0; cups_option_t *options; error_msg[0] = '\0'; @@ -1628,9 +1627,9 @@ static void *print_data_thread(void *data) { void printAllJobs(PrinterCUPS *p) { - ensure_printer_connection(p); + ensure_dest_info(p); cups_job_t *jobs; - int num_jobs = cupsGetJobs2(p->http, &jobs, p->name, 1, CUPS_WHICHJOBS_ALL); + int num_jobs = cupsGetJobs2(CUPS_HTTP_DEFAULT, &jobs, p->name, 1, CUPS_WHICHJOBS_ALL); for (int i = 0; i < num_jobs; i++) { print_job(&jobs[i]); @@ -2085,7 +2084,7 @@ char *get_option_translation(PrinterCUPS *p, ipp_t *request, *response; cups_array_t *opts_catalog, *printer_opts_catalog; - ensure_printer_connection(p); + ensure_dest_info(p); request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); uri = cupsGetOption("printer-uri-supported", p->dest->num_options, @@ -2094,8 +2093,7 @@ char *get_option_translation(PrinterCUPS *p, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, req_attrs); - response = cupsDoRequest(p->http, request, "/"); - if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) + response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ logerror("Request failed: %s\n", cupsLastErrorString()); @@ -2131,7 +2129,7 @@ char *get_choice_translation(PrinterCUPS *p, ipp_t *request, *response; cups_array_t *opts_catalog, *printer_opts_catalog; - ensure_printer_connection(p); + ensure_dest_info(p); request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES); uri = cupsGetOption("printer-uri-supported", p->dest->num_options, @@ -2140,7 +2138,7 @@ char *get_choice_translation(PrinterCUPS *p, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, req_attrs); - response = cupsDoRequest(p->http, request, "/"); + response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ diff --git a/src/backend_helper.h b/src/backend_helper.h index a45326f..58adb0a 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -41,7 +41,7 @@ typedef struct _PrinterCUPS { char *name; cups_dest_t *dest; - http_t *http; + // http_t *http; cups_dinfo_t *dinfo; char *stream_socket_path; } PrinterCUPS; @@ -225,9 +225,8 @@ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest); /** Free up the memory used by the struct **/ void free_PrinterCUPS(PrinterCUPS *); -/** Ensure that we have a connection the server**/ -gboolean ensure_printer_connection(PrinterCUPS *p); - +/** Ensure that we have destination info the server**/ +gboolean ensure_dest_info(PrinterCUPS *p); /** * Get state of the printer * state is one of the following {"idle" , "processing" , "stopped"} From b4e00862a830aafc587aa8ddfac7b9a0102646a8 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Thu, 26 Mar 2026 19:30:02 +0530 Subject: [PATCH 13/22] Removed commented out line --- src/backend_helper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend_helper.h b/src/backend_helper.h index 58adb0a..f060041 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -41,7 +41,6 @@ typedef struct _PrinterCUPS { char *name; cups_dest_t *dest; - // http_t *http; cups_dinfo_t *dinfo; char *stream_socket_path; } PrinterCUPS; From cd1a3e2c449b4973a7c3eb38d4cd345372f47ab3 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Thu, 26 Mar 2026 21:35:53 +0530 Subject: [PATCH 14/22] Fix typo --- src/backend_helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 853a124..7707b93 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -2093,7 +2093,8 @@ char *get_option_translation(PrinterCUPS *p, "printer-uri", NULL, uri); ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", 1, NULL, req_attrs); - response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) + response = cupsDoRequest(CUPS_HTTP_DEFAULT, request, "/"); + if (cupsLastError() >= IPP_STATUS_ERROR_BAD_REQUEST) { /* request failed */ logerror("Request failed: %s\n", cupsLastErrorString()); From 4be9579ed141fc4073e5777e1f15affdfd4602f8 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 11:10:53 +0530 Subject: [PATCH 15/22] updated Print data thread with use_fd flag and declared print_fd --- src/backend_helper.c | 43 +++++++++++++++++++++++++++++-------------- src/backend_helper.h | 21 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 853a124..f64f7db 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1515,7 +1515,8 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, thread_data->job_id = job_id; thread_data->num_options = num_options; thread_data->options = options; - thread_data->socket_fd = socket_fd; + thread_data->use_fd = 0; + thread_data->socket_fd = socket_fd; /* legacy: thread must call accept() */ snprintf(thread_data->title, sizeof(thread_data->title), "%s", title); // Create a thread for handling data transfer to CUPS @@ -1585,19 +1586,29 @@ static void *print_data_thread(void *data) { return NULL; } - /* Wait for the frontend to connect and start sending print data */ - int client_fd = accept(thread_data->socket_fd, NULL, NULL); - if (client_fd == -1) { - logwarn("print_data_thread: accept failed\n"); - cupsFreeDestInfo(dinfo); - close(thread_data->socket_fd); - cupsFreeOptions(thread_data->num_options, thread_data->options); - cupsFreeDests(1, thread_data->dest); - g_free(buffer); - g_free(thread_data); - return NULL; - } + /* Wait for the frontend to connect and start sending print data . + * Get the connected client fd. + * FD path: socket_fd is already the connected peer , use directly. + * Legacy path: socket_fd is a listening socket , call accept(). + */ + int client_fd; + if (thread_data->use_fd){ + client_fd = thread_data->socket_fd; + } else { + client_fd = accept(thread_data->socket_fd, NULL, NULL); + if (client_fd == -1) { + logwarn("print_data_thread: accept failed: %s\n", + strerror(errno)); + cupsFreeDestInfo(dinfo); + close(thread_data->socket_fd); + cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); + g_free(buffer); + g_free(thread_data); + return NULL; + } + } /* Read print data from the socket and forward it to CUPS */ ssize_t bytes_read; while ((bytes_read = read(client_fd, buffer, 65536)) > 0) { @@ -1617,7 +1628,11 @@ static void *print_data_thread(void *data) { logerror("Document send failed: %s\n", cupsLastErrorString()); cupsFreeDestInfo(dinfo); - close(thread_data->socket_fd); + /* when using print_socket method */ + if (!thread_data->use_fd){ + close(thread_data->socket_fd); + } + cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); diff --git a/src/backend_helper.h b/src/backend_helper.h index f060041..f8f176d 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -111,6 +111,14 @@ typedef struct _PrintDataThreadData { int num_options; cups_option_t *options; int socket_fd; + /* + * use_fd == 0 Legacy socket-file path: socket_fd is a listening + * socket; the thread must call accept() to get the + * connected client fd. + * use_fd == 1 FD-passing path: socket_fd is already the connected + * peer end from socketpair(); used directly, no accept(). + */ + int use_fd; char title[256]; } PrintDataThreadData; @@ -244,6 +252,19 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title, char *error_msg, int error_msg_len); +/** + * FD-passing variant of print_socket(). + * + * Creates a socketpair(), starts the print-data thread on one end, and + * returns the other end in *peer_fd. The D-Bus handler passes *peer_fd + * to the frontend via D-Bus UnixFD. + * + * On failure *peer_fd is -1 and error_msg is populated. + */ +void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, + char *job_id_str, int *peer_fd, const char *title, + char *error_msg, int error_msg_len); + gboolean checkRemote(const char *uri); char *extractHostFromURI(const char *uri); /** From c940d90cd1b12efa765c5417cfca99493d6c3479 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 11:18:21 +0530 Subject: [PATCH 16/22] Add print_fd function --- src/backend_helper.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/backend_helper.c b/src/backend_helper.c index 11cc45a..1deca26 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1535,6 +1535,87 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, } +void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, + char *job_id_str, int *peer_fd, const char *title, + char *error_msg, int error_msg_len) +{ + ensure_dest_info(p); + int num_options = 0; + cups_option_t *options = NULL; + error_msg[0] = '\0'; + *peer_fd = -1; + + GVariantIter *iter; + g_variant_get(settings, "a(ss)", &iter); + + char *option_name, *option_value; + for (int i = 0; i < num_settings; i++) + { + g_variant_iter_loop(iter, "(ss)", &option_name, &option_value); + logdebug(" %s : %s\n", option_name, option_value); + num_options = cupsAddOption(option_name, option_value, + num_options, &options); + } + + /* Create the CUPS job to obtain a job ID */ + int job_id = 0; + if (cupsCreateDestJob(CUPS_HTTP_DEFAULT, p->dest, p->dinfo, + &job_id, title, num_options, options) + != IPP_STATUS_OK) + { + logwarn("print_fd: job not created: %s\n", cupsLastErrorString()); + snprintf(error_msg, error_msg_len, + "job not created: %s", cupsLastErrorString()); + cupsFreeOptions(num_options, options); + return; + } + + snprintf(job_id_str, JOB_ID_BUFLEN, "%d", job_id); + + /* + * Create a connected socket pair. + * + * sv[0] — backend data thread reads print data from here. + * sv[1] — returned as *peer_fd; D-Bus handler passes this to + * the frontend via UnixFD. Frontend writes print data + * into it and closes it when done. + */ + int sv[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) + { + logwarn("print_fd: socketpair failed: %s\n", strerror(errno)); + snprintf(error_msg, error_msg_len, + "socketpair failed: %s", strerror(errno)); + cupsFreeOptions(num_options, options); + return; + } + + PrintDataThreadData *thread_data = g_malloc(sizeof(PrintDataThreadData)); + cupsCopyDest(p->dest, 0, &thread_data->dest); + thread_data->job_id = job_id; + thread_data->num_options = num_options; + thread_data->options = options; + thread_data->socket_fd = sv[0]; /* backend reads from here */ + thread_data->use_fd = 1; /* already connected, skip accept() */ + snprintf(thread_data->title, sizeof(thread_data->title), "%s", title); + + pthread_t thread; + if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) + { + logwarn("print_fd: pthread_create failed\n"); + close(sv[0]); + close(sv[1]); + cupsFreeOptions(num_options, options); + cupsFreeDests(1, thread_data->dest); + g_free(thread_data); + snprintf(error_msg, error_msg_len, "failed to create print thread"); + return; + } + + pthread_detach(thread); + *peer_fd = sv[1]; /* frontend writes print data into this */ +} + static void *print_data_thread(void *data) { PrintDataThreadData *thread_data = (PrintDataThreadData *)data; char *buffer = g_malloc(65536); From 57ce7f5caf39b9e56b2a290c090da42f2beb4178 Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Fri, 3 Apr 2026 11:24:34 +0530 Subject: [PATCH 17/22] Add on_handle_print_fd --- src/print_backend_cups.c | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 8a38906..20e46f8 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -2,6 +2,7 @@ #include #include #include +#include #include @@ -543,6 +544,61 @@ static gboolean on_handle_print_socket(PrintBackend *interface, return TRUE; } +static gboolean on_handle_print_fd(PrintBackend *interface, + GDBusMethodInvocation *invocation, + const gchar *printer_id, + int num_settings, + GVariant *settings, + const gchar *title, + gpointer user_data) +{ + const char *dialog_name = + g_dbus_method_invocation_get_sender(invocation); + + PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_id); + if (p == NULL) + { + g_dbus_method_invocation_return_error( + invocation, G_IO_ERROR, G_IO_ERROR_FAILED, + "Printer not found: %s", printer_id); + return TRUE; + } + + char jobid[JOB_ID_BUFLEN]; + char error_msg[256] = ""; + int peer_fd = -1; + jobid[0] = '\0'; + + print_fd(p, num_settings, settings, jobid, &peer_fd, + title, error_msg, sizeof(error_msg)); + + if (peer_fd == -1) + { + logwarn("on_handle_print_fd: failed for printer %s: %s\n", + printer_id, + error_msg[0] ? error_msg : "unknown error"); + g_dbus_method_invocation_return_error( + invocation, G_IO_ERROR, G_IO_ERROR_FAILED, + "%s", error_msg[0] ? error_msg : "Failed to create print job"); + return TRUE; + } + + /* + * Return peer_fd to the frontend via D-Bus UnixFD. + */ + GUnixFDList *fd_list = g_unix_fd_list_new_from_array(&peer_fd, 1); + + g_dbus_method_invocation_return_value_with_unix_fd_list( + invocation, + g_variant_new("(sh)", jobid, 0), + fd_list); + + g_object_unref(fd_list); + close(peer_fd); + + return TRUE; +} + static gboolean on_handle_get_all_options(PrintBackend *interface, GDBusMethodInvocation *invocation, const gchar *printer_name, @@ -646,6 +702,10 @@ void connect_to_signals() "handle-print-socket", //signal name G_CALLBACK(on_handle_print_socket), //callback NULL); + g_signal_connect(skeleton, //instance + "handle-print-fd", //signal name + G_CALLBACK(on_handle_print_fd), //callback + NULL); g_signal_connect(skeleton, //instance "handle-get-printer-state", //signal name G_CALLBACK(on_handle_get_printer_state), //callback From 58c35889e3ab82985e030443ac79ca796a821b2b Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Tue, 14 Apr 2026 18:57:31 +0530 Subject: [PATCH 18/22] print_data_thread deadlock resolution and add conter variable for active print thread count --- src/backend_helper.c | 63 +++++++++++++++++++++++++++----------------- src/backend_helper.h | 7 +++++ 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 1deca26..ee10fa8 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -43,6 +43,9 @@ BackendObj *get_new_BackendObj() b->num_frontends = 0; b->obj_path = NULL; b->default_printer = NULL; + g_mutex_init(&b->print_threads_mutex); + g_cond_init(&b->print_threads_cond); + b->active_print_threads = 0; return b; } @@ -1640,6 +1643,30 @@ static void *print_data_thread(void *data) { return NULL; } + /* Wait for the frontend to connect and start sending print data . + * Get the connected client fd. + * FD path: socket_fd is already the connected peer , use directly. + * Legacy path: socket_fd is a listening socket , call accept(). + */ + + int client_fd; + if (thread_data->use_fd){ + client_fd = thread_data->socket_fd; + } else { + client_fd = accept(thread_data->socket_fd, NULL, NULL); + if (client_fd == -1) { + logwarn("print_data_thread: accept failed: %s\n", + strerror(errno)); + cupsFreeDestInfo(dinfo); + close(thread_data->socket_fd); + cupsFreeOptions(thread_data->num_options, thread_data->options); + cupsFreeDests(1, thread_data->dest); + g_free(buffer); + g_free(thread_data); + return NULL; + } + } + /* * cupsStartDestDocument begins a chunked HTTP POST on this thread's * CUPS_HTTP_DEFAULT connection. cupsWriteRequestData and @@ -1647,6 +1674,15 @@ static void *print_data_thread(void *data) { * and finish that same HTTP POST — CUPS_HTTP_DEFAULT is per-thread * (stored in _cups_globals_t), so mixing threads here would use a * different connection and corrupt the stream. + * + * We start the document only after obtaining the client fd (after + * accept() for the legacy path, or directly for the FD path). For + * the FD path this is critical: the frontend receives sv[1] over + * D-Bus asynchronously and may not have started writing yet. Opening + * the CUPS HTTP POST before any data is available risks a CUPS + * server-side timeout ("No file in print request"). By deferring + * cupsStartDestDocument until the client fd is ready, the first + * cupsWriteRequestData call follows immediately with no gap. */ if (cupsStartDestDocument(CUPS_HTTP_DEFAULT, thread_data->dest, dinfo, @@ -1659,7 +1695,9 @@ static void *print_data_thread(void *data) { logerror("print_data_thread: could not start document: %s\n", cupsLastErrorString()); cupsFreeDestInfo(dinfo); - close(thread_data->socket_fd); + close(client_fd); + if (!thread_data->use_fd) + close(thread_data->socket_fd); cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); @@ -1667,29 +1705,6 @@ static void *print_data_thread(void *data) { return NULL; } - /* Wait for the frontend to connect and start sending print data . - * Get the connected client fd. - * FD path: socket_fd is already the connected peer , use directly. - * Legacy path: socket_fd is a listening socket , call accept(). - */ - - int client_fd; - if (thread_data->use_fd){ - client_fd = thread_data->socket_fd; - } else { - client_fd = accept(thread_data->socket_fd, NULL, NULL); - if (client_fd == -1) { - logwarn("print_data_thread: accept failed: %s\n", - strerror(errno)); - cupsFreeDestInfo(dinfo); - close(thread_data->socket_fd); - cupsFreeOptions(thread_data->num_options, thread_data->options); - cupsFreeDests(1, thread_data->dest); - g_free(buffer); - g_free(thread_data); - return NULL; - } - } /* Read print data from the socket and forward it to CUPS */ ssize_t bytes_read; while ((bytes_read = read(client_fd, buffer, 65536)) > 0) { diff --git a/src/backend_helper.h b/src/backend_helper.h index f8f176d..96cc89a 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -80,6 +80,10 @@ typedef struct _BackendObj int num_frontends; char *default_printer; + + GMutex print_threads_mutex; + GCond print_threads_cond; + int active_print_threads; /* count of in-flight print threads */ } BackendObj; /** @@ -120,6 +124,9 @@ typedef struct _PrintDataThreadData { */ int use_fd; char title[256]; + GMutex *print_threads_mutex; + GCond *print_threads_cond; + int *active_print_threads; } PrintDataThreadData; typedef struct _AddressList { From c83acf44218dd285dde1fc2e894abd93fefa2370 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Tue, 14 Apr 2026 13:52:04 +0000 Subject: [PATCH 19/22] Add backend_obj_wait_for_print_threads function and update print_socket/print_fd signatures for thread management --- src/backend_helper.c | 72 ++++++++++++++++++++++++++++++++++++++-- src/backend_helper.h | 10 ++++-- src/print_backend_cups.c | 9 ++--- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index ee10fa8..4ac0df4 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1414,10 +1414,22 @@ const char *get_printer_state(PrinterCUPS *p) return str; } +void backend_obj_wait_for_print_threads(BackendObj *b) +{ + g_mutex_lock(&b->print_threads_mutex); + while (b->active_print_threads > 0) + { + logdebug("Waiting for %d active print thread(s) to finish...\n", + b->active_print_threads); + g_cond_wait(&b->print_threads_cond, &b->print_threads_mutex); + } + g_mutex_unlock(&b->print_threads_mutex); +} + void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title, - char *error_msg, int error_msg_len) + char *error_msg, int error_msg_len, BackendObj *b) { ensure_dest_info(p); int num_options = 0; @@ -1522,10 +1534,22 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, thread_data->socket_fd = socket_fd; /* legacy: thread must call accept() */ snprintf(thread_data->title, sizeof(thread_data->title), "%s", title); + /* Increment active thread count and set up thread synchronization fields */ + g_mutex_lock(&b->print_threads_mutex); + b->active_print_threads++; + g_mutex_unlock(&b->print_threads_mutex); + + thread_data->threads_mutex = &b->print_threads_mutex; + thread_data->threads_cond = &b->print_threads_cond; + thread_data->active_threads = &b->active_print_threads; + // Create a thread for handling data transfer to CUPS pthread_t thread; if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) { logwarn("Error creating thread"); + g_mutex_lock(&b->print_threads_mutex); + b->active_print_threads--; + g_mutex_unlock(&b->print_threads_mutex); close(socket_fd); cupsFreeOptions(num_options, options); cupsFreeDests(1, thread_data->dest); @@ -1540,7 +1564,7 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, int *peer_fd, const char *title, - char *error_msg, int error_msg_len) + char *error_msg, int error_msg_len, BackendObj *b) { ensure_dest_info(p); int num_options = 0; @@ -1602,10 +1626,22 @@ void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, thread_data->use_fd = 1; /* already connected, skip accept() */ snprintf(thread_data->title, sizeof(thread_data->title), "%s", title); + /* Increment active thread count and set up thread synchronization fields */ + g_mutex_lock(&b->print_threads_mutex); + b->active_print_threads++; + g_mutex_unlock(&b->print_threads_mutex); + + thread_data->threads_mutex = &b->print_threads_mutex; + thread_data->threads_cond = &b->print_threads_cond; + thread_data->active_threads = &b->active_print_threads; + pthread_t thread; if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) { logwarn("print_fd: pthread_create failed\n"); + g_mutex_lock(&b->print_threads_mutex); + b->active_print_threads--; + g_mutex_unlock(&b->print_threads_mutex); close(sv[0]); close(sv[1]); cupsFreeOptions(num_options, options); @@ -1640,6 +1676,14 @@ static void *print_data_thread(void *data) { cupsFreeDests(1, thread_data->dest); g_free(buffer); g_free(thread_data); + /* Decrement active thread count and signal waiters */ + if (thread_data->active_threads) + { + g_mutex_lock(thread_data->threads_mutex); + (*thread_data->active_threads)--; + g_cond_broadcast(thread_data->threads_cond); + g_mutex_unlock(thread_data->threads_mutex); + } return NULL; } @@ -1663,6 +1707,14 @@ static void *print_data_thread(void *data) { cupsFreeDests(1, thread_data->dest); g_free(buffer); g_free(thread_data); + /* Decrement active thread count and signal waiters */ + if (thread_data->active_threads) + { + g_mutex_lock(thread_data->threads_mutex); + (*thread_data->active_threads)--; + g_cond_broadcast(thread_data->threads_cond); + g_mutex_unlock(thread_data->threads_mutex); + } return NULL; } } @@ -1702,6 +1754,14 @@ static void *print_data_thread(void *data) { cupsFreeDests(1, thread_data->dest); g_free(buffer); g_free(thread_data); + /* Decrement active thread count and signal waiters */ + if (thread_data->active_threads) + { + g_mutex_lock(thread_data->threads_mutex); + (*thread_data->active_threads)--; + g_cond_broadcast(thread_data->threads_cond); + g_mutex_unlock(thread_data->threads_mutex); + } return NULL; } @@ -1733,6 +1793,14 @@ static void *print_data_thread(void *data) { cupsFreeDests(1, thread_data->dest); g_free(buffer); g_free(thread_data); + /* Decrement active thread count and signal waiters */ + if (thread_data->active_threads) + { + g_mutex_lock(thread_data->threads_mutex); + (*thread_data->active_threads)--; + g_cond_broadcast(thread_data->threads_cond); + g_mutex_unlock(thread_data->threads_mutex); + } return NULL; } diff --git a/src/backend_helper.h b/src/backend_helper.h index 96cc89a..bf94f36 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -257,7 +257,7 @@ int add_media_to_options(PrinterCUPS *p, Media *medias, int media_count, Option void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title, - char *error_msg, int error_msg_len); + char *error_msg, int error_msg_len, BackendObj *b); /** * FD-passing variant of print_socket(). @@ -270,7 +270,13 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, */ void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, int *peer_fd, const char *title, - char *error_msg, int error_msg_len); + char *error_msg, int error_msg_len, BackendObj *b); + +/** + * Wait for all in-flight print threads to complete. + * Blocks until active_print_threads == 0. + */ +void backend_obj_wait_for_print_threads(BackendObj *b); gboolean checkRemote(const char *uri); char *extractHostFromURI(const char *uri); diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 20e46f8..a33b50d 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -360,8 +360,9 @@ static gboolean on_handle_do_listing(PrintBackend *interface, remove_frontend(b, dialog_name); if (no_frontends(b)) { - // FIXME: this is racy against method calls already in-flight from dbus - g_message("No frontends connected .. exiting backend.\n"); + /* Wait for any in-flight print threads before quitting */ + backend_obj_wait_for_print_threads(b); + logdebug("No frontends connected and no print threads active — exiting.\n"); g_idle_add_once((GSourceOnceFunc)g_main_loop_quit, loop); } } @@ -525,7 +526,7 @@ static gboolean on_handle_print_socket(PrintBackend *interface, jobid[0] = '\0'; // prevent garbage being sent over D-Bus on failure socket[0] = '\0'; // used below to detect if print_socket succeeded - print_socket(p, num_settings, settings, jobid, socket, title, error_msg, sizeof(error_msg)); + print_socket(p, num_settings, settings, jobid, socket, title, error_msg, sizeof(error_msg), b); /* If socket_path is empty, print_socket failed before creating the job. * Return a D-Bus error so the frontend doesn't hang waiting for a reply. */ @@ -570,7 +571,7 @@ static gboolean on_handle_print_fd(PrintBackend *interface, jobid[0] = '\0'; print_fd(p, num_settings, settings, jobid, &peer_fd, - title, error_msg, sizeof(error_msg)); + title, error_msg, sizeof(error_msg), b); if (peer_fd == -1) { From 3469e0abe98c14e9e7c3523f7f43b5aed67dd99e Mon Sep 17 00:00:00 2001 From: Souptik De Date: Tue, 14 Apr 2026 13:59:46 +0000 Subject: [PATCH 20/22] fixed typo --- src/backend_helper.c | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 4ac0df4..4f5421c 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1539,9 +1539,9 @@ void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, b->active_print_threads++; g_mutex_unlock(&b->print_threads_mutex); - thread_data->threads_mutex = &b->print_threads_mutex; - thread_data->threads_cond = &b->print_threads_cond; - thread_data->active_threads = &b->active_print_threads; + thread_data->print_threads_mutex = &b->print_threads_mutex; + thread_data->print_threads_cond = &b->print_threads_cond; + thread_data->active_print_threads = &b->active_print_threads; // Create a thread for handling data transfer to CUPS pthread_t thread; @@ -1631,9 +1631,9 @@ void print_fd(PrinterCUPS *p, int num_settings, GVariant *settings, b->active_print_threads++; g_mutex_unlock(&b->print_threads_mutex); - thread_data->threads_mutex = &b->print_threads_mutex; - thread_data->threads_cond = &b->print_threads_cond; - thread_data->active_threads = &b->active_print_threads; + thread_data->print_threads_mutex = &b->print_threads_mutex; + thread_data->print_threads_cond = &b->print_threads_cond; + thread_data->active_print_threads = &b->active_print_threads; pthread_t thread; if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) @@ -1677,12 +1677,12 @@ static void *print_data_thread(void *data) { g_free(buffer); g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_threads) + if (thread_data->active_print_threads) { - g_mutex_lock(thread_data->threads_mutex); - (*thread_data->active_threads)--; - g_cond_broadcast(thread_data->threads_cond); - g_mutex_unlock(thread_data->threads_mutex); + g_mutex_lock(thread_data->print_threads_mutex); + (*thread_data->active_print_threads)--; + g_cond_broadcast(thread_data->print_threads_cond); + g_mutex_unlock(thread_data->print_threads_mutex); } return NULL; } @@ -1708,12 +1708,12 @@ static void *print_data_thread(void *data) { g_free(buffer); g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_threads) + if (thread_data->active_print_threads) { - g_mutex_lock(thread_data->threads_mutex); - (*thread_data->active_threads)--; - g_cond_broadcast(thread_data->threads_cond); - g_mutex_unlock(thread_data->threads_mutex); + g_mutex_lock(thread_data->print_threads_mutex); + (*thread_data->active_print_threads)--; + g_cond_broadcast(thread_data->print_threads_cond); + g_mutex_unlock(thread_data->print_threads_mutex); } return NULL; } @@ -1755,12 +1755,12 @@ static void *print_data_thread(void *data) { g_free(buffer); g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_threads) + if (thread_data->active_print_threads) { - g_mutex_lock(thread_data->threads_mutex); - (*thread_data->active_threads)--; - g_cond_broadcast(thread_data->threads_cond); - g_mutex_unlock(thread_data->threads_mutex); + g_mutex_lock(thread_data->print_threads_mutex); + (*thread_data->active_print_threads)--; + g_cond_broadcast(thread_data->print_threads_cond); + g_mutex_unlock(thread_data->print_threads_mutex); } return NULL; } @@ -1794,12 +1794,12 @@ static void *print_data_thread(void *data) { g_free(buffer); g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_threads) + if (thread_data->active_print_threads) { - g_mutex_lock(thread_data->threads_mutex); - (*thread_data->active_threads)--; - g_cond_broadcast(thread_data->threads_cond); - g_mutex_unlock(thread_data->threads_mutex); + g_mutex_lock(thread_data->print_threads_mutex); + (*thread_data->active_print_threads)--; + g_cond_broadcast(thread_data->print_threads_cond); + g_mutex_unlock(thread_data->print_threads_mutex); } return NULL; } From 37f4e76388dbc880700312a7e70de9abb6ad47c5 Mon Sep 17 00:00:00 2001 From: Souptik De Date: Tue, 14 Apr 2026 15:09:31 +0000 Subject: [PATCH 21/22] Refactor print_data_thread to improve thread synchronization handling anf fix use of memory after free --- src/backend_helper.c | 56 ++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index 4f5421c..b249a5b 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1675,14 +1675,18 @@ static void *print_data_thread(void *data) { cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); + /* Save synchronization fields before freeing */ + GMutex *mtx = thread_data->print_threads_mutex; + GCond *cond = thread_data->print_threads_cond; + int *cnt = thread_data->active_print_threads; g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_print_threads) + if (cnt) { - g_mutex_lock(thread_data->print_threads_mutex); - (*thread_data->active_print_threads)--; - g_cond_broadcast(thread_data->print_threads_cond); - g_mutex_unlock(thread_data->print_threads_mutex); + g_mutex_lock(mtx); + (*cnt)--; + g_cond_broadcast(cond); + g_mutex_unlock(mtx); } return NULL; } @@ -1706,14 +1710,18 @@ static void *print_data_thread(void *data) { cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); + /* Save synchronization fields before freeing */ + GMutex *mtx = thread_data->print_threads_mutex; + GCond *cond = thread_data->print_threads_cond; + int *cnt = thread_data->active_print_threads; g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_print_threads) + if (cnt) { - g_mutex_lock(thread_data->print_threads_mutex); - (*thread_data->active_print_threads)--; - g_cond_broadcast(thread_data->print_threads_cond); - g_mutex_unlock(thread_data->print_threads_mutex); + g_mutex_lock(mtx); + (*cnt)--; + g_cond_broadcast(cond); + g_mutex_unlock(mtx); } return NULL; } @@ -1753,14 +1761,18 @@ static void *print_data_thread(void *data) { cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); + /* Save synchronization fields before freeing */ + GMutex *mtx = thread_data->print_threads_mutex; + GCond *cond = thread_data->print_threads_cond; + int *cnt = thread_data->active_print_threads; g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_print_threads) + if (cnt) { - g_mutex_lock(thread_data->print_threads_mutex); - (*thread_data->active_print_threads)--; - g_cond_broadcast(thread_data->print_threads_cond); - g_mutex_unlock(thread_data->print_threads_mutex); + g_mutex_lock(mtx); + (*cnt)--; + g_cond_broadcast(cond); + g_mutex_unlock(mtx); } return NULL; } @@ -1792,14 +1804,18 @@ static void *print_data_thread(void *data) { cupsFreeOptions(thread_data->num_options, thread_data->options); cupsFreeDests(1, thread_data->dest); g_free(buffer); + /* Save synchronization fields before freeing */ + GMutex *mtx = thread_data->print_threads_mutex; + GCond *cond = thread_data->print_threads_cond; + int *cnt = thread_data->active_print_threads; g_free(thread_data); /* Decrement active thread count and signal waiters */ - if (thread_data->active_print_threads) + if (cnt) { - g_mutex_lock(thread_data->print_threads_mutex); - (*thread_data->active_print_threads)--; - g_cond_broadcast(thread_data->print_threads_cond); - g_mutex_unlock(thread_data->print_threads_mutex); + g_mutex_lock(mtx); + (*cnt)--; + g_cond_broadcast(cond); + g_mutex_unlock(mtx); } return NULL; } From 22ab5029e598b3ef37d6e5aff44e2c157339389b Mon Sep 17 00:00:00 2001 From: Souptik-De Date: Mon, 27 Apr 2026 11:48:50 +0530 Subject: [PATCH 22/22] Fix double close of FD in on_handle_print_fd --- src/print_backend_cups.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index a33b50d..e5bc595 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -595,7 +595,7 @@ static gboolean on_handle_print_fd(PrintBackend *interface, fd_list); g_object_unref(fd_list); - close(peer_fd); + return TRUE; }