From 4a1c45315da9fbf3ddccc9e214a6a4b0882aa233 Mon Sep 17 00:00:00 2001 From: Biswadeep Purkayastha <98874428+metabiswadeep@users.noreply.github.com> Date: Sat, 16 Mar 2024 02:12:16 +0530 Subject: [PATCH] Stream print data through a Unix domain socket (#28) To ease making a Snap from the CPDB backend for CUPS we now transfer the print job data from the dialog to the backend via a Unix domain socket and not by dropping the data into a file. In addition, we have done also the following changes: - Updated names of some CUPS constants to CUPS 2.5.x and newer. - Removed the backend functions for get-all-jobs(), get_active_jobs_count(), and cancel_job(). - In the build test ("make check") + give more time (3 instead of 1 sec) for the print job submission before closing the backend, to get note of the confirmation of successful submission. + let CPDB frontend and backend log in debug mode + Create the directory for the socket files --- src/backend_helper.c | 220 +++++++++++++++++++-------------------- src/backend_helper.h | 23 +++- src/print_backend_cups.c | 83 +++------------ src/run-tests.sh | 16 ++- 4 files changed, 151 insertions(+), 191 deletions(-) diff --git a/src/backend_helper.c b/src/backend_helper.c index d2eab07..ec48a63 100644 --- a/src/backend_helper.c +++ b/src/backend_helper.c @@ -1,4 +1,9 @@ #include "backend_helper.h" +#include +#include +#include +#include +#include #define _CUPS_NO_DEPRECATED 1 @@ -218,7 +223,7 @@ int create_subscription () return (0); } - req = ippNewRequest(IPP_CREATE_PRINTER_SUBSCRIPTION); + req = ippNewRequest(IPP_OP_CREATE_PRINTER_SUBSCRIPTIONS); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddString(req, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, @@ -266,7 +271,7 @@ gboolean renew_subscription (int id) return FALSE; } - req = ippNewRequest(IPP_RENEW_SUBSCRIPTION); + req = ippNewRequest(IPP_OP_RENEW_SUBSCRIPTION); ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "notify-subscription-id", id); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, @@ -322,7 +327,7 @@ void cancel_subscription (int id) return; } - req = ippNewRequest(IPP_CANCEL_SUBSCRIPTION); + req = ippNewRequest(IPP_OP_CANCEL_SUBSCRIPTION); ippAddString(req, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, "/"); ippAddInteger(req, IPP_TAG_OPERATION, IPP_TAG_INTEGER, @@ -544,58 +549,6 @@ cups_dest_t *get_dest_by_name(BackendObj *b, const char *dialog_name, const char } return p->dest; } -GVariant *get_all_jobs(BackendObj *b, const char *dialog_name, int *num_jobs, gboolean active_only) -{ - int CUPS_JOB_FLAG; - if (active_only) - CUPS_JOB_FLAG = CUPS_WHICHJOBS_ACTIVE; - else - CUPS_JOB_FLAG = CUPS_WHICHJOBS_ALL; - - GHashTable *printers = get_dialog_printers(b, dialog_name); - - GVariantBuilder *builder; - GVariant *variant; - builder = g_variant_builder_new(G_VARIANT_TYPE(CPDB_JOB_ARRAY_ARGS)); - - GHashTableIter iter; - gpointer key, value; - g_hash_table_iter_init(&iter, printers); - - int ncurr = 0; - int n = 0; - - int num_printers = g_hash_table_size(printers); - cups_job_t **jobs = g_new(cups_job_t *, num_printers); - - int i_printer = 0; - while (g_hash_table_iter_next(&iter, &key, &value)) - { - /** iterate over all the printers of this dialog **/ - PrinterCUPS *p = (PrinterCUPS *)value; - ensure_printer_connection(p); - printf(" .. %s ..", p->name); - /**This is NOT reporting jobs for ipp printers : Probably a bug in cupsGetJobs2:(( **/ - ncurr = cupsGetJobs2(p->http, &(jobs[i_printer]), p->name, 0, CUPS_JOB_FLAG); - printf("%d\n", ncurr); - n += ncurr; - - for (int i = 0; i < ncurr; i++) - { - printf("i = %d\n", i); - printf("%d %s\n", jobs[i_printer][i].id, jobs[i_printer][i].title); - - g_variant_builder_add_value(builder, pack_cups_job(jobs[i_printer][i])); - } - cupsFreeJobs(ncurr, jobs[i_printer]); - i_printer++; - } - free(jobs); - - *num_jobs = n; - variant = g_variant_new(CPDB_JOB_ARRAY_ARGS, builder); - return variant; -} /***************************PrinterObj********************************/ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest) { @@ -614,6 +567,7 @@ PrinterCUPS *get_new_PrinterCUPS(const cups_dest_t *dest) p->name = dest_copy->name; p->http = NULL; p->dinfo = NULL; + p->stream_socket_path = NULL; return p; } @@ -1444,7 +1398,10 @@ const char *get_printer_state(PrinterCUPS *p) } return str; } -int print_file(PrinterCUPS *p, const char *file_path, int num_settings, GVariant *settings) + + + +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title) { ensure_printer_connection(p); int num_options = 0; @@ -1469,71 +1426,110 @@ int print_file(PrinterCUPS *p, const char *file_path, int num_settings, GVariant */ num_options = cupsAddOption(option_name, option_value, num_options, &options); } - char *file_name = cpdbExtractFileName(file_path); int job_id = 0; cupsCreateDestJob(p->http, p->dest, p->dinfo, - &job_id, file_name, num_options, options); - if (job_id) - { - /** job creation was successful , - * Now let's submit a document - * and start writing data onto it **/ - printf("Created job %d\n", job_id); - http_status_t http_status; /**document creation status **/ - http_status = cupsStartDestDocument(p->http, p->dest, p->dinfo, job_id, - file_name, CUPS_FORMAT_AUTO, - num_options, options, 1); - if (http_status == HTTP_STATUS_CONTINUE) - { - /**Document submitted successfully; - * Now write the data onto it**/ - FILE *fp = fopen(file_path, "rb"); - size_t bytes; - char buffer[65536]; - /** Read and write the data chunk by chunk **/ - while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) - { - http_status = cupsWriteRequestData(p->http, buffer, bytes); - if (http_status != HTTP_STATUS_CONTINUE) - { - printf("Error writing print data to server.\n"); - break; - } - } + &job_id, title, num_options, options); + cupsStartDestDocument(p->http, p->dest, p->dinfo, + job_id, NULL, CUPS_FORMAT_AUTO, + num_options, options, 1); + + int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_fd == -1) { + perror("Error creating socket"); + return; + } + char mkdir_cmd[256]; + snprintf(mkdir_cmd, 256, + "mkdir -p %s/cpdb/sockets", getenv("HOME")); + if (system(mkdir_cmd)!=0){ + perror("Unable to create the sockets directory"); + return; + } + int socket_option = 1; + setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &socket_option, sizeof(socket_option)); - if (cupsFinishDestDocument(p->http, p->dest, p->dinfo) == IPP_STATUS_OK) - printf("Document send succeeded.\n"); - else - printf("Document send failed: %s\n", - cupsLastErrorString()); + snprintf(job_id_str, 32, "%d", job_id); + snprintf(socket_path, 256, + "%s/cpdb/sockets/cups-%s.sock", getenv("HOME"),job_id_str); + p->stream_socket_path = socket_path; + struct sockaddr_un server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sun_family = AF_UNIX; + strncpy(server_addr.sun_path, socket_path, sizeof(server_addr.sun_path) - 1); - fclose(fp); + unlink(socket_path); - return job_id; /**some correction needed here **/ - } + if (bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { + perror("Error connecting to CPDB CUPS backend socket"); + close(socket_fd); + return; } - else - { - printf("Unable to create job %s\n", cupsLastErrorString()); - return 0; + + // Listen for incoming connections, we only need to support one + // single connection (no queue), as the socket is dedicated for a single + // job. + if (listen(socket_fd, 1) == -1) { + perror("Error listening to CPDB CUPS backend socket"); + close(socket_fd); } + + // Create a struct to pass data to the thread + PrintDataThreadData *thread_data = g_malloc(sizeof(PrintDataThreadData)); + thread_data->printer = p; + thread_data->num_options = num_options; + thread_data->options = options; + thread_data->socket_fd = socket_fd; + + // Create a thread for handling data transfer to CUPS + pthread_t thread; + if (pthread_create(&thread, NULL, print_data_thread, thread_data) != 0) { + perror("Error creating thread"); + close(socket_fd); + } else { + // Detach the thread to allow it to run independently + pthread_detach(thread); + } + + } -int get_active_jobs_count(PrinterCUPS *p) -{ - ensure_printer_connection(p); - cups_job_t *jobs; - int num_jobs = cupsGetJobs2(p->http, &jobs, p->name, 0, CUPS_WHICHJOBS_ACTIVE); - cupsFreeJobs(num_jobs, jobs); - return num_jobs; -} -gboolean cancel_job(PrinterCUPS *p, int jobid) -{ - ensure_printer_connection(p); - ipp_status_t status = cupsCancelDestJob(p->http, p->dest, jobid); - if (status == IPP_STATUS_OK) - return TRUE; - return FALSE; + +void *print_data_thread(void *data) { + PrintDataThreadData *thread_data = (PrintDataThreadData *)data; + + // 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) { + perror("Error accepting connection"); + close(thread_data->socket_fd); + } + + // 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) { + printf("Error writing print data to server.\n"); + break; + } + } + + // 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) + printf("Document send succeeded.\n"); + else + printf("Document send failed: %s\n", cupsLastErrorString()); + cupsFreeOptions(thread_data->num_options, thread_data->options); + g_free(thread_data); + g_free(buffer); + + return NULL; } + void printAllJobs(PrinterCUPS *p) { ensure_printer_connection(p); diff --git a/src/backend_helper.h b/src/backend_helper.h index ea23833..d8eb594 100644 --- a/src/backend_helper.h +++ b/src/backend_helper.h @@ -39,6 +39,7 @@ typedef struct _PrinterCUPS cups_dest_t *dest; http_t *http; cups_dinfo_t *dinfo; + char *stream_socket_path; } PrinterCUPS; /** @@ -101,6 +102,22 @@ typedef struct _Media int (*margins)[4]; /** int margins[num_margins][4]; left(0), right(1), top(2), bottom(3) **/ } Media; +/* +typedef struct _PrintResult +{ + gchar *jobid; + gchar *socket; +} PrintResult; +*/ + +typedef struct _PrintDataThreadData { + PrinterCUPS *printer; + int num_options; + cups_option_t *options; + int socket_fd; + struct sockaddr_un server_addr; +} PrintDataThreadData; + /********Backend related functions*******************/ /** Get a new BackendObj **/ @@ -192,7 +209,6 @@ void refresh_printer_list(BackendObj *b, char *dialog_name); GHashTable *get_dialog_printers(BackendObj *b, const char *dialog_name); cups_dest_t *get_dest_by_name(BackendObj *b, const char *dialog_name, const char *printer_name); PrinterCUPS *get_printer_by_name(BackendObj *b, const char *dialog_name, const char *printer_name); -GVariant *get_all_jobs(BackendObj *b, const char *dialog_name, int *num_jobs, gboolean active_only); /*********Printer related functions******************/ @@ -219,10 +235,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); -int print_file(PrinterCUPS *p, const char *file_path, int num_settings, GVariant *settings); +static void *print_data_thread(void *data); +void print_socket(PrinterCUPS *p, int num_settings, GVariant *settings, char *job_id_str, char *socket_path, const char *title); -int get_active_jobs_count(PrinterCUPS *p); -gboolean cancel_job(PrinterCUPS *p, int jobid); /** * Get translation of choice name for a given locale diff --git a/src/print_backend_cups.c b/src/print_backend_cups.c index 12b04c6..7066b57 100644 --- a/src/print_backend_cups.c +++ b/src/print_backend_cups.c @@ -461,35 +461,25 @@ static gboolean on_handle_ping(PrintBackend *interface, return TRUE; } -static gboolean on_handle_print_file(PrintBackend *interface, +static gboolean on_handle_print_socket(PrintBackend *interface, GDBusMethodInvocation *invocation, - const gchar *printer_name, - const gchar *file_path, + const gchar *printer_id, int num_settings, GVariant *settings, - const gchar *final_file_path, + const gchar *title, gpointer user_data) { - const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk - PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); + const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); + PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_id); - int job_id = print_file(p, file_path, num_settings, settings); + // Call the renamed function + char jobid[32]; + char socket[256]; + print_socket(p, num_settings, settings, jobid, socket, title); - char jobid_string[64]; - snprintf(jobid_string, sizeof(jobid_string), "%d", job_id); - print_backend_complete_print_file(interface, invocation, jobid_string); + // Complete the D-Bus method call with the result + print_backend_complete_print_socket(interface, invocation, jobid, socket); - /** - * (Currently Disabled) Printing will always be the last operation, so remove that frontend - */ - //set_dialog_cancel(b, dialog_name); - //remove_frontend(b, dialog_name); - - if (no_frontends(b)) - { - g_message("No frontends connected .. exiting backend.\n"); - exit(EXIT_SUCCESS); - } return TRUE; } @@ -533,41 +523,6 @@ static gboolean on_handle_get_all_options(PrintBackend *interface, return TRUE; } -static gboolean on_handle_get_active_jobs_count(PrintBackend *interface, - GDBusMethodInvocation *invocation, - const gchar *printer_name, - gpointer user_data) -{ - const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk - PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); - print_backend_complete_get_active_jobs_count(interface, invocation, get_active_jobs_count(p)); - return TRUE; -} -static gboolean on_handle_get_all_jobs(PrintBackend *interface, - GDBusMethodInvocation *invocation, - gboolean active_only, - gpointer user_data) -{ - const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); /// potential risk - int n; - GVariant *variant = get_all_jobs(b, dialog_name, &n, active_only); - print_backend_complete_get_all_jobs(interface, invocation, n, variant); - return TRUE; -} -static gboolean on_handle_cancel_job(PrintBackend *interface, - GDBusMethodInvocation *invocation, - const gchar *job_id, - const gchar *printer_name, - gpointer user_data) -{ - const char *dialog_name = g_dbus_method_invocation_get_sender(invocation); - int jobid = atoi(job_id); /**to do. check if given job id is integer */ - PrinterCUPS *p = get_printer_by_name(b, dialog_name, printer_name); - gboolean status = cancel_job(p, jobid); - print_backend_complete_cancel_job(interface, invocation, status); - return TRUE; -} - static gboolean on_handle_get_default_printer(PrintBackend *interface, GDBusMethodInvocation *invocation, gpointer user_data) @@ -624,8 +579,8 @@ void connect_to_signals() G_CALLBACK(on_handle_get_default_printer), //callback NULL); g_signal_connect(skeleton, //instance - "handle-print-file", //signal name - G_CALLBACK(on_handle_print_file), //callback + "handle-print-socket", //signal name + G_CALLBACK(on_handle_print_socket), //callback NULL); g_signal_connect(skeleton, //instance "handle-get-printer-state", //signal name @@ -635,18 +590,6 @@ void connect_to_signals() "handle-is-accepting-jobs", //signal name G_CALLBACK(on_handle_is_accepting_jobs), //callback NULL); - g_signal_connect(skeleton, //instance - "handle-get-active-jobs-count", //signal name - G_CALLBACK(on_handle_get_active_jobs_count), //callback - NULL); - g_signal_connect(skeleton, //instance - "handle-get-all-jobs", //signal name - G_CALLBACK(on_handle_get_all_jobs), //callback - NULL); - g_signal_connect(skeleton, //instance - "handle-cancel-job", //signal name - G_CALLBACK(on_handle_cancel_job), //callback - NULL); g_signal_connect(skeleton, //instance "handle-keep-alive", //signal name G_CALLBACK(on_handle_keep_alive), //callback diff --git a/src/run-tests.sh b/src/run-tests.sh index bea8be6..08ba6ba 100755 --- a/src/run-tests.sh +++ b/src/run-tests.sh @@ -27,8 +27,6 @@ fi clean_up() { - #return - # # Shut down all the daemons # @@ -118,6 +116,7 @@ clean_up() # Remove test bed directories # + #return if test -n "$BASE"; then rm -rf $BASE fi @@ -495,11 +494,18 @@ sleep 2 BACKEND=./cups +# Directory for job transfer sockets +mkdir $BASE/cpdb +mkdir $BASE/cpdb/sockets + # Create the log file BACKEND_LOG=$BASE/log/backend_log rm -f $BACKEND_LOG touch $BACKEND_LOG +# Debug logging for CPDB +export CPDB_DEBUG_LEVEL=debug + echo "Starting CPDB CUPS backend:" echo " $runcups $BACKEND >$BACKEND_LOG 2>&1 &" echo "" @@ -540,7 +546,7 @@ echo "" echo get-all-options $QUEUE CUPS; \ sleep 2; \ echo print-file $FILE_TO_PRINT $QUEUE CUPS; \ - sleep 1; \ + sleep 3; \ echo stop \ ) | $FRONTEND > $FRONTEND_LOG 2>&1 & FRONTEND_PID=$! @@ -551,14 +557,14 @@ if (test "x$FRONTEND_PID" != "x"); then fi # -# Give the frontend a maximum of 12 seconds to run and then kill it, to avoid +# Give the frontend a maximum of 15 seconds to run and then kill it, to avoid # the script getting stuck if stopping the frontend fails. # i=0 while kill -0 $FRONTEND_PID >/dev/null 2>&1; do i=$((i+1)) - if test $i -ge 12; then + if test $i -ge 15; then kill -KILL $FRONTEND_PID >/dev/null 2>&1 || true FRONTEND_PID= echo "FAIL: Frontend keeps running!"