/* gpgcedrv.c - WindowsCE device driver to implement a pipe. Copyright (C) 2010 Free Software Foundation, Inc. This file is part of Assuan. Assuan is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Assuan is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . */ #include #include #include #include #include #define ENABLE_DEBUG #warning Cancel and caller process termination not handled. /* Missing IOCTLs in the current mingw32ce. */ #ifndef IOCTL_PSL_NOTIFY # define FILE_DEVICE_PSL 259 # define IOCTL_PSL_NOTIFY \ CTL_CODE (259, 255, METHOD_NEITHER, FILE_ANY_ACCESS) #endif /*IOCTL_PSL_NOTIFY*/ /* The IOCTL to return the rendezvous id of the handle. The required outbuf parameter is the address of a variable to store the rendezvous ID, which is a LONG value. */ #define GPGCEDEV_IOCTL_GET_RVID \ CTL_CODE (FILE_DEVICE_STREAMS, 2048, METHOD_BUFFERED, FILE_ANY_ACCESS) /* The IOCTL used to create the pipe. The caller sends this IOCTL to the read or the write handle. The required inbuf parameter is address of a variable holding the rendezvous id of the pipe's other end. There is one possible problem with the code: If a pipe is kept in non-rendezvous state until after the rendezvous ids overflow, it is possible that the wrong end will be used. However this is not a realistic scenario. */ #define GPGCEDEV_IOCTL_MAKE_PIPE \ CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS) /* An object to store information pertaining to an open-context. */ struct opnctx_s; typedef struct opnctx_s *opnctx_t; struct opnctx_s { int inuse; /* True if this object has valid data. */ opnctx_t assoc; /* This context has been associated with this other context; i.e. a pipe has been established. */ int is_write; /* True if this is the write end of the pipe. */ LONG rvid; /* The unique rendezvous identifier. */ DWORD access_code;/* Value from OpenFile. */ DWORD share_mode; /* Value from OpenFile. */ CRITICAL_SECTION critsect; /* Lock for all operations. */ int locked; /* True if we are in a critical section. */ /* The malloced buffer and its size. We use a buffer for each handle which allows us eventually implement a system to distribute data to several handles. Not sure whether this is really needed but as a side effect it makes the code easier. */ char *buffer; size_t buffer_size; size_t buffer_len; /* The valid length of the bufer. */ size_t buffer_pos; /* The actual read or write position. */ HANDLE space_available; /* Set if space is available. */ HANDLE data_available; /* Set if data is available. */ }; /* A malloced table of open-context and the number of allocated slots. */ static opnctx_t opnctx_table; static size_t opnctx_table_size; /* A criticial section object used to protect the OPNCTX_TABLE. */ static CRITICAL_SECTION opnctx_table_cs; /* We don't need a device context thus we use the adress of the critical section object for it. */ #define DEVCTX_VALUE ((DWORD)(&opnctx_table_cs)) /* Constants used for our lock functions. */ #define LOCK_TRY 0 #define LOCK_WAIT 1 static void log_debug (const char *fmt, ...) { #ifndef ENABLE_DEBUG (void)fmt; #else va_list arg_ptr; FILE *fp; fp = fopen ("\\gpgcedev.log", "a+"); if (!fp) return; va_start (arg_ptr, fmt); vfprintf (fp, fmt, arg_ptr); va_end (arg_ptr); fclose (fp); #endif } /* Return a new rendezvous next command id. Command Ids are used to group resources of one command. We will never return an RVID of 0. */ static LONG create_rendezvous_id (void) { static LONG rendezvous_id; LONG rvid; while (!(rvid = InterlockedIncrement (&rendezvous_id))) ; return rvid; } /* Return a new opnctx handle and mark it as used. Returns NULL and sets LastError on memory failure etc. On success the context is locked. */ static opnctx_t get_new_opnctx (void) { opnctx_t opnctx = NULL; int idx; EnterCriticalSection (&opnctx_table_cs); for (idx=0; idx < opnctx_table_size; idx++) if (!opnctx_table[idx].inuse) break; if (idx == opnctx_table_size) { /* We need to increase the size of the table. The approach we take is straightforward to minimize the risk of bugs. */ opnctx_t newtbl; size_t newsize = opnctx_table_size + 64; newtbl = calloc (newsize, sizeof *newtbl); if (!newtbl) goto leave; for (idx=0; idx < opnctx_table_size; idx++) newtbl[idx] = opnctx_table[idx]; free (opnctx_table); opnctx_table = newtbl; idx = opnctx_table_size; opnctx_table_size = newsize; } opnctx = opnctx_table + idx; opnctx->assoc = NULL; opnctx->rvid = create_rendezvous_id (); opnctx->is_write = 0; opnctx->access_code = 0; opnctx->share_mode = 0; InitializeCriticalSection (&opnctx->critsect); opnctx->locked = 0; opnctx->buffer_size = 512; opnctx->buffer = malloc (opnctx->buffer_size); if (!opnctx->buffer) { opnctx = NULL; goto leave; } opnctx->buffer_len = 0; opnctx->buffer_pos = 0; opnctx->data_available = INVALID_HANDLE_VALUE; opnctx->space_available = INVALID_HANDLE_VALUE; opnctx->inuse = 1; EnterCriticalSection (&opnctx->critsect); opnctx->locked = 1; leave: LeaveCriticalSection (&opnctx_table_cs); if (opnctx) log_debug ("get_new_opnctx -> %p (rvid=%ld)\n", opnctx, opnctx->rvid); else log_debug ("get_new_opnctx -> failed\n"); return opnctx; } /* Find the OPNCTX object with the rendezvous id RVID. */ static opnctx_t find_and_lock_opnctx (LONG rvid) { opnctx_t result = NULL; int idx; EnterCriticalSection (&opnctx_table_cs); for (idx=0; idx < opnctx_table_size; idx++) if (opnctx_table[idx].inuse && opnctx_table[idx].rvid == rvid) { result = opnctx_table + idx; break; } LeaveCriticalSection (&opnctx_table_cs); if (!result) { SetLastError (ERROR_INVALID_HANDLE); log_debug ("find_opnctx -> invalid rendezvous id\n"); } else if (TryEnterCriticalSection (&result->critsect)) { result->locked++; log_debug ("find_opnctx -> %p (rvid=%ld)\n", result, result->rvid); } else { SetLastError (ERROR_BUSY); result = NULL; log_debug ("find_opnctx -> busy\n"); } return result; } /* Check that OPNCTX is valid. Returns TRUE if it is valid or FALSE if it is a bad or closed contect. In the latter case SetLastError is called. In the former case a lock is taken and unlock_opnctx needs to be called. If WAIT is false the fucntion only tries to acquire a lock. */ static BOOL validate_and_lock_opnctx (opnctx_t opnctx, int wait) { BOOL result = FALSE; int idx; EnterCriticalSection (&opnctx_table_cs); for (idx=0; idx < opnctx_table_size; idx++) if (opnctx_table[idx].inuse && (opnctx_table + idx) == opnctx) { result = TRUE; break; } LeaveCriticalSection (&opnctx_table_cs); if (!result) SetLastError (ERROR_INVALID_HANDLE); else if (wait) { EnterCriticalSection (&opnctx->critsect); opnctx->locked++; } else if (TryEnterCriticalSection (&opnctx->critsect)) opnctx->locked++; else { SetLastError (ERROR_BUSY); result = FALSE; } return result; } static void unlock_opnctx (opnctx_t opnctx) { opnctx->locked--; LeaveCriticalSection (&opnctx->critsect); } static char * wchar_to_utf8 (const wchar_t *string) { int n; size_t length = wcslen (string); char *result; n = WideCharToMultiByte (CP_UTF8, 0, string, length, NULL, 0, NULL, NULL); if (n < 0 || (n+1) <= 0) abort (); result = malloc (n+1); if (!result) abort (); n = WideCharToMultiByte (CP_ACP, 0, string, length, result, n, NULL, NULL); if (n < 0) abort (); result[n] = 0; return result; } /* Initialize the device and return a device specific context. */ DWORD GPG_Init (LPCTSTR active_key, DWORD bus_context) { char *tmpbuf; (void)bus_context; tmpbuf = wchar_to_utf8 (active_key); log_debug ("GPG_Init (%s)\n", tmpbuf); free (tmpbuf); /* We don't need any global data. However, we need to return something. */ return DEVCTX_VALUE; } /* Deinitialize this device driver. */ BOOL GPG_Deinit (DWORD devctx) { log_debug ("GPG_Deinit (%p)\n", (void*)devctx); if (devctx != DEVCTX_VALUE) { SetLastError (ERROR_INVALID_PARAMETER); return FALSE; /* Error. */ } /* FIXME: Release resources. */ return TRUE; /* Success. */ } /* Create a new open context. This fucntion is called due to a CreateFile from the application. */ DWORD GPG_Open (DWORD devctx, DWORD access_code, DWORD share_mode) { opnctx_t opnctx; log_debug ("GPG_Open(devctx=%p)\n", (void*)devctx); if (devctx != DEVCTX_VALUE) { SetLastError (ERROR_INVALID_PARAMETER); return 0; /* Error. */ } opnctx = get_new_opnctx (); if (!opnctx) return 0; opnctx->access_code = access_code; opnctx->share_mode = share_mode; unlock_opnctx (opnctx); return (DWORD)opnctx; } BOOL GPG_Close (DWORD opnctx_arg) { opnctx_t opnctx = (opnctx_t)opnctx_arg; BOOL result = FALSE; int idx; log_debug ("GPG_Close(%p)\n", (void*)opnctx); EnterCriticalSection (&opnctx_table_cs); for (idx=0; idx < opnctx_table_size; idx++) if (opnctx_table[idx].inuse && (opnctx_table + idx) == opnctx) { if (opnctx->assoc) { opnctx->assoc->assoc = NULL; opnctx->assoc = NULL; } if (opnctx->locked) { /* FIXME: Check earlier or use close only in locked state or use PReClose. */ log_debug ("GPG_Close while still locked\n"); } DeleteCriticalSection (&opnctx->critsect); if (opnctx->buffer) { free (opnctx->buffer); opnctx->buffer = NULL; opnctx->buffer_size = 0; } if (opnctx->space_available != INVALID_HANDLE_VALUE) { CloseHandle (opnctx->space_available); opnctx->space_available = INVALID_HANDLE_VALUE; } if (opnctx->data_available != INVALID_HANDLE_VALUE) { CloseHandle (opnctx->data_available); opnctx->data_available = INVALID_HANDLE_VALUE; } opnctx->inuse = 0; result = TRUE; break; } LeaveCriticalSection (&opnctx_table_cs); if (!result) SetLastError (ERROR_INVALID_HANDLE); return result; } DWORD GPG_Read (DWORD opnctx_arg, void *buffer, DWORD count) { opnctx_t rctx = (opnctx_t)opnctx_arg; opnctx_t wctx; int result = -1; const char *src; char *dst; log_debug ("GPG_Read(%p, count=%d)\n", (void*)rctx, count); /* We use the write end's buffer, thus there is no need to wait for our (read end) lock. */ if (!validate_and_lock_opnctx (rctx, LOCK_TRY)) return -1; /* Error. */ if (rctx->is_write) { SetLastError (ERROR_INVALID_ACCESS); log_debug ("GPG_Read(%p) -> invalid access\n", (void*)rctx); goto leave; } if (!rctx->assoc) { SetLastError (ERROR_PIPE_NOT_CONNECTED); goto leave; } /* Read from the corresponding write buffer. */ retry: wctx = rctx->assoc; if (!validate_and_lock_opnctx (wctx, LOCK_WAIT)) goto leave; if (wctx->buffer_pos == wctx->buffer_len) { unlock_opnctx (wctx); log_debug ("%s:%d: WFSO(data_available)\n", __func__, __LINE__); WaitForSingleObject (wctx->data_available, INFINITE); log_debug ("%s:%d: WFSO ... woke up\n", __func__, __LINE__); goto retry; } dst = buffer; src = wctx->buffer + wctx->buffer_pos; while (count > 0 && wctx->buffer_pos < wctx->buffer_len) { *dst++ = *src++; count--; wctx->buffer_pos++; } result = (dst - (char*)buffer); if (wctx->buffer_pos == wctx->buffer_len) wctx->buffer_pos = wctx->buffer_len = 0; /* Now there should be some space available. Signal the write end. Even if COUNT was passed as NULL and no space is available, signaling must be done. */ if (!SetEvent (wctx->space_available)) { log_debug ("%s:%d: SetEvent(space_available) failed: rc=%d\n", __func__, __LINE__, (int)GetLastError ()); unlock_opnctx (wctx); goto leave; } unlock_opnctx (wctx); leave: unlock_opnctx (rctx); return result; } DWORD GPG_Write (DWORD opnctx_arg, const void *buffer, DWORD count) { opnctx_t wctx = (opnctx_t)opnctx_arg; int result = -1; const char *src; char *dst; size_t nwritten = 0; log_debug ("GPG_Write(%p, count=%d)\n", (void*)wctx, count); retry: if (!validate_and_lock_opnctx (wctx, LOCK_WAIT)) return -1; /* Error. */ if (!wctx->is_write) { SetLastError (ERROR_INVALID_ACCESS); log_debug ("GPG_Write(%p) -> invalid access\n", (void*)wctx); goto leave; } if (!wctx->assoc) { SetLastError (ERROR_PIPE_NOT_CONNECTED); goto leave; } if (!count) { result = 0; goto leave; } /* Write to our buffer. */ if (wctx->buffer_len == wctx->buffer_size) { /* Buffer is full. */ unlock_opnctx (wctx); log_debug ("%s:%d: WFSO(space_available)\n", __func__, __LINE__); WaitForSingleObject (wctx->space_available, INFINITE); log_debug ("%s:%d: WFSO ... woke up\n", __func__, __LINE__); goto retry; } src = buffer; dst = wctx->buffer + wctx->buffer_len; while (count > 0 && wctx->buffer_len < wctx->buffer_size) { *dst++ = *src++; count--; wctx->buffer_len++; nwritten++; } if (!SetEvent (wctx->data_available)) { log_debug ("%s:%d: SetEvent(data_available) failed: rc=%d\n", __func__, __LINE__, (int)GetLastError ()); goto leave; } result = nwritten; leave: unlock_opnctx (wctx); return result; } DWORD GPG_Seek (DWORD opnctx, long amount, WORD type) { SetLastError (ERROR_SEEK_ON_DEVICE); return -1; /* Error. */ } static BOOL make_pipe (opnctx_t ctx, LONG rvid) { BOOL result = FALSE; opnctx_t peerctx = NULL; log_debug (" make_pipe(%p, rvid=%ld)\n", ctx, rvid); if (ctx->assoc) { SetLastError (ERROR_ALREADY_ASSIGNED); goto leave; } peerctx = find_and_lock_opnctx (rvid); if (!peerctx) { SetLastError (ERROR_NOT_FOUND); goto leave; } if (peerctx == ctx) { SetLastError (ERROR_INVALID_TARGET_HANDLE); goto leave; } if (peerctx->assoc) { SetLastError (ERROR_ALREADY_ASSIGNED); goto leave; } if ((ctx->access_code & GENERIC_READ)) { /* Check that the peer is a write end. */ if (!(peerctx->access_code & GENERIC_WRITE)) { SetLastError (ERROR_INVALID_ACCESS); log_debug (" make_pipe(%p) write end -> invalid access\n", ctx); goto leave; } peerctx->space_available = CreateEvent (NULL, FALSE, FALSE, NULL); peerctx->data_available = CreateEvent (NULL, FALSE, FALSE, NULL); ctx->assoc = peerctx; peerctx->assoc = ctx; ctx->is_write = 0; peerctx->is_write = 1; result = TRUE; } else if ((ctx->access_code & GENERIC_WRITE)) { /* Check that the peer is a read end. */ if (!(peerctx->access_code & GENERIC_READ)) { SetLastError (ERROR_INVALID_ACCESS); log_debug (" make_pipe(%p) read_end -> invalid access\n", ctx); goto leave; } ctx->space_available = CreateEvent (NULL, FALSE, FALSE, NULL); ctx->data_available = CreateEvent (NULL, FALSE, FALSE, NULL); ctx->assoc = peerctx; peerctx->assoc = ctx; ctx->is_write = 1; peerctx->is_write = 0; result = TRUE; } else { SetLastError (ERROR_INVALID_ACCESS); log_debug (" make_pipe(%p) no_access -> invalid access\n", ctx); goto leave; } leave: if (peerctx) unlock_opnctx (peerctx); return result; } BOOL GPG_IOControl (DWORD opnctx_arg, DWORD code, void *inbuf, DWORD inbuflen, void *outbuf, DWORD outbuflen, DWORD *actualoutlen) { opnctx_t opnctx = (opnctx_t)opnctx_arg; BOOL result = FALSE; LONG rvid; log_debug ("GPG_IOControl(%p, %d)\n", (void*)opnctx, code); if (!validate_and_lock_opnctx (opnctx, LOCK_TRY)) return FALSE; switch (code) { case GPGCEDEV_IOCTL_GET_RVID: if (!opnctx || inbuf || inbuflen || !outbuf || outbuflen < sizeof (LONG)) { SetLastError (ERROR_INVALID_PARAMETER); goto leave; } memcpy (outbuf, &opnctx->rvid, sizeof (LONG)); if (actualoutlen) *actualoutlen = sizeof (LONG); result = TRUE; break; case GPGCEDEV_IOCTL_MAKE_PIPE: if (!opnctx || !inbuf || inbuflen < sizeof (LONG) || outbuf || outbuflen || actualoutlen ) { SetLastError (ERROR_INVALID_PARAMETER); goto leave; } memcpy (&rvid, inbuf, sizeof (LONG)); if (make_pipe (opnctx, rvid)) result = TRUE; break; case IOCTL_PSL_NOTIFY: /* Unexpected process termination. */ break; default: SetLastError (ERROR_INVALID_PARAMETER); break; } leave: unlock_opnctx (opnctx); return result; } void GPG_PowerUp (DWORD devctx) { } void GPG_PowerDown (DWORD devctx) { } /* Entry point called by the DLL loader. */ int WINAPI DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved) { (void)reserved; switch (reason) { case DLL_PROCESS_ATTACH: InitializeCriticalSection (&opnctx_table_cs); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: DeleteCriticalSection (&opnctx_table_cs); break; default: break; } return TRUE; }