pcsc-lite
1.8.2
|
00001 /* 00002 * MUSCLE SmartCard Development ( http://www.linuxnet.com ) 00003 * 00004 * Copyright (C) 2011 00005 * Ludovic Rousseau <ludovic.rousseau@free.fr> 00006 * 00007 * $Id: hotplug_libudev.c 6130 2011-12-05 14:44:09Z rousseau $ 00008 */ 00009 00015 #include "config.h" 00016 #if defined(HAVE_LIBUDEV) && defined(USE_USB) 00017 00018 #include <string.h> 00019 #include <stdio.h> 00020 #include <dirent.h> 00021 #include <stdlib.h> 00022 #include <pthread.h> 00023 #include <libudev.h> 00024 00025 #include "debuglog.h" 00026 #include "parser.h" 00027 #include "readerfactory.h" 00028 #include "sys_generic.h" 00029 #include "hotplug.h" 00030 #include "utils.h" 00031 #include "strlcpycat.h" 00032 00033 #undef DEBUG_HOTPLUG 00034 #define ADD_SERIAL_NUMBER 00035 #define ADD_INTERFACE_NAME 00036 00037 #define FALSE 0 00038 #define TRUE 1 00039 00040 pthread_mutex_t usbNotifierMutex; 00041 00042 static pthread_t usbNotifyThread; 00043 static int driverSize = -1; 00044 static char AraKiriHotPlug = FALSE; 00045 00049 static struct _driverTracker 00050 { 00051 unsigned int manuID; 00052 unsigned int productID; 00053 00054 char *bundleName; 00055 char *libraryPath; 00056 char *readerName; 00057 char *CFBundleName; 00058 } *driverTracker = NULL; 00059 #define DRIVER_TRACKER_SIZE_STEP 10 00060 00061 /* The CCID driver already supports 176 readers. 00062 * We start with a big array size to avoid reallocation. */ 00063 #define DRIVER_TRACKER_INITIAL_SIZE 200 00064 00065 typedef enum { 00066 READER_ABSENT, 00067 READER_PRESENT, 00068 READER_FAILED 00069 } readerState_t; 00070 00074 static struct _readerTracker 00075 { 00076 readerState_t status; 00077 char bInterfaceNumber; 00078 char *devpath; 00079 char *fullName; 00080 } readerTracker[PCSCLITE_MAX_READERS_CONTEXTS]; 00081 00082 00083 static LONG HPReadBundleValues(void) 00084 { 00085 LONG rv; 00086 DIR *hpDir; 00087 struct dirent *currFP = NULL; 00088 char fullPath[FILENAME_MAX]; 00089 char fullLibPath[FILENAME_MAX]; 00090 int listCount = 0; 00091 00092 hpDir = opendir(PCSCLITE_HP_DROPDIR); 00093 00094 if (NULL == hpDir) 00095 { 00096 Log1(PCSC_LOG_ERROR, "Cannot open PC/SC drivers directory: " PCSCLITE_HP_DROPDIR); 00097 Log1(PCSC_LOG_ERROR, "Disabling USB support for pcscd."); 00098 return -1; 00099 } 00100 00101 /* allocate a first array */ 00102 driverSize = DRIVER_TRACKER_INITIAL_SIZE; 00103 driverTracker = calloc(driverSize, sizeof(*driverTracker)); 00104 if (NULL == driverTracker) 00105 { 00106 Log1(PCSC_LOG_CRITICAL, "Not enough memory"); 00107 (void)closedir(hpDir); 00108 return -1; 00109 } 00110 00111 #define GET_KEY(key, values) \ 00112 rv = LTPBundleFindValueWithKey(&plist, key, values); \ 00113 if (rv) \ 00114 { \ 00115 Log2(PCSC_LOG_ERROR, "Value/Key not defined for " key " in %s", \ 00116 fullPath); \ 00117 continue; \ 00118 } 00119 00120 while ((currFP = readdir(hpDir)) != 0) 00121 { 00122 if (strstr(currFP->d_name, ".bundle") != 0) 00123 { 00124 unsigned int alias; 00125 list_t plist, *values; 00126 list_t *manuIDs, *productIDs, *readerNames; 00127 char *CFBundleName; 00128 char *libraryPath; 00129 00130 /* 00131 * The bundle exists - let's form a full path name and get the 00132 * vendor and product ID's for this particular bundle 00133 */ 00134 (void)snprintf(fullPath, sizeof(fullPath), "%s/%s/Contents/Info.plist", 00135 PCSCLITE_HP_DROPDIR, currFP->d_name); 00136 fullPath[sizeof(fullPath) - 1] = '\0'; 00137 00138 rv = bundleParse(fullPath, &plist); 00139 if (rv) 00140 continue; 00141 00142 /* get CFBundleExecutable */ 00143 GET_KEY(PCSCLITE_HP_LIBRKEY_NAME, &values) 00144 libraryPath = list_get_at(values, 0); 00145 (void)snprintf(fullLibPath, sizeof(fullLibPath), 00146 "%s/%s/Contents/%s/%s", 00147 PCSCLITE_HP_DROPDIR, currFP->d_name, PCSC_ARCH, 00148 libraryPath); 00149 fullLibPath[sizeof(fullLibPath) - 1] = '\0'; 00150 00151 GET_KEY(PCSCLITE_HP_MANUKEY_NAME, &manuIDs) 00152 GET_KEY(PCSCLITE_HP_PRODKEY_NAME, &productIDs) 00153 GET_KEY(PCSCLITE_HP_NAMEKEY_NAME, &readerNames) 00154 00155 /* Get CFBundleName */ 00156 rv = LTPBundleFindValueWithKey(&plist, PCSCLITE_HP_CFBUNDLE_NAME, 00157 &values); 00158 if (rv) 00159 CFBundleName = NULL; 00160 else 00161 CFBundleName = strdup(list_get_at(values, 0)); 00162 00163 /* while we find a nth ifdVendorID in Info.plist */ 00164 for (alias=0; alias<list_size(manuIDs); alias++) 00165 { 00166 char *value; 00167 00168 /* variables entries */ 00169 value = list_get_at(manuIDs, alias); 00170 driverTracker[listCount].manuID = strtol(value, NULL, 16); 00171 00172 value = list_get_at(productIDs, alias); 00173 driverTracker[listCount].productID = strtol(value, NULL, 16); 00174 00175 driverTracker[listCount].readerName = strdup(list_get_at(readerNames, alias)); 00176 00177 /* constant entries for a same driver */ 00178 driverTracker[listCount].bundleName = strdup(currFP->d_name); 00179 driverTracker[listCount].libraryPath = strdup(fullLibPath); 00180 driverTracker[listCount].CFBundleName = CFBundleName; 00181 00182 #ifdef DEBUG_HOTPLUG 00183 Log2(PCSC_LOG_INFO, "Found driver for: %s", 00184 driverTracker[listCount].readerName); 00185 #endif 00186 listCount++; 00187 if (listCount >= driverSize) 00188 { 00189 int i; 00190 00191 /* increase the array size */ 00192 driverSize += DRIVER_TRACKER_SIZE_STEP; 00193 #ifdef DEBUG_HOTPLUG 00194 Log2(PCSC_LOG_INFO, 00195 "Increase driverTracker to %d entries", driverSize); 00196 #endif 00197 driverTracker = realloc(driverTracker, 00198 driverSize * sizeof(*driverTracker)); 00199 if (NULL == driverTracker) 00200 { 00201 Log1(PCSC_LOG_CRITICAL, "Not enough memory"); 00202 driverSize = -1; 00203 (void)closedir(hpDir); 00204 return -1; 00205 } 00206 00207 /* clean the newly allocated entries */ 00208 for (i=driverSize-DRIVER_TRACKER_SIZE_STEP; i<driverSize; i++) 00209 { 00210 driverTracker[i].manuID = 0; 00211 driverTracker[i].productID = 0; 00212 driverTracker[i].bundleName = NULL; 00213 driverTracker[i].libraryPath = NULL; 00214 driverTracker[i].readerName = NULL; 00215 driverTracker[i].CFBundleName = NULL; 00216 } 00217 } 00218 } 00219 bundleRelease(&plist); 00220 } 00221 } 00222 00223 driverSize = listCount; 00224 (void)closedir(hpDir); 00225 00226 #ifdef DEBUG_HOTPLUG 00227 Log2(PCSC_LOG_INFO, "Found drivers for %d readers", listCount); 00228 #endif 00229 00230 return 0; 00231 } /* HPReadBundleValues */ 00232 00233 00234 /*@null@*/ static struct _driverTracker *get_driver(struct udev_device *dev, 00235 const char *devpath, struct _driverTracker **classdriver) 00236 { 00237 int i; 00238 unsigned int idVendor, idProduct; 00239 static struct _driverTracker *driver; 00240 const char *str; 00241 00242 str = udev_device_get_sysattr_value(dev, "idVendor"); 00243 if (!str) 00244 { 00245 Log1(PCSC_LOG_ERROR, "udev_device_get_sysattr_value() failed"); 00246 return NULL; 00247 } 00248 idVendor = strtol(str, NULL, 16); 00249 00250 str = udev_device_get_sysattr_value(dev, "idProduct"); 00251 if (!str) 00252 { 00253 Log1(PCSC_LOG_ERROR, "udev_device_get_sysattr_value() failed"); 00254 return NULL; 00255 } 00256 idProduct = strtol(str, NULL, 16); 00257 00258 Log4(PCSC_LOG_DEBUG, 00259 "Looking for a driver for VID: 0x%04X, PID: 0x%04X, path: %s", 00260 idVendor, idProduct, devpath); 00261 00262 *classdriver = NULL; 00263 driver = NULL; 00264 /* check if the device is supported by one driver */ 00265 for (i=0; i<driverSize; i++) 00266 { 00267 if (driverTracker[i].libraryPath != NULL && 00268 idVendor == driverTracker[i].manuID && 00269 idProduct == driverTracker[i].productID) 00270 { 00271 if ((driverTracker[i].CFBundleName != NULL) 00272 && (0 == strcmp(driverTracker[i].CFBundleName, "CCIDCLASSDRIVER"))) 00273 *classdriver = &driverTracker[i]; 00274 else 00275 /* it is not a CCID Class driver */ 00276 driver = &driverTracker[i]; 00277 } 00278 } 00279 00280 /* if we found a specific driver */ 00281 if (driver) 00282 return driver; 00283 00284 /* else return the Class driver (if any) */ 00285 return *classdriver; 00286 } 00287 00288 00289 static void HPAddDevice(struct udev_device *dev, struct udev_device *parent, 00290 const char *devpath) 00291 { 00292 int i; 00293 char deviceName[MAX_DEVICENAME]; 00294 char fullname[MAX_READERNAME]; 00295 struct _driverTracker *driver, *classdriver; 00296 const char *sSerialNumber = NULL, *sInterfaceName = NULL; 00297 LONG ret; 00298 int bInterfaceNumber; 00299 00300 driver = get_driver(parent, devpath, &classdriver); 00301 if (NULL == driver) 00302 { 00303 /* not a smart card reader */ 00304 #ifdef DEBUG_HOTPLUG 00305 Log2(PCSC_LOG_DEBUG, "%s is not a supported smart card reader", 00306 devpath); 00307 #endif 00308 return; 00309 } 00310 00311 Log2(PCSC_LOG_INFO, "Adding USB device: %s", driver->readerName); 00312 00313 bInterfaceNumber = atoi(udev_device_get_sysattr_value(dev, 00314 "bInterfaceNumber")); 00315 (void)snprintf(deviceName, sizeof(deviceName), 00316 "usb:%04x/%04x:libudev:%d:%s", driver->manuID, driver->productID, 00317 bInterfaceNumber, devpath); 00318 deviceName[sizeof(deviceName) -1] = '\0'; 00319 00320 (void)pthread_mutex_lock(&usbNotifierMutex); 00321 00322 /* find a free entry */ 00323 for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++) 00324 { 00325 if (NULL == readerTracker[i].fullName) 00326 break; 00327 } 00328 00329 if (PCSCLITE_MAX_READERS_CONTEXTS == i) 00330 { 00331 Log2(PCSC_LOG_ERROR, 00332 "Not enough reader entries. Already found %d readers", i); 00333 (void)pthread_mutex_unlock(&usbNotifierMutex); 00334 return; 00335 } 00336 00337 #ifdef ADD_INTERFACE_NAME 00338 sInterfaceName = udev_device_get_sysattr_value(dev, "interface"); 00339 #endif 00340 00341 #ifdef ADD_SERIAL_NUMBER 00342 sSerialNumber = udev_device_get_sysattr_value(parent, "serial"); 00343 #endif 00344 00345 /* name from the Info.plist file */ 00346 strlcpy(fullname, driver->readerName, sizeof(fullname)); 00347 00348 /* interface name from the device (if any) */ 00349 if (sInterfaceName) 00350 { 00351 strlcat(fullname, " [", sizeof(fullname)); 00352 strlcat(fullname, sInterfaceName, sizeof(fullname)); 00353 strlcat(fullname, "]", sizeof(fullname)); 00354 } 00355 00356 /* serial number from the device (if any) */ 00357 if (sSerialNumber) 00358 { 00359 /* only add the serial number if it is not already present in the 00360 * interface name */ 00361 if (!sInterfaceName || NULL == strstr(sInterfaceName, sSerialNumber)) 00362 { 00363 strlcat(fullname, " (", sizeof(fullname)); 00364 strlcat(fullname, sSerialNumber, sizeof(fullname)); 00365 strlcat(fullname, ")", sizeof(fullname)); 00366 } 00367 } 00368 00369 readerTracker[i].fullName = strdup(fullname); 00370 readerTracker[i].devpath = strdup(devpath); 00371 readerTracker[i].status = READER_PRESENT; 00372 readerTracker[i].bInterfaceNumber = bInterfaceNumber; 00373 00374 ret = RFAddReader(fullname, PCSCLITE_HP_BASE_PORT + i, 00375 driver->libraryPath, deviceName); 00376 if ((SCARD_S_SUCCESS != ret) && (SCARD_E_UNKNOWN_READER != ret)) 00377 { 00378 Log2(PCSC_LOG_ERROR, "Failed adding USB device: %s", 00379 driver->readerName); 00380 00381 if (classdriver && driver != classdriver) 00382 { 00383 /* the reader can also be used by the a class driver */ 00384 ret = RFAddReader(fullname, PCSCLITE_HP_BASE_PORT + i, 00385 classdriver->libraryPath, deviceName); 00386 if ((SCARD_S_SUCCESS != ret) && (SCARD_E_UNKNOWN_READER != ret)) 00387 { 00388 Log2(PCSC_LOG_ERROR, "Failed adding USB device: %s", 00389 driver->readerName); 00390 00391 readerTracker[i].status = READER_FAILED; 00392 00393 (void)CheckForOpenCT(); 00394 } 00395 } 00396 else 00397 { 00398 readerTracker[i].status = READER_FAILED; 00399 00400 (void)CheckForOpenCT(); 00401 } 00402 } 00403 00404 (void)pthread_mutex_unlock(&usbNotifierMutex); 00405 } /* HPAddDevice */ 00406 00407 00408 static void HPRescanUsbBus(struct udev *udev) 00409 { 00410 int i, j; 00411 struct udev_enumerate *enumerate; 00412 struct udev_list_entry *devices, *dev_list_entry; 00413 00414 /* all reader are marked absent */ 00415 for (i=0; i < PCSCLITE_MAX_READERS_CONTEXTS; i++) 00416 readerTracker[i].status = READER_ABSENT; 00417 00418 /* Create a list of the devices in the 'usb' subsystem. */ 00419 enumerate = udev_enumerate_new(udev); 00420 udev_enumerate_add_match_subsystem(enumerate, "usb"); 00421 udev_enumerate_scan_devices(enumerate); 00422 devices = udev_enumerate_get_list_entry(enumerate); 00423 00424 /* For each item enumerated */ 00425 udev_list_entry_foreach(dev_list_entry, devices) 00426 { 00427 const char *devpath; 00428 struct udev_device *dev, *parent; 00429 struct _driverTracker *driver, *classdriver; 00430 int newreader; 00431 int bInterfaceNumber; 00432 const char *interface; 00433 00434 /* Get the filename of the /sys entry for the device 00435 and create a udev_device object (dev) representing it */ 00436 devpath = udev_list_entry_get_name(dev_list_entry); 00437 dev = udev_device_new_from_syspath(udev, devpath); 00438 00439 /* The device pointed to by dev contains information about 00440 the interface. In order to get information about the USB 00441 device, get the parent device with the subsystem/devtype pair 00442 of "usb"/"usb_device". This will be several levels up the 00443 tree, but the function will find it.*/ 00444 parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", 00445 "usb_device"); 00446 if (!parent) 00447 continue; 00448 00449 devpath = udev_device_get_devnode(parent); 00450 if (!devpath) 00451 { 00452 /* the device disapeared? */ 00453 Log1(PCSC_LOG_ERROR, "udev_device_get_devnode() failed"); 00454 continue; 00455 } 00456 00457 driver = get_driver(parent, devpath, &classdriver); 00458 if (NULL == driver) 00459 /* no driver known for this device */ 00460 continue; 00461 00462 #ifdef DEBUG_HOTPLUG 00463 Log2(PCSC_LOG_DEBUG, "Found matching USB device: %s", devpath); 00464 #endif 00465 00466 newreader = TRUE; 00467 bInterfaceNumber = 0; 00468 interface = udev_device_get_sysattr_value(dev, "bInterfaceNumber"); 00469 if (interface) 00470 bInterfaceNumber = atoi(interface); 00471 00472 /* Check if the reader is a new one */ 00473 for (j=0; j<PCSCLITE_MAX_READERS_CONTEXTS; j++) 00474 { 00475 if (readerTracker[j].devpath 00476 && (strcmp(readerTracker[j].devpath, devpath) == 0) 00477 && (bInterfaceNumber == readerTracker[j].bInterfaceNumber)) 00478 { 00479 /* The reader is already known */ 00480 readerTracker[j].status = READER_PRESENT; 00481 newreader = FALSE; 00482 #ifdef DEBUG_HOTPLUG 00483 Log2(PCSC_LOG_DEBUG, "Refresh USB device: %s", devpath); 00484 #endif 00485 break; 00486 } 00487 } 00488 00489 /* New reader found */ 00490 if (newreader) 00491 HPAddDevice(dev, parent, devpath); 00492 00493 /* free device */ 00494 udev_device_unref(dev); 00495 } 00496 00497 /* Free the enumerator object */ 00498 udev_enumerate_unref(enumerate); 00499 00500 pthread_mutex_lock(&usbNotifierMutex); 00501 /* check if all the previously found readers are still present */ 00502 for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++) 00503 { 00504 if ((READER_ABSENT == readerTracker[i].status) 00505 && (readerTracker[i].fullName != NULL)) 00506 { 00507 00508 Log3(PCSC_LOG_INFO, "Removing USB device[%d]: %s", i, 00509 readerTracker[i].devpath); 00510 00511 RFRemoveReader(readerTracker[i].fullName, 00512 PCSCLITE_HP_BASE_PORT + i); 00513 00514 readerTracker[i].status = READER_ABSENT; 00515 free(readerTracker[i].devpath); 00516 readerTracker[i].devpath = NULL; 00517 free(readerTracker[i].fullName); 00518 readerTracker[i].fullName = NULL; 00519 00520 } 00521 } 00522 pthread_mutex_unlock(&usbNotifierMutex); 00523 } 00524 00525 static void HPEstablishUSBNotifications(struct udev *udev) 00526 { 00527 struct udev_monitor *udev_monitor; 00528 int r, i; 00529 int fd; 00530 fd_set fds; 00531 00532 udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); 00533 00534 /* filter only the interfaces */ 00535 r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", 00536 "usb_interface"); 00537 if (r) 00538 { 00539 Log2(PCSC_LOG_ERROR, "udev_monitor_filter_add_match_subsystem_devtype() error: %d\n", r); 00540 return; 00541 } 00542 00543 r = udev_monitor_enable_receiving(udev_monitor); 00544 if (r) 00545 { 00546 Log2(PCSC_LOG_ERROR, "udev_monitor_enable_receiving() error: %d\n", r); 00547 return; 00548 } 00549 00550 /* udev monitor file descriptor */ 00551 fd = udev_monitor_get_fd(udev_monitor); 00552 00553 while (!AraKiriHotPlug) 00554 { 00555 struct udev_device *dev, *parent; 00556 const char *action, *devpath; 00557 00558 #ifdef DEBUG_HOTPLUG 00559 Log0(PCSC_LOG_INFO); 00560 #endif 00561 00562 FD_ZERO(&fds); 00563 FD_SET(fd, &fds); 00564 00565 /* wait for a udev event */ 00566 r = select(fd+1, &fds, NULL, NULL, NULL); 00567 if (r < 0) 00568 { 00569 Log2(PCSC_LOG_ERROR, "select(): %s", strerror(errno)); 00570 return; 00571 } 00572 00573 dev = udev_monitor_receive_device(udev_monitor); 00574 if (!dev) 00575 { 00576 Log1(PCSC_LOG_ERROR, "udev_monitor_receive_device() error\n"); 00577 return; 00578 } 00579 00580 action = udev_device_get_action(dev); 00581 if (0 == strcmp("remove", action)) 00582 { 00583 Log1(PCSC_LOG_INFO, "Device removed"); 00584 HPRescanUsbBus(udev); 00585 continue; 00586 } 00587 00588 if (strcmp("add", action)) 00589 continue; 00590 00591 parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb", 00592 "usb_device"); 00593 devpath = udev_device_get_devnode(parent); 00594 if (!devpath) 00595 { 00596 /* the device disapeared? */ 00597 Log1(PCSC_LOG_ERROR, "udev_device_get_devnode() failed"); 00598 continue; 00599 } 00600 00601 HPAddDevice(dev, parent, devpath); 00602 00603 /* free device */ 00604 udev_device_unref(dev); 00605 00606 } 00607 00608 for (i=0; i<driverSize; i++) 00609 { 00610 /* free strings allocated by strdup() */ 00611 free(driverTracker[i].bundleName); 00612 free(driverTracker[i].libraryPath); 00613 free(driverTracker[i].readerName); 00614 } 00615 free(driverTracker); 00616 00617 Log1(PCSC_LOG_INFO, "Hotplug stopped"); 00618 } /* HPEstablishUSBNotifications */ 00619 00620 00621 /*** 00622 * Start a thread waiting for hotplug events 00623 */ 00624 LONG HPSearchHotPluggables(void) 00625 { 00626 int i; 00627 00628 for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++) 00629 { 00630 readerTracker[i].status = READER_ABSENT; 00631 readerTracker[i].bInterfaceNumber = 0; 00632 readerTracker[i].devpath = NULL; 00633 readerTracker[i].fullName = NULL; 00634 } 00635 00636 return HPReadBundleValues(); 00637 } /* HPSearchHotPluggables */ 00638 00639 00643 LONG HPStopHotPluggables(void) 00644 { 00645 AraKiriHotPlug = TRUE; 00646 00647 return 0; 00648 } /* HPStopHotPluggables */ 00649 00650 00654 ULONG HPRegisterForHotplugEvents(void) 00655 { 00656 struct udev *udev; 00657 00658 (void)pthread_mutex_init(&usbNotifierMutex, NULL); 00659 00660 if (driverSize <= 0) 00661 { 00662 Log1(PCSC_LOG_INFO, "No bundle files in pcsc drivers directory: " 00663 PCSCLITE_HP_DROPDIR); 00664 Log1(PCSC_LOG_INFO, "Disabling USB support for pcscd"); 00665 return 0; 00666 } 00667 00668 /* Create the udev object */ 00669 udev = udev_new(); 00670 if (!udev) 00671 { 00672 Log1(PCSC_LOG_ERROR, "udev_new() failed"); 00673 return 0; 00674 } 00675 00676 HPRescanUsbBus(udev); 00677 00678 (void)ThreadCreate(&usbNotifyThread, THREAD_ATTR_DETACHED, 00679 (PCSCLITE_THREAD_FUNCTION( )) HPEstablishUSBNotifications, udev); 00680 00681 return 0; 00682 } /* HPRegisterForHotplugEvents */ 00683 00684 00685 void HPReCheckSerialReaders(void) 00686 { 00687 /* nothing to do here */ 00688 #ifdef DEBUG_HOTPLUG 00689 Log0(PCSC_LOG_ERROR); 00690 #endif 00691 } /* HPReCheckSerialReaders */ 00692 00693 #endif 00694