/*
* "$Id: usb-darwin.c 7953 2008-09-17 01:43:19Z mike $"
*
-* Copyright 2005-2010 Apple Inc. All rights reserved.
+* Copyright 2005-2013 Apple Inc. All rights reserved.
*
* IMPORTANT: This Apple software is supplied to you by Apple Computer,
* Inc. ("Apple") in consideration of your agreement to the following
#include <mach/mach_error.h>
#include <mach/mach_time.h>
#include <cups/debug-private.h>
+#include <cups/file-private.h>
#include <cups/sidechannel.h>
#include <cups/language-private.h>
#include "backend-private.h"
-
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
+#include <libproc.h>
#include <spawn.h>
#include <pthread.h>
#define kUSBClassDriverProperty CFSTR("USB Printing Class")
-#define kUSBGenericTOPrinterClassDriver CFSTR("/System/Library/Printers/Libraries/USBGenericTOPrintingClass.plugin")
+#define kUSBGenericTOPrinterClassDriver CFSTR("/System/Library/Printers/Libraries/USBGenericPrintingClass.plugin")
#define kUSBPrinterClassDeviceNotOpen -9664 /*kPMInvalidIOMContext*/
+#define CRSetCrashLogMessage(m) _crc_make_setter(message, m)
+#define _crc_make_setter(attr, arg) (gCRAnnotations.attr = (uint64_t)(unsigned long)(arg))
+#define CRASH_REPORTER_CLIENT_HIDDEN __attribute__((visibility("hidden")))
+#define CRASHREPORTER_ANNOTATIONS_VERSION 4
+#define CRASHREPORTER_ANNOTATIONS_SECTION "__crash_info"
+
+struct crashreporter_annotations_t {
+ uint64_t version; // unsigned long
+ uint64_t message; // char *
+ uint64_t signature_string; // char *
+ uint64_t backtrace; // char *
+ uint64_t message2; // char *
+ uint64_t thread; // uint64_t
+ uint64_t dialog_mode; // unsigned int
+};
+
+CRASH_REPORTER_CLIENT_HIDDEN
+struct crashreporter_annotations_t gCRAnnotations
+ __attribute__((section("__DATA," CRASHREPORTER_ANNOTATIONS_SECTION)))
+ = { CRASHREPORTER_ANNOTATIONS_VERSION, 0, 0, 0, 0, 0, 0 };
/*
* Section 5.3 USB Printing Class spec
UInt16 vendorID; /* Vendor id */
UInt16 productID; /* Product id */
printer_interface_t interface; /* identify the device to IOKit */
- UInt8 outpipe; /* mandatory bulkOut pipe */
+ UInt8 outpipe; /* mandatory bulkOut pipe */
UInt8 inpipe; /* optional bulkIn pipe */
/* general class requests */
*/
globals_t g = { 0 }; /* Globals */
+int Iterating = 0; /* Are we iterating the bus? */
/*
static Boolean list_device_cb(void *refcon, io_service_t obj);
static CFStringRef cfstr_create_trim(const char *cstr);
static CFStringRef copy_value_for_key(CFStringRef deviceID, CFStringRef *keys);
-static kern_return_t load_classdriver(CFStringRef driverPath, printer_interface_t intf, classdriver_t ***printerDriver);
+static kern_return_t load_classdriver(CFStringRef driverPath, printer_interface_t interface, classdriver_t ***printerDriver);
static kern_return_t load_printerdriver(CFStringRef *driverBundlePath);
-static kern_return_t registry_close();
+static kern_return_t registry_close(void);
static kern_return_t registry_open(CFStringRef *driverBundlePath);
-static kern_return_t unload_classdriver();
+static kern_return_t unload_classdriver(classdriver_t ***classdriver);
static OSStatus copy_deviceid(classdriver_t **printer, CFStringRef *deviceID);
static void *read_thread(void *reference);
static void *sidechannel_thread(void *reference);
static void parse_options(char *options, char *serial, int serial_size, UInt32 *location, Boolean *wait_eof);
static void release_deviceinfo(CFStringRef *make, CFStringRef *model, CFStringRef *serial);
static void setup_cfLanguage(void);
-static void soft_reset();
+static void soft_reset(void);
static void status_timer_cb(CFRunLoopTimerRef timer, void *info);
#if defined(__i386__) || defined(__x86_64__)
static void run_legacy_backend(int argc, char *argv[], int fd); /* Starts child backend process running as a ppc executable */
#endif /* __i386__ || __x86_64__ */
static void sigterm_handler(int sig); /* SIGTERM handler */
+static void sigquit_handler(int sig, siginfo_t *si, void *unused);
#ifdef PARSE_PS_ERRORS
static const char *next_line (const char *buffer);
ssize_t total_bytes; /* Total bytes written */
UInt32 bytes; /* Bytes written */
struct timeval *timeout, /* Timeout pointer */
- stimeout; /* Timeout for select() */
+ tv; /* Time value */
struct timespec cond_timeout; /* pthread condition timeout */
+ struct sigaction action; /* Actions for POSIX signals */
+
+
+ (void)uri;
+
+ /*
+ * Catch SIGQUIT to determine who is sending it...
+ */
+ memset(&action, 0, sizeof(action));
+ action.sa_sigaction = sigquit_handler;
+ action.sa_flags = SA_SIGINFO;
+ sigaction(SIGQUIT, &action, NULL);
/*
* See if the side-channel descriptor is valid...
if (!print_fd)
{
- struct sigaction action; /* POSIX signal action */
-
-
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
if (g.print_bytes)
{
- stimeout.tv_sec = 0;
- stimeout.tv_usec = 100000; /* 100ms */
- timeout = &stimeout;
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000; /* 100ms */
+ timeout = &tv;
}
else if (g.drain_output)
{
- stimeout.tv_sec = 0;
- stimeout.tv_usec = 0;
- timeout = &stimeout;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+ timeout = &tv;
}
else
timeout = NULL;
/*
* Ignore timeout errors, but retain the number of bytes written to
- * avoid sending duplicate data (<rdar://problem/6254911>)...
+ * avoid sending duplicate data...
*/
if (iostatus == kIOUSBTransactionTimeout)
/*
* Retry a write after an aborted write since we probably just got
- * SIGTERM (<rdar://problem/6860126>)...
+ * SIGTERM...
*/
else if (iostatus == kIOReturnAborted)
/*
* Write error - bail if we don't see an error we can retry...
*/
+
_cupsLangPrintFilter(stderr, "ERROR",
_("Unable to send data to printer."));
fprintf(stderr, "DEBUG: USB class driver WritePipe returned %x\n",
fprintf(stderr, "DEBUG: Sent %lld bytes...\n", (off_t)total_bytes);
- if (!print_fd)
- {
- /*
- * Re-enable the SIGTERM handler so pthread_kill() will work...
- */
-
- struct sigaction action; /* POSIX signal action */
-
- memset(&action, 0, sizeof(action));
-
- sigemptyset(&action.sa_mask);
- sigaddset(&action.sa_mask, SIGTERM);
- action.sa_handler = sigterm_handler;
- sigaction(SIGTERM, &action, NULL);
- }
-
/*
- * Wait for the side channel thread to exit...
+ * Signal the side channel thread to exit...
*/
if (have_sidechannel)
g.sidechannel_thread_stop = 1;
pthread_mutex_lock(&g.sidechannel_thread_mutex);
+
if (!g.sidechannel_thread_done)
{
- /*
- * Wait for the side-channel thread to exit...
- */
+ gettimeofday(&tv, NULL);
+ cond_timeout.tv_sec = tv.tv_sec + WAIT_SIDE_DELAY;
+ cond_timeout.tv_nsec = tv.tv_usec * 1000;
- cond_timeout.tv_sec = time(NULL) + WAIT_SIDE_DELAY;
- cond_timeout.tv_nsec = 0;
- if (pthread_cond_timedwait(&g.sidechannel_thread_cond,
- &g.sidechannel_thread_mutex,
- &cond_timeout) != 0)
+ while (!g.sidechannel_thread_done)
{
- /*
- * Force the side-channel thread to exit...
- */
-
- fputs("DEBUG: Force the side-channel thread to exit...\n", stderr);
- pthread_kill(sidechannel_thread_id, SIGTERM);
+ if (pthread_cond_timedwait(&g.sidechannel_thread_cond,
+ &g.sidechannel_thread_mutex,
+ &cond_timeout) != 0)
+ break;
}
}
- pthread_mutex_unlock(&g.sidechannel_thread_mutex);
- pthread_join(sidechannel_thread_id, NULL);
-
- pthread_cond_destroy(&g.sidechannel_thread_cond);
- pthread_mutex_destroy(&g.sidechannel_thread_mutex);
+ pthread_mutex_unlock(&g.sidechannel_thread_mutex);
}
- pthread_cond_destroy(&g.readwrite_lock_cond);
- pthread_mutex_destroy(&g.readwrite_lock_mutex);
-
/*
- * Signal the read thread to stop...
+ * Signal the read thread to exit then wait 7 seconds for it to complete...
*/
g.read_thread_stop = 1;
- /*
- * Give the read thread WAIT_EOF_DELAY seconds to complete all the data. If
- * we are not signaled in that time then force the thread to exit.
- */
-
pthread_mutex_lock(&g.read_thread_mutex);
if (!g.read_thread_done)
{
- cond_timeout.tv_sec = time(NULL) + WAIT_EOF_DELAY;
- cond_timeout.tv_nsec = 0;
+ fputs("DEBUG: Waiting for read thread to exit...\n", stderr);
- if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
- &cond_timeout) != 0)
+ gettimeofday(&tv, NULL);
+ cond_timeout.tv_sec = tv.tv_sec + WAIT_EOF_DELAY;
+ cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+ while (!g.read_thread_done)
{
- /*
- * Force the read thread to exit...
- */
+ if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
+ &cond_timeout) != 0)
+ break;
+ }
+
+ /*
+ * If it didn't exit abort the pending read and wait an additional second...
+ */
+
+ if (!g.read_thread_done)
+ {
+ fputs("DEBUG: Read thread still active, aborting the pending read...\n",
+ stderr);
g.wait_eof = 0;
- fputs("DEBUG: Force the read thread to exit...\n", stderr);
- pthread_kill(read_thread_id, SIGTERM);
+
+ (*g.classdriver)->Abort(g.classdriver);
+
+ gettimeofday(&tv, NULL);
+ cond_timeout.tv_sec = tv.tv_sec + 1;
+ cond_timeout.tv_nsec = tv.tv_usec * 1000;
+
+ while (!g.read_thread_done)
+ {
+ if (pthread_cond_timedwait(&g.read_thread_cond, &g.read_thread_mutex,
+ &cond_timeout) != 0)
+ break;
+ }
}
}
- pthread_mutex_unlock(&g.read_thread_mutex);
-
- pthread_join(read_thread_id, NULL); /* wait for the read thread to return */
- pthread_cond_destroy(&g.read_thread_cond);
- pthread_mutex_destroy(&g.read_thread_mutex);
+ pthread_mutex_unlock(&g.read_thread_mutex);
/*
* Close the connection and input file and general clean up...
uint64_t start,
delay;
+
+ (void)reference;
+
/* Calculate what 250 milliSeconds are in mach absolute time...
*/
mach_timebase_info(&timeBaseInfo);
int datalen; /* Request/response data size */
+ (void)reference;
+
do
{
datalen = sizeof(data);
static void iterate_printers(iterator_callback_t callBack,
void *userdata)
{
+ Iterating = 1;
+
mach_port_t masterPort = 0x0;
kern_return_t kr = IOMasterPort (bootstrap_port, &masterPort);
CFRelease(usb_klass);
CFRelease(usb_subklass);
- kr = IOServiceAddMatchingNotification(addNotification, kIOMatchedNotification, usbPrinterMatchDictionary, &device_added, &reference, &addIterator);
+ IOServiceAddMatchingNotification(addNotification, kIOMatchedNotification, usbPrinterMatchDictionary, &device_added, &reference, &addIterator);
if (addIterator != 0x0)
{
device_added (&reference, addIterator);
}
mach_port_deallocate(mach_task_self(), masterPort);
}
+
+ Iterating = 0;
}
/* One last call to the call back now that we are not longer have printers left to iterate...
*/
- if (reference->keepRunning)
+ if (reference->keepRunning && reference->callback)
reference->keepRunning = reference->callback(reference->userdata, 0x0);
if (!reference->keepRunning)
{
Boolean keepRunning = (obj != 0x0);
+
+ (void)refcon;
+
if (keepRunning)
{
CFStringRef deviceIDString = NULL;
if (!make ||
!CFStringGetCString(make, makestr, sizeof(makestr),
kCFStringEncodingUTF8))
- strcpy(makestr, "Unknown");
+ strlcpy(makestr, "Unknown", sizeof(makestr));
if (!model ||
!CFStringGetCString(model, &modelstr[1], sizeof(modelstr)-1,
kCFStringEncodingUTF8))
- strcpy(modelstr + 1, "Printer");
+ strlcpy(modelstr + 1, "Printer", sizeof(modelstr) - 1);
optionsstr[0] = '\0';
if (serial != NULL)
if (!keepLooking && g.status_timer != NULL)
{
fputs("STATE: -offline-report\n", stderr);
- _cupsLangPrintFilter(stderr, "INFO", _("Printer is now online."));
+ _cupsLangPrintFilter(stderr, "INFO", _("The printer is now online."));
CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), g.status_timer, kCFRunLoopDefaultMode);
CFRelease(g.status_timer);
g.status_timer = NULL;
static void status_timer_cb(CFRunLoopTimerRef timer,
void *info)
{
+ (void)timer;
+ (void)info;
+
fputs("STATE: +offline-report\n", stderr);
- _cupsLangPrintFilter(stderr, "INFO", _("Printer is offline."));
+ _cupsLangPrintFilter(stderr, "INFO", _("The printer is offline."));
if (getenv("CLASS") != NULL)
{
*/
static kern_return_t load_classdriver(CFStringRef driverPath,
- printer_interface_t intf,
+ printer_interface_t interface,
classdriver_t ***printerDriver)
{
kern_return_t kr = kUSBPrinterClassDeviceNotOpen;
classdriver_t **driver = NULL;
CFStringRef bundle = driverPath ? driverPath : kUSBGenericTOPrinterClassDriver;
char bundlestr[1024]; /* Bundle path */
- struct stat bundleinfo; /* File information for bundle */
CFURLRef url; /* URL for driver */
CFPlugInRef plugin = NULL; /* Plug-in address */
* Validate permissions for the class driver...
*/
- if (stat(bundlestr, &bundleinfo))
- {
- fprintf(stderr, "DEBUG: Class driver \"%s\" not available: %s\n",
- bundlestr, strerror(errno));
- fputs("STATE: +cups-missing-filter-warning\n", stderr);
-
- if (errno == ENOENT && driverPath)
- return (load_classdriver(NULL, intf, printerDriver));
- else
- return (kr);
- }
- else if (bundleinfo.st_uid ||
- (bundleinfo.st_gid && (bundleinfo.st_mode & S_IWGRP)) ||
- (bundleinfo.st_mode & S_IWOTH))
- {
- fprintf(stderr, "DEBUG: Class driver \"%s\" has insecure file "
- "permissions (0%o/uid=%d/gid=%d).\n", bundlestr,
- bundleinfo.st_mode, (int)bundleinfo.st_uid,
- (int)bundleinfo.st_gid);
- fputs("STATE: +cups-insecure-filter-warning\n", stderr);
+ _cups_fc_result_t result = _cupsFileCheck(bundlestr,
+ _CUPS_FILE_CHECK_DIRECTORY, 1,
+ Iterating ? NULL : _cupsFileCheckFilter, NULL);
- if (bundleinfo.st_uid || (bundleinfo.st_mode & S_IWOTH))
- {
- if (driverPath)
- return (load_classdriver(NULL, intf, printerDriver));
- else
- return (kr);
- }
- }
+ if (result && driverPath)
+ return (load_classdriver(NULL, interface, printerDriver));
+ else if (result)
+ return (kr);
/*
* Try loading the class driver...
{
classdriver_t **genericDriver = NULL;
if (driverPath != NULL && CFStringCompare(driverPath, kUSBGenericTOPrinterClassDriver, 0) != kCFCompareEqualTo)
- kr = load_classdriver(NULL, intf, &genericDriver);
+ kr = load_classdriver(NULL, interface, &genericDriver);
if (kr == kIOReturnSuccess)
{
- (*driver)->interface = intf;
+ (*driver)->interface = interface;
(*driver)->Initialize(driver, genericDriver);
(*driver)->plugin = plugin;
- (*driver)->interface = intf;
+ (*driver)->interface = interface;
*printerDriver = driver;
}
}
IOCFPlugInInterface **iodev = NULL;
SInt32 score;
kern_return_t kr;
- printer_interface_t intf;
+ printer_interface_t interface;
HRESULT res;
kr = IOCreatePlugInInterfaceForService(g.printer_obj, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score);
if (kr == kIOReturnSuccess)
{
- if ((res = (*iodev)->QueryInterface(iodev, USB_INTERFACE_KIND, (LPVOID *) &intf)) == noErr)
+ if ((res = (*iodev)->QueryInterface(iodev, USB_INTERFACE_KIND, (LPVOID *) &interface)) == noErr)
{
*driverBundlePath = IORegistryEntryCreateCFProperty(g.printer_obj, kUSBClassDriverProperty, NULL, kNilOptions);
- kr = load_classdriver(*driverBundlePath, intf, &g.classdriver);
+ kr = load_classdriver(*driverBundlePath, interface, &g.classdriver);
if (kr != kIOReturnSuccess)
- (*intf)->Release(intf);
+ (*interface)->Release(interface);
}
IODestroyPlugInInterface(iodev);
}
* 'registry_close()' - Close the connection to the printer.
*/
-static kern_return_t registry_close()
+static kern_return_t registry_close(void)
{
if (g.classdriver != NULL)
(*g.classdriver)->Close(g.classdriver);
IOCFPlugInInterface **iodev = NULL;
SInt32 score;
kern_return_t kr;
- printer_interface_t intf;
+ printer_interface_t interface;
HRESULT res;
classdriver_t **klassDriver = NULL;
CFStringRef driverBundlePath;
&iodev, &score)) == kIOReturnSuccess)
{
if ((res = (*iodev)->QueryInterface(iodev, USB_INTERFACE_KIND, (LPVOID *)
- &intf)) == noErr)
+ &interface)) == noErr)
{
- (*intf)->GetLocationID(intf, deviceLocation);
- (*intf)->GetInterfaceNumber(intf, interfaceNumber);
+ (*interface)->GetLocationID(interface, deviceLocation);
+ (*interface)->GetInterfaceNumber(interface, interfaceNumber);
driverBundlePath = IORegistryEntryCreateCFProperty(usbInterface,
kUSBClassDriverProperty,
NULL, kNilOptions);
- kr = load_classdriver(driverBundlePath, intf, &klassDriver);
+ kr = load_classdriver(driverBundlePath, interface, &klassDriver);
if (kr != kIOReturnSuccess && driverBundlePath != NULL)
- kr = load_classdriver(NULL, intf, &klassDriver);
+ kr = load_classdriver(NULL, interface, &klassDriver);
if (kr == kIOReturnSuccess && klassDriver != NULL)
- kr = copy_deviceid(klassDriver, deviceID);
+ copy_deviceid(klassDriver, deviceID);
unload_classdriver(&klassDriver);
if (driverBundlePath != NULL)
CFRelease(driverBundlePath);
- /* (*intf)->Release(intf); */
+ /* (*interface)->Release(interface); */
}
IODestroyPlugInInterface(iodev);
}
* Process the option...
*/
- if (!strcasecmp(name, "waiteof"))
+ if (!_cups_strcasecmp(name, "waiteof"))
{
- if (!strcasecmp(value, "on") ||
- !strcasecmp(value, "yes") ||
- !strcasecmp(value, "true"))
+ if (!_cups_strcasecmp(value, "on") ||
+ !_cups_strcasecmp(value, "yes") ||
+ !_cups_strcasecmp(value, "true"))
*wait_eof = true;
- else if (!strcasecmp(value, "off") ||
- !strcasecmp(value, "no") ||
- !strcasecmp(value, "false"))
+ else if (!_cups_strcasecmp(value, "off") ||
+ !_cups_strcasecmp(value, "no") ||
+ !_cups_strcasecmp(value, "false"))
*wait_eof = false;
else
_cupsLangPrintFilter(stderr, "WARNING",
_("Boolean expected for waiteof option \"%s\"."),
value);
}
- else if (!strcasecmp(name, "serial"))
+ else if (!_cups_strcasecmp(name, "serial"))
strlcpy(serial, value, serial_size);
- else if (!strcasecmp(name, "location") && location)
+ else if (!_cups_strcasecmp(name, "location") && location)
*location = strtol(value, NULL, 16);
}
}
}
+/*
+ * 'sigquit_handler()' - SIGQUIT handler.
+ */
+
+static void sigquit_handler(int sig, siginfo_t *si, void *unused)
+{
+ char *path;
+ char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
+ static char msgbuf[256] = "";
+
+
+ (void)sig;
+ (void)unused;
+
+ if (proc_pidpath(si->si_pid, pathbuf, sizeof(pathbuf)) > 0 &&
+ (path = basename(pathbuf)) != NULL)
+ snprintf(msgbuf, sizeof(msgbuf), "SIGQUIT sent by %s(%d)", path, (int)si->si_pid);
+ else
+ snprintf(msgbuf, sizeof(msgbuf), "SIGQUIT sent by PID %d", (int)si->si_pid);
+
+ CRSetCrashLogMessage(msgbuf);
+
+ abort();
+}
+
+
#ifdef PARSE_PS_ERRORS
/*
* 'next_line()' - Find the next line in a buffer.
pCommentEnd += 3; /* Skip past "]%%" */
*pCommentEnd = '\0'; /* There's always room for the nul */
- if (strncasecmp(pCommentBegin, "%%[ Error:", 10) == 0)
+ if (_cups_strncasecmp(pCommentBegin, "%%[ Error:", 10) == 0)
logLevel = "DEBUG";
- else if (strncasecmp(pCommentBegin, "%%[ Flushing", 12) == 0)
+ else if (_cups_strncasecmp(pCommentBegin, "%%[ Flushing", 12) == 0)
logLevel = "DEBUG";
else
logLevel = "INFO";
}
/* move everything over... */
- strcpy(gErrorBuffer, pLineEnd);
+ strlcpy(gErrorBuffer, pLineEnd, sizeof(gErrorBuffer));
gErrorBufferPtr = gErrorBuffer;
pLineEnd = (char *)next_line((const char *)gErrorBuffer);
}
* 'soft_reset()' - Send a soft reset to the device.
*/
-static void soft_reset()
+static void soft_reset(void)
{
fd_set input_set; /* Input set for select() */
- struct timeval stimeout; /* Timeout for select() */
+ struct timeval tv; /* Time value */
char buffer[2048]; /* Buffer */
struct timespec cond_timeout; /* pthread condition timeout */
{
(*g.classdriver)->Abort(g.classdriver);
- cond_timeout.tv_sec = time(NULL) + 1;
- cond_timeout.tv_nsec = 0;
+ gettimeofday(&tv, NULL);
+ cond_timeout.tv_sec = tv.tv_sec + 1;
+ cond_timeout.tv_nsec = tv.tv_usec * 1000;
- pthread_cond_timedwait(&g.readwrite_lock_cond, &g.readwrite_lock_mutex, &cond_timeout);
+ while (g.readwrite_lock)
+ {
+ if (pthread_cond_timedwait(&g.readwrite_lock_cond,
+ &g.readwrite_lock_mutex,
+ &cond_timeout) != 0)
+ break;
+ }
}
g.readwrite_lock = 1;
FD_ZERO(&input_set);
FD_SET(g.print_fd, &input_set);
- stimeout.tv_sec = 0;
- stimeout.tv_usec = 0;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
- while (select(g.print_fd+1, &input_set, NULL, NULL, &stimeout) > 0)
+ while (select(g.print_fd+1, &input_set, NULL, NULL, &tv) > 0)
if (read(g.print_fd, buffer, sizeof(buffer)) <= 0)
break;