| /* |
| ** 2006 Feb 14 |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** May you do good and not evil. |
| ** May you find forgiveness for yourself and forgive others. |
| ** May you share freely, never taking more than you give. |
| ** |
| ****************************************************************************** |
| ** |
| ** This file contains code that is specific to OS/2. |
| */ |
| |
| #include "sqliteInt.h" |
| |
| #if SQLITE_OS_OS2 |
| |
| /* |
| ** A Note About Memory Allocation: |
| ** |
| ** This driver uses malloc()/free() directly rather than going through |
| ** the SQLite-wrappers sqlite3_malloc()/sqlite3_free(). Those wrappers |
| ** are designed for use on embedded systems where memory is scarce and |
| ** malloc failures happen frequently. OS/2 does not typically run on |
| ** embedded systems, and when it does the developers normally have bigger |
| ** problems to worry about than running out of memory. So there is not |
| ** a compelling need to use the wrappers. |
| ** |
| ** But there is a good reason to not use the wrappers. If we use the |
| ** wrappers then we will get simulated malloc() failures within this |
| ** driver. And that causes all kinds of problems for our tests. We |
| ** could enhance SQLite to deal with simulated malloc failures within |
| ** the OS driver, but the code to deal with those failure would not |
| ** be exercised on Linux (which does not need to malloc() in the driver) |
| ** and so we would have difficulty writing coverage tests for that |
| ** code. Better to leave the code out, we think. |
| ** |
| ** The point of this discussion is as follows: When creating a new |
| ** OS layer for an embedded system, if you use this file as an example, |
| ** avoid the use of malloc()/free(). Those routines work ok on OS/2 |
| ** desktops but not so well in embedded systems. |
| */ |
| |
| /* |
| ** Macros used to determine whether or not to use threads. |
| */ |
| #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE |
| # define SQLITE_OS2_THREADS 1 |
| #endif |
| |
| /* |
| ** Include code that is common to all os_*.c files |
| */ |
| #include "os_common.h" |
| |
| /* |
| ** The os2File structure is subclass of sqlite3_file specific for the OS/2 |
| ** protability layer. |
| */ |
| typedef struct os2File os2File; |
| struct os2File { |
| const sqlite3_io_methods *pMethod; /* Always the first entry */ |
| HFILE h; /* Handle for accessing the file */ |
| char* pathToDel; /* Name of file to delete on close, NULL if not */ |
| unsigned char locktype; /* Type of lock currently held on this file */ |
| }; |
| |
| #define LOCK_TIMEOUT 10L /* the default locking timeout */ |
| |
| /***************************************************************************** |
| ** The next group of routines implement the I/O methods specified |
| ** by the sqlite3_io_methods object. |
| ******************************************************************************/ |
| |
| /* |
| ** Close a file. |
| */ |
| static int os2Close( sqlite3_file *id ){ |
| APIRET rc = NO_ERROR; |
| os2File *pFile; |
| if( id && (pFile = (os2File*)id) != 0 ){ |
| OSTRACE2( "CLOSE %d\n", pFile->h ); |
| rc = DosClose( pFile->h ); |
| pFile->locktype = NO_LOCK; |
| if( pFile->pathToDel != NULL ){ |
| rc = DosForceDelete( (PSZ)pFile->pathToDel ); |
| free( pFile->pathToDel ); |
| pFile->pathToDel = NULL; |
| } |
| id = 0; |
| OpenCounter( -1 ); |
| } |
| |
| return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR; |
| } |
| |
| /* |
| ** Read data from a file into a buffer. Return SQLITE_OK if all |
| ** bytes were read successfully and SQLITE_IOERR if anything goes |
| ** wrong. |
| */ |
| static int os2Read( |
| sqlite3_file *id, /* File to read from */ |
| void *pBuf, /* Write content into this buffer */ |
| int amt, /* Number of bytes to read */ |
| sqlite3_int64 offset /* Begin reading at this offset */ |
| ){ |
| ULONG fileLocation = 0L; |
| ULONG got; |
| os2File *pFile = (os2File*)id; |
| assert( id!=0 ); |
| SimulateIOError( return SQLITE_IOERR_READ ); |
| OSTRACE3( "READ %d lock=%d\n", pFile->h, pFile->locktype ); |
| if( DosSetFilePtr(pFile->h, offset, FILE_BEGIN, &fileLocation) != NO_ERROR ){ |
| return SQLITE_IOERR; |
| } |
| if( DosRead( pFile->h, pBuf, amt, &got ) != NO_ERROR ){ |
| return SQLITE_IOERR_READ; |
| } |
| if( got == (ULONG)amt ) |
| return SQLITE_OK; |
| else { |
| /* Unread portions of the input buffer must be zero-filled */ |
| memset(&((char*)pBuf)[got], 0, amt-got); |
| return SQLITE_IOERR_SHORT_READ; |
| } |
| } |
| |
| /* |
| ** Write data from a buffer into a file. Return SQLITE_OK on success |
| ** or some other error code on failure. |
| */ |
| static int os2Write( |
| sqlite3_file *id, /* File to write into */ |
| const void *pBuf, /* The bytes to be written */ |
| int amt, /* Number of bytes to write */ |
| sqlite3_int64 offset /* Offset into the file to begin writing at */ |
| ){ |
| ULONG fileLocation = 0L; |
| APIRET rc = NO_ERROR; |
| ULONG wrote; |
| os2File *pFile = (os2File*)id; |
| assert( id!=0 ); |
| SimulateIOError( return SQLITE_IOERR_WRITE ); |
| SimulateDiskfullError( return SQLITE_FULL ); |
| OSTRACE3( "WRITE %d lock=%d\n", pFile->h, pFile->locktype ); |
| if( DosSetFilePtr(pFile->h, offset, FILE_BEGIN, &fileLocation) != NO_ERROR ){ |
| return SQLITE_IOERR; |
| } |
| assert( amt>0 ); |
| while( amt > 0 && |
| ( rc = DosWrite( pFile->h, (PVOID)pBuf, amt, &wrote ) ) == NO_ERROR && |
| wrote > 0 |
| ){ |
| amt -= wrote; |
| pBuf = &((char*)pBuf)[wrote]; |
| } |
| |
| return ( rc != NO_ERROR || amt > (int)wrote ) ? SQLITE_FULL : SQLITE_OK; |
| } |
| |
| /* |
| ** Truncate an open file to a specified size |
| */ |
| static int os2Truncate( sqlite3_file *id, i64 nByte ){ |
| APIRET rc = NO_ERROR; |
| os2File *pFile = (os2File*)id; |
| OSTRACE3( "TRUNCATE %d %lld\n", pFile->h, nByte ); |
| SimulateIOError( return SQLITE_IOERR_TRUNCATE ); |
| rc = DosSetFileSize( pFile->h, nByte ); |
| return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR_TRUNCATE; |
| } |
| |
| #ifdef SQLITE_TEST |
| /* |
| ** Count the number of fullsyncs and normal syncs. This is used to test |
| ** that syncs and fullsyncs are occuring at the right times. |
| */ |
| int sqlite3_sync_count = 0; |
| int sqlite3_fullsync_count = 0; |
| #endif |
| |
| /* |
| ** Make sure all writes to a particular file are committed to disk. |
| */ |
| static int os2Sync( sqlite3_file *id, int flags ){ |
| os2File *pFile = (os2File*)id; |
| OSTRACE3( "SYNC %d lock=%d\n", pFile->h, pFile->locktype ); |
| #ifdef SQLITE_TEST |
| if( flags & SQLITE_SYNC_FULL){ |
| sqlite3_fullsync_count++; |
| } |
| sqlite3_sync_count++; |
| #endif |
| /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a |
| ** no-op |
| */ |
| #ifdef SQLITE_NO_SYNC |
| UNUSED_PARAMETER(pFile); |
| return SQLITE_OK; |
| #else |
| return DosResetBuffer( pFile->h ) == NO_ERROR ? SQLITE_OK : SQLITE_IOERR; |
| #endif |
| } |
| |
| /* |
| ** Determine the current size of a file in bytes |
| */ |
| static int os2FileSize( sqlite3_file *id, sqlite3_int64 *pSize ){ |
| APIRET rc = NO_ERROR; |
| FILESTATUS3 fsts3FileInfo; |
| memset(&fsts3FileInfo, 0, sizeof(fsts3FileInfo)); |
| assert( id!=0 ); |
| SimulateIOError( return SQLITE_IOERR_FSTAT ); |
| rc = DosQueryFileInfo( ((os2File*)id)->h, FIL_STANDARD, &fsts3FileInfo, sizeof(FILESTATUS3) ); |
| if( rc == NO_ERROR ){ |
| *pSize = fsts3FileInfo.cbFile; |
| return SQLITE_OK; |
| }else{ |
| return SQLITE_IOERR_FSTAT; |
| } |
| } |
| |
| /* |
| ** Acquire a reader lock. |
| */ |
| static int getReadLock( os2File *pFile ){ |
| FILELOCK LockArea, |
| UnlockArea; |
| APIRET res; |
| memset(&LockArea, 0, sizeof(LockArea)); |
| memset(&UnlockArea, 0, sizeof(UnlockArea)); |
| LockArea.lOffset = SHARED_FIRST; |
| LockArea.lRange = SHARED_SIZE; |
| UnlockArea.lOffset = 0L; |
| UnlockArea.lRange = 0L; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 1L ); |
| OSTRACE3( "GETREADLOCK %d res=%d\n", pFile->h, res ); |
| return res; |
| } |
| |
| /* |
| ** Undo a readlock |
| */ |
| static int unlockReadLock( os2File *id ){ |
| FILELOCK LockArea, |
| UnlockArea; |
| APIRET res; |
| memset(&LockArea, 0, sizeof(LockArea)); |
| memset(&UnlockArea, 0, sizeof(UnlockArea)); |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = SHARED_FIRST; |
| UnlockArea.lRange = SHARED_SIZE; |
| res = DosSetFileLocks( id->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 1L ); |
| OSTRACE3( "UNLOCK-READLOCK file handle=%d res=%d?\n", id->h, res ); |
| return res; |
| } |
| |
| /* |
| ** Lock the file with the lock specified by parameter locktype - one |
| ** of the following: |
| ** |
| ** (1) SHARED_LOCK |
| ** (2) RESERVED_LOCK |
| ** (3) PENDING_LOCK |
| ** (4) EXCLUSIVE_LOCK |
| ** |
| ** Sometimes when requesting one lock state, additional lock states |
| ** are inserted in between. The locking might fail on one of the later |
| ** transitions leaving the lock state different from what it started but |
| ** still short of its goal. The following chart shows the allowed |
| ** transitions and the inserted intermediate states: |
| ** |
| ** UNLOCKED -> SHARED |
| ** SHARED -> RESERVED |
| ** SHARED -> (PENDING) -> EXCLUSIVE |
| ** RESERVED -> (PENDING) -> EXCLUSIVE |
| ** PENDING -> EXCLUSIVE |
| ** |
| ** This routine will only increase a lock. The os2Unlock() routine |
| ** erases all locks at once and returns us immediately to locking level 0. |
| ** It is not possible to lower the locking level one step at a time. You |
| ** must go straight to locking level 0. |
| */ |
| static int os2Lock( sqlite3_file *id, int locktype ){ |
| int rc = SQLITE_OK; /* Return code from subroutines */ |
| APIRET res = NO_ERROR; /* Result of an OS/2 lock call */ |
| int newLocktype; /* Set pFile->locktype to this value before exiting */ |
| int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ |
| FILELOCK LockArea, |
| UnlockArea; |
| os2File *pFile = (os2File*)id; |
| memset(&LockArea, 0, sizeof(LockArea)); |
| memset(&UnlockArea, 0, sizeof(UnlockArea)); |
| assert( pFile!=0 ); |
| OSTRACE4( "LOCK %d %d was %d\n", pFile->h, locktype, pFile->locktype ); |
| |
| /* If there is already a lock of this type or more restrictive on the |
| ** os2File, do nothing. Don't use the end_lock: exit path, as |
| ** sqlite3_mutex_enter() hasn't been called yet. |
| */ |
| if( pFile->locktype>=locktype ){ |
| OSTRACE3( "LOCK %d %d ok (already held)\n", pFile->h, locktype ); |
| return SQLITE_OK; |
| } |
| |
| /* Make sure the locking sequence is correct |
| */ |
| assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); |
| assert( locktype!=PENDING_LOCK ); |
| assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); |
| |
| /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or |
| ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of |
| ** the PENDING_LOCK byte is temporary. |
| */ |
| newLocktype = pFile->locktype; |
| if( pFile->locktype==NO_LOCK |
| || (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK) |
| ){ |
| LockArea.lOffset = PENDING_BYTE; |
| LockArea.lRange = 1L; |
| UnlockArea.lOffset = 0L; |
| UnlockArea.lRange = 0L; |
| |
| /* wait longer than LOCK_TIMEOUT here not to have to try multiple times */ |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, 100L, 0L ); |
| if( res == NO_ERROR ){ |
| gotPendingLock = 1; |
| OSTRACE3( "LOCK %d pending lock boolean set. res=%d\n", pFile->h, res ); |
| } |
| } |
| |
| /* Acquire a shared lock |
| */ |
| if( locktype==SHARED_LOCK && res == NO_ERROR ){ |
| assert( pFile->locktype==NO_LOCK ); |
| res = getReadLock(pFile); |
| if( res == NO_ERROR ){ |
| newLocktype = SHARED_LOCK; |
| } |
| OSTRACE3( "LOCK %d acquire shared lock. res=%d\n", pFile->h, res ); |
| } |
| |
| /* Acquire a RESERVED lock |
| */ |
| if( locktype==RESERVED_LOCK && res == NO_ERROR ){ |
| assert( pFile->locktype==SHARED_LOCK ); |
| LockArea.lOffset = RESERVED_BYTE; |
| LockArea.lRange = 1L; |
| UnlockArea.lOffset = 0L; |
| UnlockArea.lRange = 0L; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| if( res == NO_ERROR ){ |
| newLocktype = RESERVED_LOCK; |
| } |
| OSTRACE3( "LOCK %d acquire reserved lock. res=%d\n", pFile->h, res ); |
| } |
| |
| /* Acquire a PENDING lock |
| */ |
| if( locktype==EXCLUSIVE_LOCK && res == NO_ERROR ){ |
| newLocktype = PENDING_LOCK; |
| gotPendingLock = 0; |
| OSTRACE2( "LOCK %d acquire pending lock. pending lock boolean unset.\n", pFile->h ); |
| } |
| |
| /* Acquire an EXCLUSIVE lock |
| */ |
| if( locktype==EXCLUSIVE_LOCK && res == NO_ERROR ){ |
| assert( pFile->locktype>=SHARED_LOCK ); |
| res = unlockReadLock(pFile); |
| OSTRACE2( "unreadlock = %d\n", res ); |
| LockArea.lOffset = SHARED_FIRST; |
| LockArea.lRange = SHARED_SIZE; |
| UnlockArea.lOffset = 0L; |
| UnlockArea.lRange = 0L; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| if( res == NO_ERROR ){ |
| newLocktype = EXCLUSIVE_LOCK; |
| }else{ |
| OSTRACE2( "OS/2 error-code = %d\n", res ); |
| getReadLock(pFile); |
| } |
| OSTRACE3( "LOCK %d acquire exclusive lock. res=%d\n", pFile->h, res ); |
| } |
| |
| /* If we are holding a PENDING lock that ought to be released, then |
| ** release it now. |
| */ |
| if( gotPendingLock && locktype==SHARED_LOCK ){ |
| int r; |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = PENDING_BYTE; |
| UnlockArea.lRange = 1L; |
| r = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "LOCK %d unlocking pending/is shared. r=%d\n", pFile->h, r ); |
| } |
| |
| /* Update the state of the lock has held in the file descriptor then |
| ** return the appropriate result code. |
| */ |
| if( res == NO_ERROR ){ |
| rc = SQLITE_OK; |
| }else{ |
| OSTRACE4( "LOCK FAILED %d trying for %d but got %d\n", pFile->h, |
| locktype, newLocktype ); |
| rc = SQLITE_BUSY; |
| } |
| pFile->locktype = newLocktype; |
| OSTRACE3( "LOCK %d now %d\n", pFile->h, pFile->locktype ); |
| return rc; |
| } |
| |
| /* |
| ** This routine checks if there is a RESERVED lock held on the specified |
| ** file by this or any other process. If such a lock is held, return |
| ** non-zero, otherwise zero. |
| */ |
| static int os2CheckReservedLock( sqlite3_file *id, int *pOut ){ |
| int r = 0; |
| os2File *pFile = (os2File*)id; |
| assert( pFile!=0 ); |
| if( pFile->locktype>=RESERVED_LOCK ){ |
| r = 1; |
| OSTRACE3( "TEST WR-LOCK %d %d (local)\n", pFile->h, r ); |
| }else{ |
| FILELOCK LockArea, |
| UnlockArea; |
| APIRET rc = NO_ERROR; |
| memset(&LockArea, 0, sizeof(LockArea)); |
| memset(&UnlockArea, 0, sizeof(UnlockArea)); |
| LockArea.lOffset = RESERVED_BYTE; |
| LockArea.lRange = 1L; |
| UnlockArea.lOffset = 0L; |
| UnlockArea.lRange = 0L; |
| rc = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "TEST WR-LOCK %d lock reserved byte rc=%d\n", pFile->h, rc ); |
| if( rc == NO_ERROR ){ |
| APIRET rcu = NO_ERROR; /* return code for unlocking */ |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = RESERVED_BYTE; |
| UnlockArea.lRange = 1L; |
| rcu = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "TEST WR-LOCK %d unlock reserved byte r=%d\n", pFile->h, rcu ); |
| } |
| r = !(rc == NO_ERROR); |
| OSTRACE3( "TEST WR-LOCK %d %d (remote)\n", pFile->h, r ); |
| } |
| *pOut = r; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Lower the locking level on file descriptor id to locktype. locktype |
| ** must be either NO_LOCK or SHARED_LOCK. |
| ** |
| ** If the locking level of the file descriptor is already at or below |
| ** the requested locking level, this routine is a no-op. |
| ** |
| ** It is not possible for this routine to fail if the second argument |
| ** is NO_LOCK. If the second argument is SHARED_LOCK then this routine |
| ** might return SQLITE_IOERR; |
| */ |
| static int os2Unlock( sqlite3_file *id, int locktype ){ |
| int type; |
| os2File *pFile = (os2File*)id; |
| APIRET rc = SQLITE_OK; |
| APIRET res = NO_ERROR; |
| FILELOCK LockArea, |
| UnlockArea; |
| memset(&LockArea, 0, sizeof(LockArea)); |
| memset(&UnlockArea, 0, sizeof(UnlockArea)); |
| assert( pFile!=0 ); |
| assert( locktype<=SHARED_LOCK ); |
| OSTRACE4( "UNLOCK %d to %d was %d\n", pFile->h, locktype, pFile->locktype ); |
| type = pFile->locktype; |
| if( type>=EXCLUSIVE_LOCK ){ |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = SHARED_FIRST; |
| UnlockArea.lRange = SHARED_SIZE; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "UNLOCK %d exclusive lock res=%d\n", pFile->h, res ); |
| if( locktype==SHARED_LOCK && getReadLock(pFile) != NO_ERROR ){ |
| /* This should never happen. We should always be able to |
| ** reacquire the read lock */ |
| OSTRACE3( "UNLOCK %d to %d getReadLock() failed\n", pFile->h, locktype ); |
| rc = SQLITE_IOERR_UNLOCK; |
| } |
| } |
| if( type>=RESERVED_LOCK ){ |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = RESERVED_BYTE; |
| UnlockArea.lRange = 1L; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "UNLOCK %d reserved res=%d\n", pFile->h, res ); |
| } |
| if( locktype==NO_LOCK && type>=SHARED_LOCK ){ |
| res = unlockReadLock(pFile); |
| OSTRACE5( "UNLOCK %d is %d want %d res=%d\n", pFile->h, type, locktype, res ); |
| } |
| if( type>=PENDING_LOCK ){ |
| LockArea.lOffset = 0L; |
| LockArea.lRange = 0L; |
| UnlockArea.lOffset = PENDING_BYTE; |
| UnlockArea.lRange = 1L; |
| res = DosSetFileLocks( pFile->h, &UnlockArea, &LockArea, LOCK_TIMEOUT, 0L ); |
| OSTRACE3( "UNLOCK %d pending res=%d\n", pFile->h, res ); |
| } |
| pFile->locktype = locktype; |
| OSTRACE3( "UNLOCK %d now %d\n", pFile->h, pFile->locktype ); |
| return rc; |
| } |
| |
| /* |
| ** Control and query of the open file handle. |
| */ |
| static int os2FileControl(sqlite3_file *id, int op, void *pArg){ |
| switch( op ){ |
| case SQLITE_FCNTL_LOCKSTATE: { |
| *(int*)pArg = ((os2File*)id)->locktype; |
| OSTRACE3( "FCNTL_LOCKSTATE %d lock=%d\n", ((os2File*)id)->h, ((os2File*)id)->locktype ); |
| return SQLITE_OK; |
| } |
| } |
| return SQLITE_ERROR; |
| } |
| |
| /* |
| ** Return the sector size in bytes of the underlying block device for |
| ** the specified file. This is almost always 512 bytes, but may be |
| ** larger for some devices. |
| ** |
| ** SQLite code assumes this function cannot fail. It also assumes that |
| ** if two files are created in the same file-system directory (i.e. |
| ** a database and its journal file) that the sector size will be the |
| ** same for both. |
| */ |
| static int os2SectorSize(sqlite3_file *id){ |
| return SQLITE_DEFAULT_SECTOR_SIZE; |
| } |
| |
| /* |
| ** Return a vector of device characteristics. |
| */ |
| static int os2DeviceCharacteristics(sqlite3_file *id){ |
| return 0; |
| } |
| |
| |
| /* |
| ** Character set conversion objects used by conversion routines. |
| */ |
| static UconvObject ucUtf8 = NULL; /* convert between UTF-8 and UCS-2 */ |
| static UconvObject uclCp = NULL; /* convert between local codepage and UCS-2 */ |
| |
| /* |
| ** Helper function to initialize the conversion objects from and to UTF-8. |
| */ |
| static void initUconvObjects( void ){ |
| if( UniCreateUconvObject( UTF_8, &ucUtf8 ) != ULS_SUCCESS ) |
| ucUtf8 = NULL; |
| if ( UniCreateUconvObject( (UniChar *)L"@path=yes", &uclCp ) != ULS_SUCCESS ) |
| uclCp = NULL; |
| } |
| |
| /* |
| ** Helper function to free the conversion objects from and to UTF-8. |
| */ |
| static void freeUconvObjects( void ){ |
| if ( ucUtf8 ) |
| UniFreeUconvObject( ucUtf8 ); |
| if ( uclCp ) |
| UniFreeUconvObject( uclCp ); |
| ucUtf8 = NULL; |
| uclCp = NULL; |
| } |
| |
| /* |
| ** Helper function to convert UTF-8 filenames to local OS/2 codepage. |
| ** The two-step process: first convert the incoming UTF-8 string |
| ** into UCS-2 and then from UCS-2 to the current codepage. |
| ** The returned char pointer has to be freed. |
| */ |
| static char *convertUtf8PathToCp( const char *in ){ |
| UniChar tempPath[CCHMAXPATH]; |
| char *out = (char *)calloc( CCHMAXPATH, 1 ); |
| |
| if( !out ) |
| return NULL; |
| |
| if( !ucUtf8 || !uclCp ) |
| initUconvObjects(); |
| |
| /* determine string for the conversion of UTF-8 which is CP1208 */ |
| if( UniStrToUcs( ucUtf8, tempPath, (char *)in, CCHMAXPATH ) != ULS_SUCCESS ) |
| return out; /* if conversion fails, return the empty string */ |
| |
| /* conversion for current codepage which can be used for paths */ |
| UniStrFromUcs( uclCp, out, tempPath, CCHMAXPATH ); |
| |
| return out; |
| } |
| |
| /* |
| ** Helper function to convert filenames from local codepage to UTF-8. |
| ** The two-step process: first convert the incoming codepage-specific |
| ** string into UCS-2 and then from UCS-2 to the codepage of UTF-8. |
| ** The returned char pointer has to be freed. |
| ** |
| ** This function is non-static to be able to use this in shell.c and |
| ** similar applications that take command line arguments. |
| */ |
| char *convertCpPathToUtf8( const char *in ){ |
| UniChar tempPath[CCHMAXPATH]; |
| char *out = (char *)calloc( CCHMAXPATH, 1 ); |
| |
| if( !out ) |
| return NULL; |
| |
| if( !ucUtf8 || !uclCp ) |
| initUconvObjects(); |
| |
| /* conversion for current codepage which can be used for paths */ |
| if( UniStrToUcs( uclCp, tempPath, (char *)in, CCHMAXPATH ) != ULS_SUCCESS ) |
| return out; /* if conversion fails, return the empty string */ |
| |
| /* determine string for the conversion of UTF-8 which is CP1208 */ |
| UniStrFromUcs( ucUtf8, out, tempPath, CCHMAXPATH ); |
| |
| return out; |
| } |
| |
| /* |
| ** This vector defines all the methods that can operate on an |
| ** sqlite3_file for os2. |
| */ |
| static const sqlite3_io_methods os2IoMethod = { |
| 1, /* iVersion */ |
| os2Close, |
| os2Read, |
| os2Write, |
| os2Truncate, |
| os2Sync, |
| os2FileSize, |
| os2Lock, |
| os2Unlock, |
| os2CheckReservedLock, |
| os2FileControl, |
| os2SectorSize, |
| os2DeviceCharacteristics |
| }; |
| |
| /*************************************************************************** |
| ** Here ends the I/O methods that form the sqlite3_io_methods object. |
| ** |
| ** The next block of code implements the VFS methods. |
| ****************************************************************************/ |
| |
| /* |
| ** Create a temporary file name in zBuf. zBuf must be big enough to |
| ** hold at pVfs->mxPathname characters. |
| */ |
| static int getTempname(int nBuf, char *zBuf ){ |
| static const unsigned char zChars[] = |
| "abcdefghijklmnopqrstuvwxyz" |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| "0123456789"; |
| int i, j; |
| char zTempPathBuf[3]; |
| PSZ zTempPath = (PSZ)&zTempPathBuf; |
| if( sqlite3_temp_directory ){ |
| zTempPath = sqlite3_temp_directory; |
| }else{ |
| if( DosScanEnv( (PSZ)"TEMP", &zTempPath ) ){ |
| if( DosScanEnv( (PSZ)"TMP", &zTempPath ) ){ |
| if( DosScanEnv( (PSZ)"TMPDIR", &zTempPath ) ){ |
| ULONG ulDriveNum = 0, ulDriveMap = 0; |
| DosQueryCurrentDisk( &ulDriveNum, &ulDriveMap ); |
| sprintf( (char*)zTempPath, "%c:", (char)( 'A' + ulDriveNum - 1 ) ); |
| } |
| } |
| } |
| } |
| /* Strip off a trailing slashes or backslashes, otherwise we would get * |
| * multiple (back)slashes which causes DosOpen() to fail. * |
| * Trailing spaces are not allowed, either. */ |
| j = sqlite3Strlen30(zTempPath); |
| while( j > 0 && ( zTempPath[j-1] == '\\' || zTempPath[j-1] == '/' |
| || zTempPath[j-1] == ' ' ) ){ |
| j--; |
| } |
| zTempPath[j] = '\0'; |
| if( !sqlite3_temp_directory ){ |
| char *zTempPathUTF = convertCpPathToUtf8( zTempPath ); |
| sqlite3_snprintf( nBuf-30, zBuf, |
| "%s\\"SQLITE_TEMP_FILE_PREFIX, zTempPathUTF ); |
| free( zTempPathUTF ); |
| }else{ |
| sqlite3_snprintf( nBuf-30, zBuf, |
| "%s\\"SQLITE_TEMP_FILE_PREFIX, zTempPath ); |
| } |
| j = sqlite3Strlen30( zBuf ); |
| sqlite3_randomness( 20, &zBuf[j] ); |
| for( i = 0; i < 20; i++, j++ ){ |
| zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; |
| } |
| zBuf[j] = 0; |
| OSTRACE2( "TEMP FILENAME: %s\n", zBuf ); |
| return SQLITE_OK; |
| } |
| |
| |
| /* |
| ** Turn a relative pathname into a full pathname. Write the full |
| ** pathname into zFull[]. zFull[] will be at least pVfs->mxPathname |
| ** bytes in size. |
| */ |
| static int os2FullPathname( |
| sqlite3_vfs *pVfs, /* Pointer to vfs object */ |
| const char *zRelative, /* Possibly relative input path */ |
| int nFull, /* Size of output buffer in bytes */ |
| char *zFull /* Output buffer */ |
| ){ |
| char *zRelativeCp = convertUtf8PathToCp( zRelative ); |
| char zFullCp[CCHMAXPATH] = "\0"; |
| char *zFullUTF; |
| APIRET rc = DosQueryPathInfo( zRelativeCp, FIL_QUERYFULLNAME, zFullCp, |
| CCHMAXPATH ); |
| free( zRelativeCp ); |
| zFullUTF = convertCpPathToUtf8( zFullCp ); |
| sqlite3_snprintf( nFull, zFull, zFullUTF ); |
| free( zFullUTF ); |
| return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR; |
| } |
| |
| |
| /* |
| ** Open a file. |
| */ |
| static int os2Open( |
| sqlite3_vfs *pVfs, /* Not used */ |
| const char *zName, /* Name of the file */ |
| sqlite3_file *id, /* Write the SQLite file handle here */ |
| int flags, /* Open mode flags */ |
| int *pOutFlags /* Status return flags */ |
| ){ |
| HFILE h; |
| ULONG ulFileAttribute = FILE_NORMAL; |
| ULONG ulOpenFlags = 0; |
| ULONG ulOpenMode = 0; |
| os2File *pFile = (os2File*)id; |
| APIRET rc = NO_ERROR; |
| ULONG ulAction; |
| char *zNameCp; |
| char zTmpname[CCHMAXPATH+1]; /* Buffer to hold name of temp file */ |
| |
| /* If the second argument to this function is NULL, generate a |
| ** temporary file name to use |
| */ |
| if( !zName ){ |
| int rc = getTempname(CCHMAXPATH+1, zTmpname); |
| if( rc!=SQLITE_OK ){ |
| return rc; |
| } |
| zName = zTmpname; |
| } |
| |
| |
| memset( pFile, 0, sizeof(*pFile) ); |
| |
| OSTRACE2( "OPEN want %d\n", flags ); |
| |
| if( flags & SQLITE_OPEN_READWRITE ){ |
| ulOpenMode |= OPEN_ACCESS_READWRITE; |
| OSTRACE1( "OPEN read/write\n" ); |
| }else{ |
| ulOpenMode |= OPEN_ACCESS_READONLY; |
| OSTRACE1( "OPEN read only\n" ); |
| } |
| |
| if( flags & SQLITE_OPEN_CREATE ){ |
| ulOpenFlags |= OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_CREATE_IF_NEW; |
| OSTRACE1( "OPEN open new/create\n" ); |
| }else{ |
| ulOpenFlags |= OPEN_ACTION_OPEN_IF_EXISTS | OPEN_ACTION_FAIL_IF_NEW; |
| OSTRACE1( "OPEN open existing\n" ); |
| } |
| |
| if( flags & SQLITE_OPEN_MAIN_DB ){ |
| ulOpenMode |= OPEN_SHARE_DENYNONE; |
| OSTRACE1( "OPEN share read/write\n" ); |
| }else{ |
| ulOpenMode |= OPEN_SHARE_DENYWRITE; |
| OSTRACE1( "OPEN share read only\n" ); |
| } |
| |
| if( flags & SQLITE_OPEN_DELETEONCLOSE ){ |
| char pathUtf8[CCHMAXPATH]; |
| #ifdef NDEBUG /* when debugging we want to make sure it is deleted */ |
| ulFileAttribute = FILE_HIDDEN; |
| #endif |
| os2FullPathname( pVfs, zName, CCHMAXPATH, pathUtf8 ); |
| pFile->pathToDel = convertUtf8PathToCp( pathUtf8 ); |
| OSTRACE1( "OPEN hidden/delete on close file attributes\n" ); |
| }else{ |
| pFile->pathToDel = NULL; |
| OSTRACE1( "OPEN normal file attribute\n" ); |
| } |
| |
| /* always open in random access mode for possibly better speed */ |
| ulOpenMode |= OPEN_FLAGS_RANDOM; |
| ulOpenMode |= OPEN_FLAGS_FAIL_ON_ERROR; |
| ulOpenMode |= OPEN_FLAGS_NOINHERIT; |
| |
| zNameCp = convertUtf8PathToCp( zName ); |
| rc = DosOpen( (PSZ)zNameCp, |
| &h, |
| &ulAction, |
| 0L, |
| ulFileAttribute, |
| ulOpenFlags, |
| ulOpenMode, |
| (PEAOP2)NULL ); |
| free( zNameCp ); |
| if( rc != NO_ERROR ){ |
| OSTRACE7( "OPEN Invalid handle rc=%d: zName=%s, ulAction=%#lx, ulAttr=%#lx, ulFlags=%#lx, ulMode=%#lx\n", |
| rc, zName, ulAction, ulFileAttribute, ulOpenFlags, ulOpenMode ); |
| if( pFile->pathToDel ) |
| free( pFile->pathToDel ); |
| pFile->pathToDel = NULL; |
| if( flags & SQLITE_OPEN_READWRITE ){ |
| OSTRACE2( "OPEN %d Invalid handle\n", ((flags | SQLITE_OPEN_READONLY) & ~SQLITE_OPEN_READWRITE) ); |
| return os2Open( pVfs, zName, id, |
| ((flags | SQLITE_OPEN_READONLY) & ~SQLITE_OPEN_READWRITE), |
| pOutFlags ); |
| }else{ |
| return SQLITE_CANTOPEN; |
| } |
| } |
| |
| if( pOutFlags ){ |
| *pOutFlags = flags & SQLITE_OPEN_READWRITE ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY; |
| } |
| |
| pFile->pMethod = &os2IoMethod; |
| pFile->h = h; |
| OpenCounter(+1); |
| OSTRACE3( "OPEN %d pOutFlags=%d\n", pFile->h, pOutFlags ); |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** Delete the named file. |
| */ |
| static int os2Delete( |
| sqlite3_vfs *pVfs, /* Not used on os2 */ |
| const char *zFilename, /* Name of file to delete */ |
| int syncDir /* Not used on os2 */ |
| ){ |
| APIRET rc = NO_ERROR; |
| char *zFilenameCp = convertUtf8PathToCp( zFilename ); |
| SimulateIOError( return SQLITE_IOERR_DELETE ); |
| rc = DosDelete( (PSZ)zFilenameCp ); |
| free( zFilenameCp ); |
| OSTRACE2( "DELETE \"%s\"\n", zFilename ); |
| return rc == NO_ERROR ? SQLITE_OK : SQLITE_IOERR_DELETE; |
| } |
| |
| /* |
| ** Check the existance and status of a file. |
| */ |
| static int os2Access( |
| sqlite3_vfs *pVfs, /* Not used on os2 */ |
| const char *zFilename, /* Name of file to check */ |
| int flags, /* Type of test to make on this file */ |
| int *pOut /* Write results here */ |
| ){ |
| FILESTATUS3 fsts3ConfigInfo; |
| APIRET rc = NO_ERROR; |
| char *zFilenameCp = convertUtf8PathToCp( zFilename ); |
| |
| memset( &fsts3ConfigInfo, 0, sizeof(fsts3ConfigInfo) ); |
| rc = DosQueryPathInfo( (PSZ)zFilenameCp, FIL_STANDARD, |
| &fsts3ConfigInfo, sizeof(FILESTATUS3) ); |
| free( zFilenameCp ); |
| OSTRACE4( "ACCESS fsts3ConfigInfo.attrFile=%d flags=%d rc=%d\n", |
| fsts3ConfigInfo.attrFile, flags, rc ); |
| switch( flags ){ |
| case SQLITE_ACCESS_READ: |
| case SQLITE_ACCESS_EXISTS: |
| rc = (rc == NO_ERROR); |
| OSTRACE3( "ACCESS %s access of read and exists rc=%d\n", zFilename, rc ); |
| break; |
| case SQLITE_ACCESS_READWRITE: |
| rc = (rc == NO_ERROR) && ( (fsts3ConfigInfo.attrFile & FILE_READONLY) == 0 ); |
| OSTRACE3( "ACCESS %s access of read/write rc=%d\n", zFilename, rc ); |
| break; |
| default: |
| assert( !"Invalid flags argument" ); |
| } |
| *pOut = rc; |
| return SQLITE_OK; |
| } |
| |
| |
| #ifndef SQLITE_OMIT_LOAD_EXTENSION |
| /* |
| ** Interfaces for opening a shared library, finding entry points |
| ** within the shared library, and closing the shared library. |
| */ |
| /* |
| ** Interfaces for opening a shared library, finding entry points |
| ** within the shared library, and closing the shared library. |
| */ |
| static void *os2DlOpen(sqlite3_vfs *pVfs, const char *zFilename){ |
| UCHAR loadErr[256]; |
| HMODULE hmod; |
| APIRET rc; |
| char *zFilenameCp = convertUtf8PathToCp(zFilename); |
| rc = DosLoadModule((PSZ)loadErr, sizeof(loadErr), zFilenameCp, &hmod); |
| free(zFilenameCp); |
| return rc != NO_ERROR ? 0 : (void*)hmod; |
| } |
| /* |
| ** A no-op since the error code is returned on the DosLoadModule call. |
| ** os2Dlopen returns zero if DosLoadModule is not successful. |
| */ |
| static void os2DlError(sqlite3_vfs *pVfs, int nBuf, char *zBufOut){ |
| /* no-op */ |
| } |
| static void *os2DlSym(sqlite3_vfs *pVfs, void *pHandle, const char *zSymbol){ |
| PFN pfn; |
| APIRET rc; |
| rc = DosQueryProcAddr((HMODULE)pHandle, 0L, zSymbol, &pfn); |
| if( rc != NO_ERROR ){ |
| /* if the symbol itself was not found, search again for the same |
| * symbol with an extra underscore, that might be needed depending |
| * on the calling convention */ |
| char _zSymbol[256] = "_"; |
| strncat(_zSymbol, zSymbol, 255); |
| rc = DosQueryProcAddr((HMODULE)pHandle, 0L, _zSymbol, &pfn); |
| } |
| return rc != NO_ERROR ? 0 : (void*)pfn; |
| } |
| static void os2DlClose(sqlite3_vfs *pVfs, void *pHandle){ |
| DosFreeModule((HMODULE)pHandle); |
| } |
| #else /* if SQLITE_OMIT_LOAD_EXTENSION is defined: */ |
| #define os2DlOpen 0 |
| #define os2DlError 0 |
| #define os2DlSym 0 |
| #define os2DlClose 0 |
| #endif |
| |
| |
| /* |
| ** Write up to nBuf bytes of randomness into zBuf. |
| */ |
| static int os2Randomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf ){ |
| int n = 0; |
| #if defined(SQLITE_TEST) |
| n = nBuf; |
| memset(zBuf, 0, nBuf); |
| #else |
| int sizeofULong = sizeof(ULONG); |
| if( (int)sizeof(DATETIME) <= nBuf - n ){ |
| DATETIME x; |
| DosGetDateTime(&x); |
| memcpy(&zBuf[n], &x, sizeof(x)); |
| n += sizeof(x); |
| } |
| |
| if( sizeofULong <= nBuf - n ){ |
| PPIB ppib; |
| DosGetInfoBlocks(NULL, &ppib); |
| memcpy(&zBuf[n], &ppib->pib_ulpid, sizeofULong); |
| n += sizeofULong; |
| } |
| |
| if( sizeofULong <= nBuf - n ){ |
| PTIB ptib; |
| DosGetInfoBlocks(&ptib, NULL); |
| memcpy(&zBuf[n], &ptib->tib_ptib2->tib2_ultid, sizeofULong); |
| n += sizeofULong; |
| } |
| |
| /* if we still haven't filled the buffer yet the following will */ |
| /* grab everything once instead of making several calls for a single item */ |
| if( sizeofULong <= nBuf - n ){ |
| ULONG ulSysInfo[QSV_MAX]; |
| DosQuerySysInfo(1L, QSV_MAX, ulSysInfo, sizeofULong * QSV_MAX); |
| |
| memcpy(&zBuf[n], &ulSysInfo[QSV_MS_COUNT - 1], sizeofULong); |
| n += sizeofULong; |
| |
| if( sizeofULong <= nBuf - n ){ |
| memcpy(&zBuf[n], &ulSysInfo[QSV_TIMER_INTERVAL - 1], sizeofULong); |
| n += sizeofULong; |
| } |
| if( sizeofULong <= nBuf - n ){ |
| memcpy(&zBuf[n], &ulSysInfo[QSV_TIME_LOW - 1], sizeofULong); |
| n += sizeofULong; |
| } |
| if( sizeofULong <= nBuf - n ){ |
| memcpy(&zBuf[n], &ulSysInfo[QSV_TIME_HIGH - 1], sizeofULong); |
| n += sizeofULong; |
| } |
| if( sizeofULong <= nBuf - n ){ |
| memcpy(&zBuf[n], &ulSysInfo[QSV_TOTAVAILMEM - 1], sizeofULong); |
| n += sizeofULong; |
| } |
| } |
| #endif |
| |
| return n; |
| } |
| |
| /* |
| ** Sleep for a little while. Return the amount of time slept. |
| ** The argument is the number of microseconds we want to sleep. |
| ** The return value is the number of microseconds of sleep actually |
| ** requested from the underlying operating system, a number which |
| ** might be greater than or equal to the argument, but not less |
| ** than the argument. |
| */ |
| static int os2Sleep( sqlite3_vfs *pVfs, int microsec ){ |
| DosSleep( (microsec/1000) ); |
| return microsec; |
| } |
| |
| /* |
| ** The following variable, if set to a non-zero value, becomes the result |
| ** returned from sqlite3OsCurrentTime(). This is used for testing. |
| */ |
| #ifdef SQLITE_TEST |
| int sqlite3_current_time = 0; |
| #endif |
| |
| /* |
| ** Find the current time (in Universal Coordinated Time). Write the |
| ** current time and date as a Julian Day number into *prNow and |
| ** return 0. Return 1 if the time and date cannot be found. |
| */ |
| int os2CurrentTime( sqlite3_vfs *pVfs, double *prNow ){ |
| double now; |
| SHORT minute; /* needs to be able to cope with negative timezone offset */ |
| USHORT second, hour, |
| day, month, year; |
| DATETIME dt; |
| DosGetDateTime( &dt ); |
| second = (USHORT)dt.seconds; |
| minute = (SHORT)dt.minutes + dt.timezone; |
| hour = (USHORT)dt.hours; |
| day = (USHORT)dt.day; |
| month = (USHORT)dt.month; |
| year = (USHORT)dt.year; |
| |
| /* Calculations from http://www.astro.keele.ac.uk/~rno/Astronomy/hjd.html |
| http://www.astro.keele.ac.uk/~rno/Astronomy/hjd-0.1.c */ |
| /* Calculate the Julian days */ |
| now = day - 32076 + |
| 1461*(year + 4800 + (month - 14)/12)/4 + |
| 367*(month - 2 - (month - 14)/12*12)/12 - |
| 3*((year + 4900 + (month - 14)/12)/100)/4; |
| |
| /* Add the fractional hours, mins and seconds */ |
| now += (hour + 12.0)/24.0; |
| now += minute/1440.0; |
| now += second/86400.0; |
| *prNow = now; |
| #ifdef SQLITE_TEST |
| if( sqlite3_current_time ){ |
| *prNow = sqlite3_current_time/86400.0 + 2440587.5; |
| } |
| #endif |
| return 0; |
| } |
| |
| static int os2GetLastError(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ |
| return 0; |
| } |
| |
| /* |
| ** Initialize and deinitialize the operating system interface. |
| */ |
| int sqlite3_os_init(void){ |
| static sqlite3_vfs os2Vfs = { |
| 1, /* iVersion */ |
| sizeof(os2File), /* szOsFile */ |
| CCHMAXPATH, /* mxPathname */ |
| 0, /* pNext */ |
| "os2", /* zName */ |
| 0, /* pAppData */ |
| |
| os2Open, /* xOpen */ |
| os2Delete, /* xDelete */ |
| os2Access, /* xAccess */ |
| os2FullPathname, /* xFullPathname */ |
| os2DlOpen, /* xDlOpen */ |
| os2DlError, /* xDlError */ |
| os2DlSym, /* xDlSym */ |
| os2DlClose, /* xDlClose */ |
| os2Randomness, /* xRandomness */ |
| os2Sleep, /* xSleep */ |
| os2CurrentTime, /* xCurrentTime */ |
| os2GetLastError /* xGetLastError */ |
| 0, /* xShmOpen */ |
| 0, /* xShmSize */ |
| 0, /* xShmPush */ |
| 0, /* xShmPull */ |
| 0, /* xShmLock */ |
| 0, /* xShmClose */ |
| 0, /* xShmDelete */ |
| 0, /* xRename */ |
| 0, /* xCurrentTimeInt64 */ |
| }; |
| sqlite3_vfs_register(&os2Vfs, 1); |
| initUconvObjects(); |
| return SQLITE_OK; |
| } |
| int sqlite3_os_end(void){ |
| freeUconvObjects(); |
| return SQLITE_OK; |
| } |
| |
| #endif /* SQLITE_OS_OS2 */ |