1 /************************************************************************************\
2 
3   hpaio.c - HP SANE backend for multi-function peripherals (libsane-hpaio)
4 
5   (c) 2001-2008 Copyright HP Development Company, LP
6 
7   Permission is hereby granted, free of charge, to any person obtaining a copy
8   of this software and associated documentation files (the "Software"), to deal
9   in the Software without restriction, including without limitation the rights
10   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11   of the Software, and to permit persons to whom the Software is furnished to do
12   so, subject to the following conditions:
13 
14   The above copyright notice and this permission notice shall be included in all
15   copies or substantial portions of the Software.
16 
17   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19   FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20   COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 
24   Contributing Authors: David Paschal, Don Welch, David Suffield, Narla Naga Samrat Chowdary,
25                         Yashwant Sahu, Sarbeswar Meher
26 
27 \************************************************************************************/
28 
29 
30 #ifndef _GNU_SOURCE
31 #define _GNU_SOURCE
32 #endif
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <cups/cups.h>
38 #include "hpmud.h"
39 #include "hp_ipp.h"
40 #include "soap.h"
41 #include "soapht.h"
42 #include "marvell.h"
43 #include "hpaio.h"
44 #include "ledm.h"
45 #include "sclpml.h"
46 #include "escl.h"
47 #include "io.h"
48 #include "orblitei.h"
49 
50 
51 #define DEBUG_DECLARE_ONLY
52 #include "sanei_debug.h"
53 
54 static SANE_Device **DeviceList = NULL;
55 
AddDeviceList(char * uri,char * model,SANE_Device *** pd)56 static int AddDeviceList(char *uri, char *model, SANE_Device ***pd)
57 {
58    int i = 0, uri_length = 0;
59 
60    if (*pd == NULL)
61    {
62       /* Allocate array of pointers. */
63       *pd = malloc(sizeof(SANE_Device *) * MAX_DEVICE);
64       memset(*pd, 0, sizeof(SANE_Device *) * MAX_DEVICE);
65    }
66 
67    uri += 3; /* Skip "hp:" */
68    uri_length = strlen(uri);
69    if(strstr(uri, "&queue=false"))
70        uri_length -= 12; // Last 12 bytes of URI i.e "&queue=false"
71 
72    /* Find empty slot in array of pointers. */
73    for (i=0; i < MAX_DEVICE; i++)
74    {
75       if ((*pd)[i] == NULL)
76       {
77          /* Allocate Sane_Device and members. */
78          (*pd)[i] = malloc(sizeof(SANE_Device));
79          (*pd)[i]->name = malloc(strlen(uri)+1);
80          strcpy((char *)(*pd)[i]->name, uri);
81          (*pd)[i]->model = strdup(model);
82          (*pd)[i]->vendor = "Hewlett-Packard";
83          (*pd)[i]->type = "all-in-one";
84          break;
85       }
86 
87       //Check for Duplicate URI. If URI is added already then don't add it again.
88       if( strncasecmp((*pd)[i]->name, uri, uri_length) == 0 )
89           break;
90    }
91 
92    return 0;
93 }
94 
ResetDeviceList(SANE_Device *** pd)95 static int ResetDeviceList(SANE_Device ***pd)
96 {
97    int i;
98 
99    if (*pd)
100    {
101       for (i=0; (*pd)[i] && i<MAX_DEVICE; i++)
102       {
103          if ((*pd)[i]->name)
104             free((void *)(*pd)[i]->name);
105          if ((*pd)[i]->model)
106             free((void *)(*pd)[i]->model);
107          free((*pd)[i]);
108       }
109       free(*pd);
110       *pd = NULL;
111    }
112 
113    return 0;
114 }
115 
116 /* Parse URI record from buf. Assumes one record per line. All returned strings are zero terminated. */
GetUriLine(char * buf,char * uri,char ** tail)117 static int GetUriLine(char *buf, char *uri, char **tail)
118 {
119    int i=0, j;
120    int maxBuf = HPMUD_LINE_SIZE*64;
121 
122    uri[0] = 0;
123 
124    if (strncasecmp(&buf[i], "direct ", 7) == 0)
125    {
126       i = 7;
127       j = 0;
128       for (; buf[i] == ' ' && i < maxBuf; i++);  /* eat white space before string */
129       while ((buf[i] != ' ') && (i < maxBuf) && (j < HPMUD_LINE_SIZE))
130          uri[j++] = buf[i++];
131       uri[j] = 0;
132 
133       for (; buf[i] != '\n' && i < maxBuf; i++);  /* eat rest of line */
134    }
135    else
136    {
137       for (; buf[i] != '\n' && i < maxBuf; i++);  /* eat line */
138    }
139 
140    i++;   /* bump past '\n' */
141 
142    if (tail != NULL)
143       *tail = buf + i;  /* tail points to next line */
144 
145    return i;
146 }
147 
AddCupsList(char * uri,char *** printer)148 static int AddCupsList(char *uri, char ***printer)
149 {
150    int i, stat=1;
151 
152    /* Look for hp network URIs only. */
153    if (strncasecmp(uri, "hp:/net/", 8) !=0)
154       goto bugout;
155 
156    if (*printer == NULL)
157    {
158       /* Allocate array of string pointers. */
159       *printer = malloc(sizeof(char *) * MAX_DEVICE);
160       memset(*printer, 0, sizeof(char *) * MAX_DEVICE);
161    }
162 
163    /* Ignor duplicates (ie: printer queues using the same device). */
164    for (i=0; (*printer)[i] != NULL && i<MAX_DEVICE; i++)
165    {
166       if (strcmp((*printer)[i], uri) == 0)
167          goto bugout;
168    }
169 
170    /* Find empty slot in array of pointers. */
171    for (i=0; i<MAX_DEVICE; i++)
172    {
173       if ((*printer)[i] == NULL)
174       {
175          (*printer)[i] = strdup(uri);
176          break;
177       }
178    }
179 
180    stat = 0;
181 
182 bugout:
183 
184    return stat;
185 }
186 
187 
GetCupsPrinters(char *** printer)188 static int GetCupsPrinters(char ***printer)
189 {
190    http_t *http=NULL;     /* HTTP object */
191    ipp_t *request=NULL;  /* IPP request object */
192    ipp_t *response=NULL; /* IPP response object */
193    ipp_attribute_t *attr;     /* Current IPP attribute */
194    int cnt=0;
195 
196    /* Connect to the HTTP server */
197    if ((http = httpConnectEncrypt(cupsServer(), ippPort(), cupsEncryption())) == NULL)
198       goto bugout;
199 
200    /* Assemble the IPP request */
201    request = ippNew();
202 
203    ippSetOperation( request, CUPS_GET_PRINTERS );
204    ippSetRequestId( request, 1 );
205 
206    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, "utf-8");
207    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, "en");
208    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "device-uri");
209 
210    /* Send the request and get a response. */
211    if ((response = cupsDoRequest(http, request, "/")) == NULL)
212       goto bugout;
213 
214    for (attr = ippFirstAttribute ( response ); attr != NULL; attr = ippNextAttribute( response ))
215    {
216       /* Skip leading attributes until we hit a printer. */
217       while (attr != NULL && ippGetGroupTag( attr ) != IPP_TAG_PRINTER)
218          attr = ippNextAttribute( response );
219 
220       if (attr == NULL)
221          break;
222 
223       while (attr != NULL && ippGetGroupTag( attr ) == IPP_TAG_PRINTER)
224       {
225          if (strcmp(ippGetName( attr ), "device-uri") == 0 && ippGetValueTag( attr ) == IPP_TAG_URI && AddCupsList(ippGetString( attr, 0, NULL ), printer) == 0)
226             cnt++;
227          attr = ippNextAttribute( response );
228       }
229 
230       if (attr == NULL)
231          break;
232    }
233 
234    ippDelete(response);
235 
236  bugout:
237    return cnt;
238 }
239 
AddDevice(char * uri)240 static int AddDevice(char *uri)
241 {
242     struct hpmud_model_attributes ma;
243     char model[HPMUD_LINE_SIZE];
244     char new_uri[256];
245     int len = 0, i = 0, j = 0;
246     int scan_type;
247     int device_added = 0;
248 
249     hpmud_query_model(uri, &ma);
250     if (ma.scantype > 0)
251     {
252        hpmud_get_uri_model(uri, model, sizeof(model));
253        AddDeviceList(uri, model, &DeviceList);
254        device_added = 1;
255     }
256     else
257     {
258        // This is added to make the uri hp:/net/hp_model_name?ip-xxx.xxx.xxx.xxx&queue=false
259        // For some of the devices the scan MDL recevied would be model_name instead of hp_model_name
260        len = strlen(uri);
261        strncpy(new_uri, uri, 9);
262        new_uri[8] = 'h';
263        new_uri[9] = 'p';
264        new_uri[10] = '_';
265        for (i = 11,j = 8; j<=len; ++i, ++j)
266          new_uri[i] = uri[j];
267 
268        hpmud_query_model(new_uri, &ma);
269        DBG(6,"scantype=%d %s\n", ma.scantype, new_uri);
270 
271        if(ma.scantype>0)
272        {
273           hpmud_get_uri_model(new_uri, model, sizeof(model));
274           AddDeviceList(new_uri, model, &DeviceList);
275           device_added = 1;
276        }
277        else
278        {
279          DBG(6,"unsupported scantype=%d %s\n", ma.scantype, new_uri);
280        }
281     }
282 
283     return device_added;
284 }
285 
DevDiscovery(int localOnly)286 static int DevDiscovery(int localOnly)
287 {
288     char message[HPMUD_LINE_SIZE*64];
289     char uri[HPMUD_LINE_SIZE];
290     char *tail = message;
291     int i, scan_type, cnt=0, total=0, bytes_read;
292     char **cups_printer=NULL;     /* list of printers */
293     char* token = NULL;
294     enum HPMUD_RESULT stat;
295 
296     stat = hpmud_probe_devices(HPMUD_BUS_ALL, message, sizeof(message), &cnt, &bytes_read);
297     if (stat != HPMUD_R_OK)
298       goto bugout;
299 
300     /* Look for local all-in-one scan devices (defined by hpmud). */
301     for (i=0; i<cnt; i++)
302     {
303         GetUriLine(tail, uri, &tail);
304         total += AddDevice(uri);
305     }
306     memset(message, 0, sizeof(message));
307     /* Look for Network Scan devices if localonly flag if FALSE. */
308     if (!localOnly)
309     {
310         /* Look for all-in-one scan devices for which print queue created */
311         cnt = GetCupsPrinters(&cups_printer);
312         for (i=0; i<cnt; i++)
313         {
314             total += AddDevice(cups_printer[i]);
315             free(cups_printer[i]);
316         }
317         if (cups_printer)
318             free(cups_printer);
319 #ifdef HAVE_LIBNETSNMP
320         /* Discover NW scanners using Bonjour*/
321         bytes_read = mdns_probe_nw_scanners(message, sizeof(message), &cnt);
322         token = strtok(message, ";");
323         while (token)
324         {
325             total += AddDevice(token);
326             token = strtok(NULL, ";");
327         }
328 #endif
329         if(!total)
330         {
331           SendScanEvent("hpaio:/net/HP_Scan_Devices?ip=1.1.1.1", EVENT_ERROR_NO_PROBED_DEVICES_FOUND);
332         }
333     }
334 
335 bugout:
336    return total;
337 }
338 
339 /******************************************************* SANE API *******************************************************/
340 
sane_hpaio_init(SANE_Int * pVersionCode,SANE_Auth_Callback authorize)341 extern SANE_Status sane_hpaio_init(SANE_Int * pVersionCode, SANE_Auth_Callback authorize)
342 {
343     int stat;
344 
345     DBG_INIT();
346     InitDbus();
347 
348     DBG(8, "sane_hpaio_init(): %s %d\n", __FILE__, __LINE__);
349 
350     if( pVersionCode )
351     {
352        *pVersionCode = SANE_VERSION_CODE( 1, 0, 0 );
353     }
354 
355 
356     stat = orblite_init(pVersionCode, authorize);
357 
358     return stat;
359 }  /* sane_hpaio_init() */
360 
sane_hpaio_exit(void)361 extern void sane_hpaio_exit(void)
362 {
363    DBG(8, "sane_hpaio_exit(): %s %d\n", __FILE__, __LINE__);
364    ResetDeviceList(&DeviceList);
365 }
366 
sane_hpaio_get_devices(const SANE_Device *** deviceList,SANE_Bool localOnly)367 extern SANE_Status sane_hpaio_get_devices(const SANE_Device ***deviceList, SANE_Bool localOnly)
368 {
369    DBG(8, "sane_hpaio_get_devices(local=%d): %s %d\n", localOnly, __FILE__, __LINE__);
370    ResetDeviceList(&DeviceList);
371    DevDiscovery(localOnly);
372    *deviceList = (const SANE_Device **)DeviceList;
373    SANE_Device*** devList;
374    orblite_get_devices(devList, localOnly);
375 
376    return SANE_STATUS_GOOD;
377 }
378 
sane_hpaio_open(SANE_String_Const devicename,SANE_Handle * pHandle)379 extern SANE_Status sane_hpaio_open(SANE_String_Const devicename, SANE_Handle * pHandle)
380 {
381     struct hpmud_model_attributes ma;
382     char devname[256];
383 
384     /* Get device attributes and determine what backend to call. */
385     snprintf(devname, sizeof(devname)-1, "hp:%s", devicename);   /* prepend "hp:" */
386     hpmud_query_model(devname, &ma);
387    DBG(8, "sane_hpaio_open(%s): %s %d scan_type=%d scansrc=%d\n", devicename, __FILE__, __LINE__, ma.scantype, ma.scansrc);
388 
389     if ((ma.scantype == HPMUD_SCANTYPE_MARVELL) || (ma.scantype == HPMUD_SCANTYPE_MARVELL2))
390        return marvell_open(devicename, pHandle);
391     if (ma.scantype == HPMUD_SCANTYPE_SOAP)
392        return soap_open(devicename, pHandle);
393     if (ma.scantype == HPMUD_SCANTYPE_SOAPHT)
394        return soapht_open(devicename, pHandle);
395     if (ma.scantype == HPMUD_SCANTYPE_LEDM)
396        return ledm_open(devicename, pHandle);
397     if ((ma.scantype == HPMUD_SCANTYPE_SCL) || (ma.scantype == HPMUD_SCANTYPE_SCL_DUPLEX) ||(ma.scantype == HPMUD_SCANTYPE_PML))
398        return sclpml_open(devicename, pHandle);
399     if (ma.scantype == HPMUD_SCANTYPE_ESCL)
400        return escl_open(devicename, pHandle);
401     if (ma.scantype == HPMUD_SCANTYPE_ORBLITE)
402        return orblite_open(devicename, pHandle);
403     else
404        return SANE_STATUS_UNSUPPORTED;
405 }   /* sane_hpaio_open() */
406 
sane_hpaio_close(SANE_Handle handle)407 extern void sane_hpaio_close(SANE_Handle handle)
408 {
409     if (strcmp(*((char **)handle), "MARVELL") == 0)
410        return marvell_close(handle);
411     if (strcmp(*((char **)handle), "SOAP") == 0)
412        return soap_close(handle);
413     if (strcmp(*((char **)handle), "SOAPHT") == 0)
414        return soapht_close(handle);
415     if (strcmp(*((char **)handle), "LEDM") == 0)
416        return ledm_close(handle);
417     if (strcmp(*((char **)handle), "SCL-PML") == 0)
418        return sclpml_close(handle);
419     if (strcmp(*((char **)handle), "ESCL") == 0)
420        return escl_close(handle);
421     if (strcmp(*((char **)handle), "ORBLITE") == 0)
422        orblite_close(handle);
423 }  /* sane_hpaio_close() */
424 
sane_hpaio_get_option_descriptor(SANE_Handle handle,SANE_Int option)425 extern const SANE_Option_Descriptor * sane_hpaio_get_option_descriptor(SANE_Handle handle, SANE_Int option)
426 {
427     if (strcmp(*((char **)handle), "MARVELL") == 0)
428        return marvell_get_option_descriptor(handle, option);
429     if (strcmp(*((char **)handle), "SOAP") == 0)
430        return soap_get_option_descriptor(handle, option);
431     if (strcmp(*((char **)handle), "SOAPHT") == 0)
432        return soapht_get_option_descriptor(handle, option);
433     if (strcmp(*((char **)handle), "LEDM") == 0)
434        return ledm_get_option_descriptor(handle, option);
435     if (strcmp(*((char **)handle), "SCL-PML") == 0)
436        return sclpml_get_option_descriptor(handle, option);
437     if (strcmp(*((char **)handle), "ESCL") == 0)
438        return escl_get_option_descriptor(handle, option);
439     if (strcmp(*((char **)handle), "ORBLITE") == 0)
440 	{
441    	  struct t_SANE* h = (struct t_SANE*)handle;
442 	  if (option < optCount || option < optLast)
443 		{
444 		DBG(8, "1. sane_hpaio_get_option_descriptor optCount = %d, option = %d, optLast = %d \n",(int)optCount,option,(int)optLast );
445           	return &h->Options[option];
446 		}
447 	  else
448 		{
449 		DBG(8, "2. sane_hpaio_get_option_descriptor optCount = %d, option = %d, optLast = %d \n",(int)optCount,option,(int)optLast );
450        		return NULL;
451 		}
452 	}
453     else
454        return NULL;
455 }  /* sane_hpaio_get_option_descriptor() */
456 
sane_hpaio_control_option(SANE_Handle handle,SANE_Int option,SANE_Action action,void * pValue,SANE_Int * pInfo)457 extern SANE_Status sane_hpaio_control_option(SANE_Handle handle, SANE_Int option, SANE_Action action, void * pValue, SANE_Int * pInfo )
458 {
459     if (strcmp(*((char **)handle), "MARVELL") == 0)
460        return marvell_control_option(handle, option, action, pValue, pInfo);
461     if (strcmp(*((char **)handle), "SOAP") == 0)
462        return soap_control_option(handle, option, action, pValue, pInfo);
463     if (strcmp(*((char **)handle), "SOAPHT") == 0)
464        return soapht_control_option(handle, option, action, pValue, pInfo);
465     if (strcmp(*((char **)handle), "LEDM") == 0)
466        return ledm_control_option(handle, option, action, pValue, pInfo);
467     if (strcmp(*((char **)handle), "SCL-PML") == 0)
468        return sclpml_control_option(handle, option, action, pValue, pInfo);
469     if (strcmp(*((char **)handle), "ESCL") == 0)
470        return escl_control_option(handle, option, action, pValue, pInfo);
471     if (strcmp(*((char **)handle), "ORBLITE") == 0)
472        return orblite_control_option(handle, option, action, pValue, pInfo);
473     else
474        return SANE_STATUS_UNSUPPORTED;
475 }   /* sane_hpaio_control_option() */
476 
sane_hpaio_get_parameters(SANE_Handle handle,SANE_Parameters * pParams)477 extern SANE_Status sane_hpaio_get_parameters(SANE_Handle handle, SANE_Parameters *pParams)
478 {
479     if (strcmp(*((char **)handle), "MARVELL") == 0)
480        return marvell_get_parameters(handle, pParams);
481     if (strcmp(*((char **)handle), "SOAP") == 0)
482        return soap_get_parameters(handle, pParams);
483     if (strcmp(*((char **)handle), "SOAPHT") == 0)
484        return soapht_get_parameters(handle, pParams);
485     if (strcmp(*((char **)handle), "LEDM") == 0)
486        return ledm_get_parameters(handle, pParams);
487     if (strcmp(*((char **)handle), "SCL-PML") == 0)
488        return sclpml_get_parameters(handle, pParams);
489     if (strcmp(*((char **)handle), "ESCL") == 0)
490        return escl_get_parameters(handle, pParams);
491     if (strcmp(*((char **)handle), "ORBLITE") == 0)
492        return orblite_get_parameters(handle, pParams);
493     else
494        return SANE_STATUS_UNSUPPORTED;
495 }  /* sane_hpaio_get_parameters() */
496 
sane_hpaio_start(SANE_Handle handle)497 extern SANE_Status sane_hpaio_start(SANE_Handle handle)
498 {
499     if (strcmp(*((char **)handle), "MARVELL") == 0)
500        return marvell_start(handle);
501     if (strcmp(*((char **)handle), "SOAP") == 0)
502        return soap_start(handle);
503     if (strcmp(*((char **)handle), "SOAPHT") == 0)
504        return soapht_start(handle);
505     if (strcmp(*((char **)handle), "LEDM") == 0)
506        return ledm_start(handle);
507     if (strcmp(*((char **)handle), "SCL-PML") == 0)
508        return sclpml_start(handle);
509     if (strcmp(*((char **)handle), "ESCL") == 0)
510        return escl_start(handle);
511     if (strcmp(*((char **)handle), "ORBLITE") == 0)
512        return orblite_start(handle);
513     else
514        return SANE_STATUS_UNSUPPORTED;
515 }   /* sane_hpaio_start() */
516 
517 
sane_hpaio_read(SANE_Handle handle,SANE_Byte * data,SANE_Int maxLength,SANE_Int * pLength)518 extern SANE_Status sane_hpaio_read(SANE_Handle handle, SANE_Byte *data, SANE_Int maxLength, SANE_Int *pLength)
519 {
520     if (strcmp(*((char **)handle), "LEDM") == 0)
521        return ledm_read(handle, data, maxLength, pLength);
522     if (strcmp(*((char **)handle), "MARVELL") == 0)
523        return marvell_read(handle, data, maxLength, pLength);
524     if (strcmp(*((char **)handle), "SOAP") == 0)
525        return soap_read(handle, data, maxLength, pLength);
526     if (strcmp(*((char **)handle), "SOAPHT") == 0)
527        return soapht_read(handle, data, maxLength, pLength);
528     if (strcmp(*((char **)handle), "SCL-PML") == 0)
529        return sclpml_read(handle, data, maxLength, pLength);
530     if (strcmp(*((char **)handle), "ESCL") == 0)
531        return escl_read(handle, data, maxLength, pLength);
532     if (strcmp(*((char **)handle), "ORBLITE") == 0)
533        return orblite_read(handle, data, maxLength, pLength);
534     else
535        return SANE_STATUS_UNSUPPORTED;
536 
537 } /* sane_hpaio_read() */
538 
539 /* Note, sane_cancel is called normally not just during IO abort situations. */
sane_hpaio_cancel(SANE_Handle handle)540 extern void sane_hpaio_cancel( SANE_Handle handle )
541 {
542     if (strcmp(*((char **)handle), "MARVELL") == 0)
543        return marvell_cancel(handle);
544     if (strcmp(*((char **)handle), "SOAP") == 0)
545        return soap_cancel(handle);
546     if (strcmp(*((char **)handle), "SOAPHT") == 0)
547        return soapht_cancel(handle);
548     if (strcmp(*((char **)handle), "LEDM") == 0)
549        return ledm_cancel(handle);
550     if (strcmp(*((char **)handle), "SCL-PML") == 0)
551        return sclpml_cancel(handle);
552     if (strcmp(*((char **)handle), "ESCL") == 0)
553        return escl_cancel(handle);
554     if (strcmp(*((char **)handle), "ORBLITE") == 0)
555        orblite_cancel(handle);
556 }  /* sane_hpaio_cancel() */
557 
sane_hpaio_set_io_mode(SANE_Handle handle,SANE_Bool nonBlocking)558 extern SANE_Status sane_hpaio_set_io_mode(SANE_Handle handle, SANE_Bool nonBlocking)
559 {
560     return SANE_STATUS_UNSUPPORTED;
561 }
562 
sane_hpaio_get_select_fd(SANE_Handle handle,SANE_Int * pFd)563 extern SANE_Status sane_hpaio_get_select_fd(SANE_Handle handle, SANE_Int *pFd)
564 {
565     return SANE_STATUS_UNSUPPORTED;
566 }
567 
568 
569