Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2e0b3dc
Include glib.h in backend_helper.c
Souptik-De Mar 3, 2026
72ad60a
Add workflow_dispatch trigger to build.yml
Souptik-De Mar 5, 2026
7d79253
Add workflow_dispatch trigger to CodeQL workflow
Souptik-De Mar 5, 2026
a609030
Merge branch 'OpenPrinting:master' into master
Souptik-De Mar 9, 2026
e43a34c
Merge branch 'master' of https://github.com/Souptik-De/cpdb-backend-cups
Souptik-De Mar 9, 2026
eabf8ed
Removed redundent glib.h
Souptik-De Mar 9, 2026
27ebfb4
Merge branch 'OpenPrinting:master' into master
Souptik-De Mar 15, 2026
c6bcee1
Add error handling for missing HOME environment variable in print_soc…
Souptik-De Mar 17, 2026
be83c5a
Merge branch 'master' of https://github.com/Souptik-De/cpdb-backend-cups
Souptik-De Mar 17, 2026
ac98a12
Add error message handling in print_socket and update print_backend_c…
Souptik-De Mar 19, 2026
119fddc
Merge branch 'OpenPrinting:master' into master
Souptik-De Mar 19, 2026
70541c4
Fix print_socket call
Souptik-De Mar 19, 2026
c99307a
Merge branch 'master' of https://github.com/Souptik-De/cpdb-backend-cups
Souptik-De Mar 19, 2026
1132501
Add error message handling to print_socket function with proper decla…
Souptik-De Mar 19, 2026
f908daa
updated print data thread structure
Souptik-De Mar 22, 2026
ba7aee5
fix auth failure when printing to authenticated CUPS policy
Souptik-De Mar 22, 2026
55f70a6
Merge branch 'OpenPrinting:master' into master
Souptik-De Mar 22, 2026
d8296ed
refactor print_data_thread
Souptik-De Mar 22, 2026
655812f
Merge branch 'OpenPrinting:master' into master
Souptik-De Mar 24, 2026
c978f13
stop manually managing http_t, instead use CUPS_HTTP_DEFAULT everywhere
Souptik-De Mar 26, 2026
b4e0086
Removed commented out line
Souptik-De Mar 26, 2026
cd1a3e2
Fix typo
Souptik-De Mar 26, 2026
1ebbc99
Merge branch 'OpenPrinting:master' into master
Souptik-De Apr 1, 2026
4be9579
updated Print data thread with use_fd flag and declared print_fd
Souptik-De Apr 3, 2026
73c850b
Merge branch 'master' of https://github.com/Souptik-De/cpdb-backend-cups
Souptik-De Apr 3, 2026
c940d90
Add print_fd function
Souptik-De Apr 3, 2026
57ce7f5
Add on_handle_print_fd
Souptik-De Apr 3, 2026
58c3588
print_data_thread deadlock resolution and add conter variable for act…
Souptik-De Apr 14, 2026
c83acf4
Add backend_obj_wait_for_print_threads function and update print_sock…
Souptik-De Apr 14, 2026
3469e0a
fixed typo
Souptik-De Apr 14, 2026
37f4e76
Refactor print_data_thread to improve thread synchronization handling…
Souptik-De Apr 14, 2026
22ab502
Fix double close of FD in on_handle_print_fd
Souptik-De Apr 27, 2026
d7b2668
Merge branch 'OpenPrinting:master' into master
Souptik-De Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 212 additions & 17 deletions src/backend_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -1411,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;
Expand Down Expand Up @@ -1511,13 +1526,26 @@ 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);

/* 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->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;
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);
Expand All @@ -1530,6 +1558,99 @@ 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, BackendObj *b)
{
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);

/* 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->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)
{
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);
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);
Expand All @@ -1550,17 +1671,74 @@ 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 (cnt)
{
g_mutex_lock(mtx);
(*cnt)--;
g_cond_broadcast(cond);
g_mutex_unlock(mtx);
}
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);
/* 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 (cnt)
{
g_mutex_lock(mtx);
(*cnt)--;
g_cond_broadcast(cond);
g_mutex_unlock(mtx);
}
return NULL;
}
}

/*
* 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.
*
* 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,
Expand All @@ -1573,24 +1751,25 @@ 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);
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);
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);
/* 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 (cnt)
{
g_mutex_lock(mtx);
(*cnt)--;
g_cond_broadcast(cond);
g_mutex_unlock(mtx);
}
return NULL;
}

Expand All @@ -1613,11 +1792,27 @@ 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);
/* 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 (cnt)
{
g_mutex_lock(mtx);
(*cnt)--;
g_cond_broadcast(cond);
g_mutex_unlock(mtx);
}
return NULL;
}

Expand Down
36 changes: 35 additions & 1 deletion src/backend_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -111,7 +115,18 @@ 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];
GMutex *print_threads_mutex;
GCond *print_threads_cond;
int *active_print_threads;
} PrintDataThreadData;

typedef struct _AddressList {
Expand Down Expand Up @@ -242,7 +257,26 @@ 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().
*
* 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, 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);
Expand Down
Loading
Loading