%% options copyright owner = Dirk Krause copyright year = 2012-2014 license = bsd %% header /** Printqda job structure. */ typedef struct { pqd_conf_t cfg; /**< Configuration. */ dk3_app_t *app; /**< Application structure. */ char const * const *lmsg; /**< Localized message texts. */ char const * const *cmsg; /**< Localized messages for config. */ char **up; /**< Uppercase option arguments. */ char **lo; /**< Lowercase option arguments. */ int exval; /**< Exit status code. */ int fadm; /**< Flag: Running as printqda. */ int fcerr; /**< Flag: Connection error occured. */ } pqda_job_t; %% module #include "printqd.h" #include "printqda.h" $!trace-include /** If this is defined to 1, each original request / daemon request pair is reported to stdout. */ #define PRINTQDA_DEBUG 0 #if DK3_CHAR_SIZE == 1 #if DK3_HAVE_STRUCT_SOCKADDR_UN /** Configuration file name relative to sysconf directory. */ static char const pqda_conffile[] = { PQD_CONFFILE }; /** Constant non-localized keywords used by the module. */ static char const * const pqda_c8_kw[] = { $!string-table # # 0 Program name when running as administrator. # printqda # # 1 Program group name. # dkt-3 # # 2 Space to separate arguments in line # # # 3 Request keyword # acct-check # # 4 Request keyword # acct-start # # 5 Request keyword # acct-end # # 6 Program running as multiplexor # printqdc $!end }; /** Localized message texts used by the program. */ static char const * const pqda_loc[] = { $!string-table file=printqda.str # # 0 Name of localized message texts file. # printqda.str # # 1 2 Option was already set! # Option was already set: " "! # # 3 4 Option not started by a character! # Option not started by a character: " "! # # 5 6 Empty option! # Empty option: " "! # # 7 Syntax error in request! # Syntax error in request! # # 8 Syntax error, string not finalized! # Syntax error, string not finalized! # # 9 Request contents too long! # Request contents too long! # # 10 Incomplete request! # Incomplete request! Missing one from:\nuser name, printer name, page count, job name, or job title! # # 11 Too few arguments in request! # Too few arguments in request! # # 12 Incomplete request! # Incomplete request, missing either user or printer name! # # 13 14 Illegal control sub-request! # Illegal control sub-request: " "! # # 15 # Connection failed, skipping all remaining input! # # 16 17 # Name of system configuration directory too long:\n" "! # # 18 # Failed to find system configuration directory! # # 19 # Only printqda can process control requests, not printqdc! $!end }; /** Request types (first word in request line). */ static char const * const pqda_request_types[] = { $!string-table info acct-check acct-start acct-end jobstart filestart fileend jobend control $!end }; /** Possible responses to LPRng jobstart requests. */ static char const * const pqda_decisions[] = { $!string-table ACCEPT REMOVE HOLD $!end }; /** Subrequests of control request. */ static char const * const pqda_control_sub_requests[] = { $!string-table r$eset a$dd d$atabase-cleanup $!end }; /** Initialize job structure. @param job Job structure to initialize. */ static void pqda_job_init(pqda_job_t *job) { $? "+ pqda_job_init" dk3mem_res((void *)job, sizeof(pqda_job_t)); job->app = NULL; job->lmsg = NULL; job->cmsg = NULL; job->exval = 3; $? ". exval = 3" job->fadm = 0; $? ". fadm = 0" job->fcerr = 0; job->up = NULL; job->lo = NULL; $? "- pqda_job_init" } /** Clean up job structure before exiting program. @param job Job structure to clean up. */ static void pqda_job_cleanup(pqda_job_t *job) { $? "+ pqda_job_cleanup" if(job->app) { dk3app_close(job->app); job->app = NULL; } $? "- pqda_job_cleanup" } /** Check whether the program is run as administration program printqda or as multiplexor printqdc. The result is stored in fadm. @param job Job structure. @param exename Name of the executable file running. */ static void pqda_check_admin(pqda_job_t *job, char const *exename) { char bu[DK3_MAX_PATH]; /* Buffer for modification. */ char *p1; /* Start of program name. */ char *p2; /* File name suffix. */ $? "+ pqda_check_admin" if(exename) { if(strlen(exename) < sizeof(bu)) { strcpy(bu, exename); p1 = dk3str_rchr(bu, '/'); if(p1) { p1++; } else { p1 = bu; } p2 = dk3str_rchr(p1, '.'); if(p2) { *p2 = '\0'; } $? ". \"%s\"==\"%s\"", p1, pqda_c8_kw[0] if(0 == strcmp(p1, pqda_c8_kw[0])) { job->fadm = 1; $? ". fadm = 1" } } } $? "- pqda_check_admin %d", job->fadm } /** Store an LPRng option. @param job Job structure. @param kc Key character. @param v Value to store. @return 1 on success, 0 on error. */ static int pqda_lprng_set_option(pqda_job_t *job, char kc, char *v) { int back = 0; $? "+ pqda_lprng_set_option %c \"%s\"", kc, TR_STR(v) if(('a' <= kc) && ('z' >= kc)) { if((job->lo)[kc - 'a']) { if(job->fadm) { /* WARNING: Option set twice! */ dk3app_log_3(job->app, DK3_LL_WARNING, job->lmsg, 1, 2, v); } } (job->lo)[kc - 'a'] = v; back = 1; } else { if(('A' <= kc) && ('Z' >= kc)) { if((job->up)[kc - 'A']) { if(job->fadm) { /* WARNING: Option set twice! */ dk3app_log_3(job->app, DK3_LL_WARNING, job->lmsg, 1, 2, v); } } (job->up)[kc - 'A'] = v; back = 1; } } $? "- pqda_lprng_set_option %d", back return back; } /** Retrieve one LPRng option. @param job Job structure. @param kc Key character. @return Pointer to option value if defined, NULL otherwise. */ static char * pqda_lprng_get_option(pqda_job_t *job, char kc) { char *back = NULL; $? "+ pqda_lprng_get_option %c", kc if(('a' <= kc) && ('z' >= kc)) { back = (job->lo)[kc - 'a']; } else { if(('A' <= kc) && ('Z' >= kc)) { back = (job->up)[kc - 'A']; } } $? "- pqda_lprng_get_option \"%s\"", TR_STR(back) return back; } /** Retrieve LPRng specific options. @param job Job structure. @param p2 String containing the options. @return 1 on success, 0 on error. */ static int pqda_lprng_options(pqda_job_t *job, char *p2) { char *ptr; /* Current character to process. */ char *ps; /* Start of current string. */ int back = 1; int i; /* Traverse all up and lo pointers. */ int st; /* Current reader state. */ $? "+ pqda_lprng_options \"%s\"", p2 for(i = 0; i < 26; i++) { (job->up)[i] = NULL; (job->lo)[i] = NULL; } st = 0; ptr = p2; ps = NULL; while(*ptr) { $? ". st = %d c = %c", st, *ptr switch(st) { case 0: { /* in space before a string. */ if('\'' == *ptr) { st = 1; ps = ptr; } } break; case 1: { /* in string. */ switch(*ptr) { case '\'': { $? ". string end" *ptr = '\0'; st = 0; if(ps) { $? ". ps" ps++; if('-' == ps[0]) { if('\0' != ps[1]) { if(!pqda_lprng_set_option(job, ps[1], &(ps[2]))) { back = 0; $? "! failed to save" if(job->fadm) { /* ERROR: Not a character */ dk3app_log_3(job->app, DK3_LL_ERROR, job->lmsg, 3, 4, ps); } } } else { $? "! error in string" back = 0; if(job->fadm) { /* ERROR in string, empty option */ dk3app_log_3(job->app, DK3_LL_ERROR, job->lmsg, 5, 6, ps); } } } } else { $? "! ps" back = 0; if(job->fadm) { /* ERROR in input! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 7); } } } break; case '\\': { $? ". backslash" st = 2; } break; } } break; case 2: { /* in string, backslash found. */ st = 1; } break; } ptr++; } if(0 != st) { $? "! wrong state" back = 0; if(job->fadm) { /* ERROR: Incorrect input! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 8); } } $? "- pqda_lprng_options %d", back return back; } /** Convert a filestart/fileend request. @param job Job structure. @param rq First request word. @param ib3 Input buffer 3 to use for response. @return 1 on success, 0 on error. */ static int pqda_file_startend(pqda_job_t *job, char const *rq, char *ib3) { char *pn; /* Printer name. */ char *un; /* User name. */ char *pc; /* Pagecount value as text. */ char *jn; /* Print job name. */ char *jt; /* Print job title. */ size_t sz; /* Size of converted request. */ int back = 0; $? "+ pqda_file_startend" un = NULL; pn = NULL; pc = NULL; jn = NULL; jt = NULL; un = pqda_lprng_get_option(job, 'n'); pn = pqda_lprng_get_option(job, 'P'); pc = pqda_lprng_get_option(job, 'p'); jt = pqda_lprng_get_option(job, 'J'); jn = pqda_lprng_get_option(job, 'A'); if(!(jn)) { jn = pqda_lprng_get_option(job, 'D'); } if(!(jn)) { jn = pn; } if(!(jt)) { jt = jn; } if((un) && (pn) && (pc) && (jt) && (jn)) { sz = 5 * strlen(pqda_c8_kw[2]); sz += strlen(rq); sz += strlen(un); sz += strlen(pn); sz += strlen(pc); sz += strlen(jt); sz += strlen(jn); if(PQD_INPUT_BUFFER_SIZE > sz) { strcpy(ib3, rq); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, pn); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, un); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, pc); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, jn); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, jt); back = 1; } else { if(job->fadm) { /* ERROR: Request contents too long! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 9); } } } else { if(job->fadm) { /* ERROR: Incomplete jobstart/jobend request! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 10); } } $? "- pqda_file_startend %d", back return back; } /** Prepare input for printqd. @param job Job structure. @param ib1 Original request, unmodified. @param ib3 Input buffer for printqd, our destination. @param p2 Arguments to original request, writable copy. @param act Request type. */ static int pqda_daemon_input( pqda_job_t *job, char *ib1, char *ib3, char *p2, int act ) { char *parts[16]; /* Splitted request string. */ char *un; /* User name. */ char *pn; /* Printer name. */ size_t na; /* Number of elements in nparts. */ size_t sz; /* String length of result string. */ int res; /* Operation result. */ int back = 0; $? "+ pqda_daemon_input \"%s\"", p2 if((act < 8) || (job->fadm)) { /* control requests only printqda. */ un = NULL; pn = NULL; switch(act) { case 0: { /* info */ na = dk3str_c8_explode(parts, 15, p2, NULL); if(2 <= na) { back = 1; } else { if(job->fadm) { /* ERROR: Too few arguments in request! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 11); } } } break; case 1: { /* acct-check */ na = dk3str_c8_explode(parts, 15, p2, NULL); if(2 <= na) { back = 1; } else { if(job->fadm) { /* ERROR: Too few arguments in request! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 11); } } } break; case 2: { /* acct-start */ na = dk3str_c8_explode(parts, 15, p2, NULL); if(5 <= na) { back = 1; } else { if(job->fadm) { /* ERROR: Too few arguments in request! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 11); } } } break; case 3: { /* acct-end */ na = dk3str_c8_explode(parts, 15, p2, NULL); if(5 <= na) { back = 1; } else { if(job->fadm) { /* ERROR: Too few arguments in request */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 11); } } } break; case 4: { /* jobstart */ if(pqda_lprng_options(job, p2)) { un = pqda_lprng_get_option(job, 'n'); pn = pqda_lprng_get_option(job, 'P'); if((un) && (pn)) { sz = strlen(pqda_c8_kw[3]); sz += 2 * strlen(pqda_c8_kw[2]); sz += strlen(un); sz += strlen(pn); if(sz < PQD_INPUT_BUFFER_SIZE) { strcpy(ib3, pqda_c8_kw[3]); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, pn); strcat(ib3, pqda_c8_kw[2]); strcat(ib3, un); back = 1; } else { if(job->fadm) { /* ERROR: Request data too long! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 9); } } } else { if(job->fadm) { /* ERROR: Incomplete information in request! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 12); } } } } break; case 5: { /* filestart */ if(pqda_lprng_options(job, p2)) { back = pqda_file_startend(job, pqda_c8_kw[4], ib3); } } break; case 6: { /* fileend */ if(pqda_lprng_options(job, p2)) { back = pqda_file_startend(job, pqda_c8_kw[5], ib3); } } break; case 7: { /* jobend */ /* Nothing to do, we ignore this request. */ } break; case 8: { /* control */ na = dk3str_c8_explode(parts, 15, p2, NULL); if(na > 0) { res = dk3str_array_abbr(pqda_control_sub_requests, parts[0], '$', 0); if(-1 < res) { back = 1; } else { if(job->fadm) { /* ERROR: Too few arguments in request! */ dk3app_log_3(job->app, DK3_LL_ERROR, job->lmsg, 13, 14, parts[0]); } } } else { if(job->fadm) { dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 11); } } } break; } } else { if((8 == act) && (!(job->fadm))) { job->exval = 1; $? ". exval = 1" dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 19); #if DK3_HAVE_SYSLOG openlog(pqda_c8_kw[6], LOG_PID, LOG_LPR); syslog(LOG_ERR, "Attempt to send control request to printqdc!"); closelog(); #endif } } #if PRINTQDA_DEBUG if(back) { printf("%s\n%s\n", ib1, ib3); } else { if(7 != act) { printf("!!! FAILED %s\n", ib1); } } #endif $? "- pqda_daemon_input %d ib3 = \"%s\"", back, ib3 return back; } /** Convert daemon response to this programs response. @param job Job structure. @param ib3 Destination buffer. @param ib2 Daemon response. @param act Request type. @return 1 if we must write output to standard output, 0 otherwise. */ static int pqda_daemon_output(pqda_job_t *job, char *ib3, char *ib2, int act) { int back = 0; $? "+ pqda_daemon_output" ib3[0] = '\0'; switch(act) { case 0: { $? ". info" back = 1; } break; case 1: { $? ". acct-check" back = 1; } break; case 4: { $? ". jobstart" back = 1; } break; } if(back) { strcpy(ib3, ib2); dk3str_c8_delnl(ib3); if(!(strlen(ib3) > 0)) { back = 0; } } $? "- pqda_daemon_output %d", back return back; } /** Process all input lines found on standard input. @param job Job structure */ static void pqda_process_input(pqda_job_t *job) { char ib1[PQD_INPUT_BUFFER_SIZE]; /* Original input. */ char ib2[PQD_INPUT_BUFFER_SIZE]; /* Copy for test. */ char ib3[PQD_INPUT_BUFFER_SIZE]; /* Daemon input. */ char *p1; /* Request keyword. */ char *p2; /* Request arguments. */ dk3_socket_t sock; /* Socket to daemon. */ size_t sz; /* Request size. */ int act; /* Action to take. */ int bw; /* Bytes written. */ int first; /* Flag: First resp. */ int res; /* Shutdown result. */ $? "+ pqda_process_input" while(fgets(ib1, sizeof(ib1), stdin)) { if(!(job->fcerr)) { dk3str_c8_delnl(ib1); strcpy(ib2, ib1); p1 = dk3str_c8_start(ib2, NULL); if(p1) { if(*p1 != '#') { p2 = dk3str_c8_next(p1, NULL); act = dk3str_c8_array_index(pqda_request_types, p1, 0); strcpy(ib3, ib1); if(pqda_daemon_input(job, ib1, ib3, p2, act)) { sz = strlen(ib3); sock = dk3socket_un_stream_client( (job->cfg).usn, 0L, 0L, NULL, job->app ); if(INVALID_SOCKET != sock) { bw = dk3socket_send(sock, ib3, sz, 0L, 0L, NULL, job->app); if(bw >= 0) { res = dk3socket_shutdown( sock, DK3_TCPIP_SHUTDOWN_WRITE, NULL, job->app ); if(res) { first = 1; ib2[0] = '\0'; do { bw = dk3socket_recv( sock, ib3, (sizeof(ib3) - 1), 0L, 0L, NULL, job->app ); if(bw > 0) { if(first) { first = 0; ib3[((size_t)bw < sizeof(ib3)) ? bw : (sizeof(ib3) - 1)] = '\0'; strcpy(ib2, ib3); } } } while(bw > 0); if(!(first)) { if(pqda_daemon_output(job, ib3, ib2, act)) { fputs(ib3, stdout); fputc('\n', stdout); fflush(stdout); if(!(job->fadm)) { $? ". check \"%s\"", ib3 switch(dk3str_c8_array_index(pqda_decisions, ib3, 0)) { case 1: { job->exval = 3; $? ". exval = 3" } break; case 2: { job->exval = 6; $? ". exval = 3" } break; } } else { $? ". feel as printqda" } } } } } dk3socket_close(sock, NULL, NULL); sock = INVALID_SOCKET; } else { job->fcerr = 1; job->exval = 1; $? ". exval = 1" /* FATAL: Connection failed, skipping all remaining input */ if(job->fadm) { dk3app_log_1(job->app, DK3_LL_FATAL, job->lmsg, 15); } else { #if DK3_HAVE_SYSLOG openlog(pqda_c8_kw[6], LOG_PID, LOG_LPR); syslog(LOG_ERR, "Failed to connect to printqd! Skipping all input!"); closelog(); #endif } } } else { $? "! pqda_daemon_input \"%s\"", ib1 } } } } } $? "- pqda_process_input" } /** Process configuration file and run. @param job Job structure. @param cfgfilename Name of configuration file. */ static void pqda_run_with_config_filename(pqda_job_t *job, char const *cfgfilename) { int res; $? "+ pqda_run_with_config_filename \"%s\"", TR_STR(cfgfilename) res = pqdconf_read( &(job->cfg), cfgfilename, ((job->fadm) ? PQD_PROGRAM_TYPE_ADMIN : PQD_PROGRAM_TYPE_MULTIPLEXOR), job->app, job->cmsg ); if(res) { $? ". config read ok" res = pqdconf_check( &(job->cfg), ((job->fadm) ? PQD_PROGRAM_TYPE_ADMIN : PQD_PROGRAM_TYPE_MULTIPLEXOR), job->app, job->cmsg ); if(res) { $? ". config check ok" $? ". configuration was read successfully" job->exval = 0; $? ". exval = 0" pqda_process_input(job); } else { $? "! config check" /* ERROR: Check failed! */ } } else { $? "! config read" /* ERROR: Failed to read configuration! */ } pqdconf_cleanup(&(job->cfg)); $? "- pqda_run_with_config_filename" } /** Find configuration file name and run with it. @param job Job structure. */ static void pqda_search_for_config_filename(pqda_job_t *job) { char bu[DK3_MAX_PATH]; /* Buffer to construct file name. */ char const *scd; /* System configuration directory. */ $? "+ pqda_search_for_config_filename" scd = dk3inst_get_directory(1); if(scd) { if(strlen(scd) < sizeof(bu)) { strcpy(bu, scd); if((strlen(bu) + strlen(pqda_conffile)) < sizeof(bu)) { strcat(bu, pqda_conffile); pqda_run_with_config_filename(job, bu); } else { /* ERROR: sysconfdir name too long! */ if(job->fadm) { dk3app_log_3(job->app, DK3_LL_ERROR, job->lmsg, 16, 17, scd); } else { #if DK3_HAVE_SYSLOG openlog(pqda_c8_kw[6], LOG_PID, LOG_LPR); syslog(LOG_ERR, "File name of sysconfdir too long!"); closelog(); #endif } } } else { /* ERROR: sysconfdir too long! */ if(job->fadm) { dk3app_log_3(job->app, DK3_LL_ERROR, job->lmsg, 16, 17, scd); } else { #if DK3_HAVE_SYSLOG openlog(pqda_c8_kw[6], LOG_PID, LOG_LPR); syslog(LOG_ERR, "File name of sysconfdir too long!"); closelog(); #endif } } } else { /* ERROR: Failed to obtain sysconfdir! */ if(job->fadm) { dk3app_log_1(job->app, DK3_LL_ERROR, job->lmsg, 18); } else { #if DK3_HAVE_SYSLOG openlog(pqda_c8_kw[6], LOG_PID, LOG_LPR); syslog(LOG_ERR, "Failed to find sysconfdir!"); closelog(); #endif } } $? "- pqda_search_for_config_filename" } /** Run after allocating an application structure. @param job Job structure. */ static void pqda_run_with_app(pqda_job_t *job) { char const * const *xargv; /* Command line arguments array. */ int xargc; /* Number of command line arguments. */ $? "+ pqda_run_with_app" job->lmsg = dk3app_messages(job->app, pqda_loc[0], pqda_loc); job->cmsg = pqdconf_get_messages(job->app); xargc = dk3app_get_argc(job->app); xargv = dk3app_get_argv(job->app); if((xargc > 1) && (job->fadm)) { pqda_run_with_config_filename(job, xargv[1]); } else { pqda_search_for_config_filename(job); } $? "- pqda_run_with_app" } /** For printqdc we use a silent application now instead of a daemon application. Multiple instance of printqdc may run at the same time, so we should not let them write all to the same log file. */ #if VERSION_BEFORE_20120526 /** When running as daemon (no output to stderr) we need the current directory. @param job Job structure. @param argc Number of command line arguments. @param argv Command line arguments array. */ static void pqda_run_for_daemon(pqda_job_t *job, int argc, char *argv[]) { char bu[DK3_MAX_PATH]; /* Buffer for current working dir. */ $? "+ pqda_run_for_daemon" if(dk3sf_c8_getcwd_app(bu, sizeof(bu), NULL)) { job->app = dk3app_open_daemon( bu, argc, (dkChar const * const *)argv, pqda_c8_kw[1] ); if(job->app) { pqda_run_with_app(job); } } else { /* ERROR: Failed to find current directory! */ } $? "- pqda_run_for_daemon" } #endif /** Entry point of the program. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value indicates an error. */ int main(int argc, char *argv[]) { pqda_job_t job; /* Job structure. */ char *up[26]; /* Upper case option arguments. */ char *lo[26]; /* Lower case option arguments. */ int exval; /* Exit status code. */ $!trace-init /tmp/printqda.deb $? "+ main" pqda_job_init(&job); pqda_check_admin(&job, argv[0]); job.up = up; job.lo = lo; #if VERSION_BEFORE_20120526 if(job.fadm) { job.app = dk3app_open_command( argc, (dkChar const * const *)argv, pqda_c8_kw[1] ); if(job.app) { pqda_run_with_app(&job); } else { fputs("printqda: ERROR: Not enough memory (RAM/swap)!\n", stderr); fflush(stderr); } } else { pqda_run_for_daemon(&job, argc, argv); } #endif if(job.fadm) { job.app = dk3app_open_command( argc, (dkChar const * const *)argv, pqda_c8_kw[1] ); } else { job.app = dk3app_open_silent( argc, (dkChar const * const *)argv, pqda_c8_kw[1] ); } if(job.app) { pqda_run_with_app(&job); } else { if(job.fadm) { fputs("printqda: ERROR: Not enough memory (RAM/swap)!\n", stderr); fflush(stderr); } } exval = job.exval; #if 0 /* NO, EXIT CODE MUST INDICATE ACTION TOO! */ if(!(job.fadm)) { exval = 0; } #endif pqda_job_cleanup(&job); $? "- main %d", exval $!trace-end fflush(stdout); exit(exval); return exval; } #else /** Program entry point. @param argc @param argv @return 0 on success, any other value indicates an error. */ int main(int argc, char *argv[]) { fprintf(stderr, "printqd: ERROR: No UNIX domain sockets available!\n"); fflush(stderr); exit(1); } #endif #else /** Program entry point. @param argc @param argv @return 0 on success, any other value indicates an error. */ int main(int argc, char *argv[]) { fprintf( stderr, "printqd: ERROR: This program does not support wide characters (%d)!\n", DK3_CHAR_SIZE ); fflush(stderr); exit(1); } #endif