// connect calls C.httpConnect2 to create a new, open connection to // the CUPS server specified by environment variables, client.conf, etc. // // connect also acquires the connection semaphore and locks the OS // thread to allow the CUPS API to use thread-local storage cleanly. // // The caller is responsible to close the connection when finished // using cupsCore.disconnect. func (cc *cupsCore) connect() (*C.http_t, error) { cc.connectionSemaphore.Acquire() // Lock the OS thread so that thread-local storage is available to // cupsLastError() and cupsLastErrorString(). runtime.LockOSThread() var http *C.http_t select { case h := <-cc.connectionPool: // Reuse another connection. http = h default: // No connection available for reuse; create a new one. http = C.httpConnect2(cc.host, cc.port, nil, C.AF_UNSPEC, cc.encryption, 1, cc.connectTimeout, nil) if http == nil { defer cc.disconnect(http) return nil, fmt.Errorf("Failed to connect to CUPS server %s:%d because %d %s", C.GoString(cc.host), int(cc.port), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString())) } } return http, nil }
// getPPD gets the filename of the PPD for a printer by calling // C.cupsGetPPD3. If the PPD hasn't changed since the time indicated // by modtime, then the returned filename is a nil pointer. // // Note that modtime is a pointer whose value is changed by this // function. // // The caller is responsible to C.free the returned *C.char filename // if the returned filename is not nil. func (cc *cupsCore) getPPD(printername *C.char, modtime *C.time_t) (*C.char, error) { bufsize := C.size_t(filePathMaxLength) buffer := (*C.char)(C.malloc(bufsize)) if buffer == nil { return nil, errors.New("Failed to malloc; out of memory?") } C.memset(unsafe.Pointer(buffer), 0, bufsize) var http *C.http_t if !cc.hostIsLocal { // Don't need a connection or corresponding semaphore if the PPD // is on the local filesystem. // Still need OS thread lock; see else. var err error http, err = cc.connect() if err != nil { return nil, err } defer cc.disconnect(http) } else { // Lock the OS thread so that thread-local storage is available to // cupsLastError() and cupsLastErrorString(). runtime.LockOSThread() defer runtime.UnlockOSThread() } httpStatus := C.cupsGetPPD3(http, printername, modtime, buffer, bufsize) switch httpStatus { case C.HTTP_STATUS_NOT_MODIFIED: // Cache hit. if len(C.GoString(buffer)) > 0 { os.Remove(C.GoString(buffer)) } C.free(unsafe.Pointer(buffer)) return nil, nil case C.HTTP_STATUS_OK: // Cache miss. return buffer, nil default: if len(C.GoString(buffer)) > 0 { os.Remove(C.GoString(buffer)) } C.free(unsafe.Pointer(buffer)) cupsLastError := C.cupsLastError() if cupsLastError != C.IPP_STATUS_OK { return nil, fmt.Errorf("Failed to call cupsGetPPD3(): %d %s", int(cupsLastError), C.GoString(C.cupsLastErrorString())) } return nil, fmt.Errorf("Failed to call cupsGetPPD3(); HTTP status: %d", int(httpStatus)) } }
// printFile prints by calling C.cupsPrintFile2(). // Returns the CUPS job ID, which is 0 (and meaningless) when err // is not nil. func (cc *cupsCore) printFile(user, printername, filename, title *C.char, numOptions C.int, options *C.cups_option_t) (C.int, error) { http, err := cc.connect() if err != nil { return 0, err } defer cc.disconnect(http) C.cupsSetUser(user) jobID := C.cupsPrintFile2(http, printername, filename, title, numOptions, options) if jobID == 0 { return 0, fmt.Errorf("Failed to call cupsPrintFile2() for file %s: %d %s", C.GoString(filename), int(C.cupsLastError()), C.GoString(C.cupsLastErrorString())) } return jobID, nil }
// CreateTempFile calls cupsTempFd() to create a new file that (1) lives in a // "temporary" location (like /tmp) and (2) is readable by CUPS. The caller // is responsible for deleting the file. func CreateTempFile() (*os.File, error) { length := C.size_t(filePathMaxLength) filename := (*C.char)(C.malloc(length)) if filename == nil { return nil, errors.New("Failed to malloc(); out of memory?") } defer C.free(unsafe.Pointer(filename)) createTempFileLock.Lock() defer createTempFileLock.Unlock() runtime.LockOSThread() defer runtime.UnlockOSThread() fd := C.cupsTempFd(filename, C.int(length)) if fd == C.int(-1) { err := fmt.Errorf("Failed to call cupsTempFd(): %d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString())) return nil, err } return os.NewFile(uintptr(fd), C.GoString(filename)), nil }
// doRequest calls cupsDoRequest(). func (cc *cupsCore) doRequest(request *C.ipp_t, acceptableStatusCodes []C.ipp_status_t) (*C.ipp_t, error) { http, err := cc.connect() if err != nil { return nil, err } defer cc.disconnect(http) if C.ippValidateAttributes(request) != 1 { return nil, fmt.Errorf("Bad IPP request: %s", C.GoString(C.cupsLastErrorString())) } response := C.cupsDoRequest(http, request, C.POST_RESOURCE) if response == nil { return nil, fmt.Errorf("cupsDoRequest failed: %d %s", int(C.cupsLastError()), C.GoString(C.cupsLastErrorString())) } statusCode := C.getIPPRequestStatusCode(response) for _, sc := range acceptableStatusCodes { if statusCode == sc { return response, nil } } return nil, fmt.Errorf("IPP status code %d", int(statusCode)) }