[Pcsclite-cvs-commit] r2748 - in /trunk/PCSC: configure.in src/Makefile.am src/hotplug_generic.c src/hotplug_libhal.c src/hotplug_linux.c
rousseau at users.alioth.debian.org
rousseau at users.alioth.debian.org
Mon Jan 21 09:10:58 UTC 2008
Author: rousseau
Date: Mon Jan 21 09:10:58 2008
New Revision: 2748
URL: http://svn.debian.org/wsvn/pcsclite/?sc=1&rev=2748
Log:
add libhal support to avoid polling the USB bus.
libusb is still supported but libhal is now the default
Added:
trunk/PCSC/src/hotplug_libhal.c
Modified:
trunk/PCSC/configure.in
trunk/PCSC/src/Makefile.am
trunk/PCSC/src/hotplug_generic.c
trunk/PCSC/src/hotplug_linux.c
Modified: trunk/PCSC/configure.in
URL: http://svn.debian.org/wsvn/pcsclite/trunk/PCSC/configure.in?rev=2748&op=diff
==============================================================================
--- trunk/PCSC/configure.in (original)
+++ trunk/PCSC/configure.in Mon Jan 21 09:10:58 2008
@@ -136,13 +136,56 @@
# See if socket() is found from libsocket
AC_CHECK_LIB(socket, socket, [LIBS="$LIBS -lsocket"])
+# --enable-libhal
+AC_ARG_ENABLE(libhal,
+ AC_HELP_STRING([--disable-libhal],[do not use libhal]),
+ [ use_libhal="${enableval}" ], [ use_libhal="${use_libhal-yes}" ] )
+
+# check if libhal is used
+if test "x$use_libhal" != xno ; then
+ PKG_CHECK_MODULES(LIBHAL, hal)
+
+ saved_CPPFLAGS="$CPPFLAGS"
+ saved_LIBS="$LIBS"
+
+ CPPFLAGS="$CPPFLAGS $LIBHAL_CFLAGS"
+ LIBS="$LDFLAGS $LIBHAL_LIBS"
+
+ AC_CHECK_HEADERS(libhal.h, [],
+ [ AC_MSG_ERROR([libhal.h not found, use ./configure LIBHAL_CFLAGS=...]) ])
+
+ LIBS="$LIBS $COREFOUNDATION $IOKIT"
+
+ AC_MSG_CHECKING([for libhal_ctx_new])
+ AC_TRY_LINK_FUNC(libhal_ctx_new, [ AC_MSG_RESULT([yes]) ],
+ [ AC_MSG_ERROR([libhal not found, use ./configure LIBHAL_LIBS=...]) ])
+
+ # everything needed for libhal support is present
+ use_libhal=yes
+
+ CPPFLAGS="$saved_CPPFLAGS"
+ LIBS="$saved_LIBS"
+fi
+AC_SUBST(LIBHAL_CFLAGS)
+AC_SUBST(LIBHAL_LIBS)
+
+if test x$use_libhal = xyes; then
+ AC_DEFINE(HAVE_LIBHAL, 1, [Libhal is available])
+fi
+
# --enable-libusb
AC_ARG_ENABLE(libusb,
- AC_HELP_STRING([--disable-libusb],[do not use libusb]),
- [ use_libusb="${enableval}" ], [ use_libusb="${use_libusb-yes}" ] )
+ AC_HELP_STRING([--enable-libusb],[use libusb]),
+ [ use_libusb="${enableval}" ],
+ [ if test "x$use_libhal" = xyes ; then use_libusb="${use_libusb-no}" ;
+ else use_libusb="${use_libusb-yes}" ; fi ] )
# check if libusb is used
if test "x$use_libusb" != xno ; then
+ if test "x$use_libhal" != xno ; then
+ AC_MSG_ERROR([You can't use libhal _and_ libusb. Select only one.])
+ fi
+
PKG_CHECK_MODULES(LIBUSB, libusb, [],
[
AC_MSG_RESULT([no])
@@ -187,6 +230,7 @@
if test x$use_libusb = xyes; then
AC_DEFINE(HAVE_LIBUSB, 1, [Libusb is available])
fi
+AC_MSG_RESULT([enable libhal support : $use_libhal])
AC_MSG_RESULT([enable libusb support : $use_libusb])
# --enable-usbdropdir=DIR
Modified: trunk/PCSC/src/Makefile.am
URL: http://svn.debian.org/wsvn/pcsclite/trunk/PCSC/src/Makefile.am?rev=2748&op=diff
==============================================================================
--- trunk/PCSC/src/Makefile.am (original)
+++ trunk/PCSC/src/Makefile.am Mon Jan 21 09:10:58 2008
@@ -49,6 +49,7 @@
eventhandler.h \
hotplug_generic.c \
hotplug.h \
+ hotplug_libhal.c \
hotplug_libusb.c \
hotplug_linux.c \
hotplug_macosx.c \
@@ -88,11 +89,12 @@
winscard_msg_srv.c \
winscard_svc.c \
winscard_svc.h
-pcscd_CFLAGS = $(CFLAGS) $(PTHREAD_CFLAGS) $(LIBUSB_CFLAGS) -DPCSCD
+pcscd_CFLAGS = $(CFLAGS) $(PTHREAD_CFLAGS) $(LIBUSB_CFLAGS) $(LIBHAL_CFLAGS) \
+ -DPCSCD
pcscd_LDFLAGS = $(LDFLAGS) -export-dynamic
pcscd_LDADD = \
$(PTHREAD_LIBS) $(COREFOUNDATION) \
- $(LIBUSB_LIBS) $(LIBDL) $(IOKIT) \
+ $(LIBUSB_LIBS) $(LIBDL) $(IOKIT) $(LIBHAL_LIBS) \
$(PTHREAD_LIBS) $(PTHREAD_CFLAGS)
testpcsc_SOURCES = testpcsc.c
Modified: trunk/PCSC/src/hotplug_generic.c
URL: http://svn.debian.org/wsvn/pcsclite/trunk/PCSC/src/hotplug_generic.c?rev=2748&op=diff
==============================================================================
--- trunk/PCSC/src/hotplug_generic.c (original)
+++ trunk/PCSC/src/hotplug_generic.c Mon Jan 21 09:10:58 2008
@@ -25,7 +25,7 @@
#define FALSE 0
#endif
-#if !defined(__APPLE__) && !defined(HAVE_LIBUSB) && !defined(__linux__)
+#if !defined(__APPLE__) && !defined(HAVE_LIBUSB) && !defined(__linux__) && !defined(HAVE_LIBHAL)
char ReCheckSerialReaders = FALSE;
Added: trunk/PCSC/src/hotplug_libhal.c
URL: http://svn.debian.org/wsvn/pcsclite/trunk/PCSC/src/hotplug_libhal.c?rev=2748&op=file
==============================================================================
--- trunk/PCSC/src/hotplug_libhal.c (added)
+++ trunk/PCSC/src/hotplug_libhal.c Mon Jan 21 09:10:58 2008
@@ -1,0 +1,517 @@
+/*
+ * MUSCLE SmartCard Development ( http://www.linuxnet.com )
+ *
+ * Copyright (C) 2008
+ * Ludovic Rousseau <ludovic.rousseau at free.fr>
+ *
+ * $Id:$
+ */
+
+/**
+ * @file
+ * @brief This provides a search API for hot pluggble devices using HAL/DBus
+ */
+
+#include "config.h"
+#ifdef HAVE_LIBHAL
+
+#include <string.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <libhal.h>
+
+#include "misc.h"
+#include "wintypes.h"
+#include "pcscd.h"
+#include "debuglog.h"
+#include "parser.h"
+#include "readerfactory.h"
+#include "sys_generic.h"
+#include "hotplug.h"
+#include "thread_generic.h"
+
+#undef DEBUG_HOTPLUG
+#define ADD_SERIAL_NUMBER
+
+#define FALSE 0
+#define TRUE 1
+
+#define UDI_BASE "/org/freedesktop/Hal/devices/"
+
+extern PCSCLITE_MUTEX usbNotifierMutex;
+
+static PCSCLITE_THREAD_T usbNotifyThread;
+static int driverSize = -1;
+static char AraKiriHotPlug = FALSE;
+
+static DBusConnection *conn;
+static LibHalContext *hal_ctx;
+
+/*
+ * keep track of drivers in a dynamically allocated array
+ */
+static struct _driverTracker
+{
+ unsigned int manuID;
+ unsigned int productID;
+
+ char *bundleName;
+ char *libraryPath;
+ char *readerName;
+ int ifdCapabilities;
+} *driverTracker = NULL;
+#define DRIVER_TRACKER_SIZE_STEP 8
+
+/*
+ * keep track of PCSCLITE_MAX_READERS_CONTEXTS simultaneous readers
+ */
+static struct _readerTracker
+{
+ char *udi; /* device name seen by HAL */
+ char *fullName; /* full reader name (including serial number) */
+} readerTracker[PCSCLITE_MAX_READERS_CONTEXTS];
+
+static LONG HPReadBundleValues(void);
+static void HPAddDevice(LibHalContext *ctx, const char *udi);
+static void HPRemoveDevice(LibHalContext *ctx, const char *udi);
+static void HPEstablishUSBNotifications(void);
+
+/** Generate a short name for a device
+ *
+ * @param udi Universal Device Id
+ */
+static const char *short_name(const char *udi)
+{
+ return &udi[sizeof(UDI_BASE) - 1];
+} /* short_name */
+
+
+static LONG HPReadBundleValues(void)
+{
+ LONG rv;
+ DIR *hpDir;
+ struct dirent *currFP = NULL;
+ char fullPath[FILENAME_MAX];
+ char fullLibPath[FILENAME_MAX];
+ char keyValue[TOKEN_MAX_VALUE_SIZE];
+ int listCount = 0;
+
+ hpDir = opendir(PCSCLITE_HP_DROPDIR);
+
+ if (NULL == hpDir)
+ {
+ Log1(PCSC_LOG_ERROR, "Cannot open PC/SC drivers directory: " PCSCLITE_HP_DROPDIR);
+ Log1(PCSC_LOG_ERROR, "Disabling USB support for pcscd.");
+ return -1;
+ }
+
+ /* allocate a first array */
+ driverTracker = calloc(DRIVER_TRACKER_SIZE_STEP, sizeof(*driverTracker));
+ if (NULL == driverTracker)
+ {
+ Log1(PCSC_LOG_CRITICAL, "Not enough memory");
+ return -1;
+ }
+ driverSize = DRIVER_TRACKER_SIZE_STEP;
+
+ while ((currFP = readdir(hpDir)) != 0)
+ {
+ if (strstr(currFP->d_name, ".bundle") != 0)
+ {
+ int alias = 0;
+
+ /*
+ * The bundle exists - let's form a full path name and get the
+ * vendor and product ID's for this particular bundle
+ */
+ snprintf(fullPath, sizeof(fullPath), "%s/%s/Contents/Info.plist",
+ PCSCLITE_HP_DROPDIR, currFP->d_name);
+ fullPath[sizeof(fullPath) - 1] = '\0';
+
+ /* while we find a nth ifdVendorID in Info.plist */
+ while (LTPBundleFindValueWithKey(fullPath, PCSCLITE_HP_MANUKEY_NAME,
+ keyValue, alias) == 0)
+ {
+ driverTracker[listCount].bundleName = strdup(currFP->d_name);
+
+ /* Get ifdVendorID */
+ rv = LTPBundleFindValueWithKey(fullPath,
+ PCSCLITE_HP_MANUKEY_NAME, keyValue, alias);
+ if (0 == rv)
+ driverTracker[listCount].manuID = strtol(keyValue, NULL, 16);
+
+ /* get ifdProductID */
+ rv = LTPBundleFindValueWithKey(fullPath,
+ PCSCLITE_HP_PRODKEY_NAME, keyValue, alias);
+ if (0 == rv)
+ driverTracker[listCount].productID =
+ strtol(keyValue, NULL, 16);
+
+ /* get ifdFriendlyName */
+ rv = LTPBundleFindValueWithKey(fullPath,
+ PCSCLITE_HP_NAMEKEY_NAME, keyValue, alias);
+ if (0 == rv)
+ driverTracker[listCount].readerName = strdup(keyValue);
+
+ /* get CFBundleExecutable */
+ rv = LTPBundleFindValueWithKey(fullPath,
+ PCSCLITE_HP_LIBRKEY_NAME, keyValue, 0);
+ if (0 == rv)
+ {
+ snprintf(fullLibPath, sizeof(fullLibPath),
+ "%s/%s/Contents/%s/%s",
+ PCSCLITE_HP_DROPDIR, currFP->d_name, PCSC_ARCH,
+ keyValue);
+ fullLibPath[sizeof(fullLibPath) - 1] = '\0';
+ driverTracker[listCount].libraryPath = strdup(fullLibPath);
+ }
+
+ /* Get ifdCapabilities */
+ rv = LTPBundleFindValueWithKey(fullPath,
+ PCSCLITE_HP_CPCTKEY_NAME, keyValue, 0);
+ if (0 == rv)
+ driverTracker[listCount].ifdCapabilities = strtol(keyValue,
+ NULL, 16);
+
+#ifdef DEBUG_HOTPLUG
+ Log2(PCSC_LOG_INFO, "Found driver for: %s",
+ driverTracker[listCount].readerName);
+#endif
+ alias++;
+
+ if (NULL == driverTracker[listCount].readerName)
+ continue;
+
+ listCount++;
+ if (listCount >= driverSize)
+ {
+ int i;
+
+ /* increase the array size */
+ driverSize += DRIVER_TRACKER_SIZE_STEP;
+#ifdef DEBUG_HOTPLUG
+ Log2(PCSC_LOG_INFO,
+ "Increase driverTracker to %d entries", driverSize);
+#endif
+ driverTracker = realloc(driverTracker,
+ driverSize * sizeof(*driverTracker));
+ if (NULL == driverTracker)
+ {
+ Log1(PCSC_LOG_CRITICAL, "Not enough memory");
+ driverSize = -1;
+ return -1;
+ }
+
+ /* clean the newly allocated entries */
+ for (i=driverSize-DRIVER_TRACKER_SIZE_STEP; i<driverSize; i++)
+ {
+ driverTracker[i].manuID = 0;
+ driverTracker[i].productID = 0;
+ driverTracker[i].bundleName = NULL;
+ driverTracker[i].libraryPath = NULL;
+ driverTracker[i].readerName = NULL;
+ driverTracker[i].ifdCapabilities = 0;
+ }
+ }
+ }
+ }
+ }
+
+ driverSize = listCount;
+ closedir(hpDir);
+
+#ifdef DEBUG_HOTPLUG
+ Log2(PCSC_LOG_INFO, "Found drivers for %d readers", listCount);
+#endif
+
+ return 0;
+} /* HPReadBundleValues */
+
+
+void HPEstablishUSBNotifications(void)
+{
+ while (!AraKiriHotPlug && dbus_connection_read_write_dispatch(conn, -1))
+ {
+#ifdef DEBUG_HOTPLUG
+ Log0(PCSC_LOG_INFO);
+#endif
+ }
+} /* HPEstablishUSBNotifications */
+
+
+/**
+ * Start a thread waiting for hotplug events
+ */
+LONG HPSearchHotPluggables(void)
+{
+ int i;
+
+ for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
+ {
+ readerTracker[i].udi = NULL;
+ readerTracker[i].fullName = NULL;
+ }
+
+ return HPReadBundleValues();
+} /* HPSearchHotPluggables */
+
+
+/**
+ * Stop the hotplug thread
+ */
+LONG HPStopHotPluggables(void)
+{
+ AraKiriHotPlug = TRUE;
+
+ return 0;
+} /* HPStopHotPluggables */
+
+
+static struct _driverTracker *get_driver(LibHalContext *ctx, const char *udi)
+{
+ DBusError error;
+ int i;
+ unsigned int idVendor, idProduct;
+
+ dbus_error_init(&error);
+
+ if (!libhal_device_property_exists(ctx, udi, "usb.vendor_id", NULL))
+ return NULL;
+
+ /* Vendor ID */
+ idVendor = libhal_device_get_property_int(ctx, udi,
+ "usb.vendor_id", &error);
+ if (dbus_error_is_set(&error))
+ {
+ Log3(PCSC_LOG_ERROR, "libhal_device_get_property_int %s: %d",
+ error.name, error.message);
+ dbus_error_free(&error);
+ return NULL;
+ }
+
+ /* Product ID */
+ idProduct = libhal_device_get_property_int(ctx, udi,
+ "usb.product_id", &error);
+ if (dbus_error_is_set(&error))
+ {
+ Log3(PCSC_LOG_ERROR, "libhal_device_get_property_int %s: %d",
+ error.name, error.message);
+ dbus_error_free(&error);
+ return NULL;
+ }
+
+ Log3(PCSC_LOG_DEBUG, "Looking a driver for VID: 0x%04X, PID: 0x%04X", idVendor, idProduct);
+
+ /* check if the device is supported by one driver */
+ for (i=0; i<driverSize; i++)
+ {
+ if (driverTracker[i].libraryPath != NULL &&
+ idVendor == driverTracker[i].manuID &&
+ idProduct == driverTracker[i].productID)
+ {
+ return &driverTracker[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+static void HPAddDevice(LibHalContext *ctx, const char *udi)
+{
+ int i;
+ char deviceName[MAX_DEVICENAME];
+ DBusError error;
+ struct _driverTracker *driver;
+
+ dbus_error_init (&error);
+
+ driver = get_driver(ctx, udi);
+ if (NULL == driver)
+ {
+ /* not a smart card reader */
+#ifdef DEBUG_HOTPLUG
+ Log2(PCSC_LOG_DEBUG, "%s is not a reader", short_name(udi));
+#endif
+ return;
+ }
+
+ Log2(PCSC_LOG_INFO, "Adding USB device: %s", short_name(udi));
+
+ snprintf(deviceName, sizeof(deviceName), "usb:%04x/%04x:libhal:%s",
+ driver->manuID, driver->productID, udi);
+ deviceName[sizeof(deviceName) -1] = '\0';
+
+ /* wait until the device is visible by libusb/etc. */
+ SYS_Sleep(1);
+
+ SYS_MutexLock(&usbNotifierMutex);
+
+ /* find a free entry */
+ for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
+ {
+ if (NULL == readerTracker[i].fullName)
+ break;
+ }
+
+ if (PCSCLITE_MAX_READERS_CONTEXTS == i)
+ {
+ Log2(PCSC_LOG_ERROR,
+ "Not enough reader entries. Already found %d readers", i);
+ SYS_MutexUnLock(&usbNotifierMutex);
+ return;
+ }
+
+ readerTracker[i].udi = strdup(udi);
+
+#ifdef ADD_SERIAL_NUMBER
+ if (libhal_device_property_exists(ctx, udi, "usb_device.serial", &error))
+ {
+ char fullname[MAX_READERNAME];
+ int iSerialNumber;
+
+ iSerialNumber = libhal_device_get_property_int(ctx, udi,
+ "usb_device.serial", &error);
+
+ snprintf(fullname, sizeof(fullname), "%s (%d)",
+ driver->readerName, iSerialNumber);
+ readerTracker[i].fullName = strdup(fullname);
+ }
+ else
+#endif
+ readerTracker[i].fullName = strdup(driver->readerName);
+#ifdef ADD_SERIAL_NUMBER
+ if (dbus_error_is_set(&error))
+ dbus_error_free(&error);
+#endif
+
+ RFAddReader(readerTracker[i].fullName, PCSCLITE_HP_BASE_PORT + i,
+ driver->libraryPath, deviceName);
+
+ SYS_MutexUnLock(&usbNotifierMutex);
+} /* HPAddDevice */
+
+
+static void HPRemoveDevice(LibHalContext *ctx, const char *udi)
+{
+ int i;
+
+ for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
+ {
+ if (readerTracker[i].udi && strcmp(readerTracker[i].udi, udi) == 0)
+ break;
+ }
+ if (PCSCLITE_MAX_READERS_CONTEXTS == i)
+ {
+#ifdef DEBUG_HOTPLUG
+ Log2(PCSC_LOG_DEBUG, "USB device %s not already used", short_name(udi));
+#endif
+ return;
+ }
+ Log3(PCSC_LOG_INFO, "Removing USB device[%d]: %s", i,
+ short_name(readerTracker[i].udi));
+
+ SYS_MutexLock(&usbNotifierMutex);
+
+ RFRemoveReader(readerTracker[i].fullName, PCSCLITE_HP_BASE_PORT + i);
+ free(readerTracker[i].fullName);
+ readerTracker[i].fullName = NULL;
+ free(readerTracker[i].udi);
+ readerTracker[i].udi = NULL;
+
+ SYS_MutexUnLock(&usbNotifierMutex);
+
+ return;
+} /* HPRemoveDevice */
+
+
+/*
+ * Sets up callbacks for device hotplug events.
+ */
+ULONG HPRegisterForHotplugEvents(void)
+{
+ char **device_names;
+ int i, num_devices;
+ DBusError error;
+
+ if (0 == driverSize)
+ {
+ Log1(PCSC_LOG_INFO, "No bundle files in pcsc drivers directory: " PCSCLITE_HP_DROPDIR);
+ Log1(PCSC_LOG_INFO, "Disabling USB support for pcscd");
+ return 1;
+ }
+
+ dbus_error_init(&error);
+ conn = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if (conn == NULL)
+ {
+ Log3(PCSC_LOG_ERROR, "error: dbus_bus_get: %s: %s",
+ error.name, error.message);
+ if (dbus_error_is_set(&error))
+ dbus_error_free(&error);
+ return 1;
+ }
+
+ if ((hal_ctx = libhal_ctx_new()) == NULL)
+ {
+ Log1(PCSC_LOG_ERROR, "error: libhal_ctx_new");
+ return 1;
+ }
+ if (!libhal_ctx_set_dbus_connection(hal_ctx, conn))
+ {
+ Log1(PCSC_LOG_ERROR, "error: libhal_ctx_set_dbus_connection");
+ return 1;
+ }
+ if (!libhal_ctx_init(hal_ctx, &error))
+ {
+ if (dbus_error_is_set(&error))
+ {
+ Log3(PCSC_LOG_ERROR, "error: libhal_ctx_init: %s: %s",
+ error.name, error.message);
+ if (dbus_error_is_set(&error))
+ dbus_error_free(&error);
+ }
+ Log1(PCSC_LOG_ERROR, "Could not initialise connection to hald.");
+ Log1(PCSC_LOG_ERROR, "Normally this means the HAL daemon (hald) is not running or not ready.");
+ return 1;
+ }
+
+ /* callback when device added */
+ libhal_ctx_set_device_added(hal_ctx, HPAddDevice);
+
+ /* callback when device removed */
+ libhal_ctx_set_device_removed(hal_ctx, HPRemoveDevice);
+
+ device_names = libhal_get_all_devices(hal_ctx, &num_devices, &error);
+ if (device_names == NULL)
+ {
+ if (dbus_error_is_set(&error))
+ dbus_error_free(&error);
+ Log1(PCSC_LOG_ERROR, "Couldn't obtain list of devices");
+ return 1;
+ }
+
+ /* try to add every present USB devices */
+ for (i = 0; i < num_devices; i++)
+ HPAddDevice(hal_ctx, device_names[i]);
+
+ libhal_free_string_array(device_names);
+
+ SYS_ThreadCreate(&usbNotifyThread, THREAD_ATTR_DETACHED,
+ (PCSCLITE_THREAD_FUNCTION( )) HPEstablishUSBNotifications, NULL);
+
+ return 0;
+} /* HPRegisterForHotplugEvents */
+
+
+void HPReCheckSerialReaders(void)
+{
+ /* nothing to do here */
+#ifdef DEBUG_HOTPLUG
+ Log0(PCSC_LOG_ERROR);
+#endif
+} /* HPReCheckSerialReaders */
+
+#endif
+
Modified: trunk/PCSC/src/hotplug_linux.c
URL: http://svn.debian.org/wsvn/pcsclite/trunk/PCSC/src/hotplug_linux.c?rev=2748&op=diff
==============================================================================
--- trunk/PCSC/src/hotplug_linux.c (original)
+++ trunk/PCSC/src/hotplug_linux.c Mon Jan 21 09:10:58 2008
@@ -19,7 +19,7 @@
#include "config.h"
#include <string.h>
-#if defined(__linux__) && !defined(HAVE_LIBUSB)
+#if defined(__linux__) && !defined(HAVE_LIBUSB) && !defined(HAVE_LIBHAL)
#include <sys/types.h>
#include <stdio.h>
#include <dirent.h>
More information about the Pcsclite-cvs-commit
mailing list