Index: file_io/unix/flock.c =================================================================== --- file_io/unix/flock.c (revision 453079) +++ file_io/unix/flock.c (working copy) @@ -16,22 +16,103 @@ #include "apr_arch_file_io.h" +/* Mac OS X (Darwin) does not support file locking with fcntl(2) when the file + * resides on an SMB network share. Attempting to do so will result in an error + * of ENOTSUP, which indicates that the operation is not supported. + * + * Code has been added below for Mac OS X (Darwin) that will detect if the file + * being locked resides on an SMB network share. If so, file locking will be + * done with flock(2) instead, which works almost as expected. + * + * The only caveat with using flock(2) with a file on an SMB share is that the + * lock will always act like an exclusive lock, even when a shared lock is + * requested. This has some implications in a multi-user environment since the + * file with the supposedly shared lock will be unavailable until released. + * + * A bigger problem with the shared lock acting like an exclusive lock appears + * with some software (notably Subversion) that performs recursive shared locks. + * The first 'shared' lock succeeds, but the second will fail. To alleviate this + * problem, the code implements a scheme of using temporary files that track the + * 'shared' locks and thus allows obtaining of recursive shared locks. + * + * -- Brian D. Wells + */ +#if defined(DARWIN) +#include "apr_strings.h" +#include "apr_pools.h" +#include "apr_user.h" +#include +#include +#include +#include +#include +#include +#include +#else #if APR_HAVE_FCNTL_H #include #endif #ifdef HAVE_SYS_FILE_H #include #endif +#endif /* not DARWIN */ +#if defined(DARWIN) +#define DARWIN_FLOCK_PREFIX "darwin.flock.smbfs" +#define DARWIN_FLOCK_DATA_VERSION 1 + +typedef struct darwin_flock_data_block { + apr_off_t previousblockoffset; + apr_off_t nextblockoffset; + apr_size_t keysize; + apr_off_t keyoffset; + apr_size_t datasize; + apr_off_t dataoffset; +} darwin_flock_data_block_t; + +typedef struct darwin_flock_data_header { + int version; + apr_off_t firstblockoffset; + apr_off_t reserved1; + apr_size_t reserved2; +} darwin_flock_data_header_t; + +typedef struct darwin_flock_data_info { + apr_file_t *datafile; + apr_pool_t *datapool; + apr_pool_t *temppool; + darwin_flock_data_header_t *dataheader; +} darwin_flock_data_info_t; + +apr_status_t darwin_file_lock(apr_file_t *thefile, int type); +apr_status_t darwin_lock_fcntl(apr_file_t *thefile, int type); +apr_status_t darwin_lock_flock(apr_file_t *thefile, int type); +apr_status_t darwin_file_unlock(apr_file_t *thefile); +apr_status_t darwin_unlock_fcntl(apr_file_t *thefile); +apr_status_t darwin_unlock_flock(apr_file_t *thefile); + +apr_status_t darwin_flock_data_init(darwin_flock_data_info_t **datainfo, + apr_file_t *shared, apr_pool_t *pool); +apr_status_t darwin_flock_data_cleanup(darwin_flock_data_info_t *datainfo); +apr_status_t darwin_flock_data_get(void **data, const char *key, + darwin_flock_data_info_t *datainfo); +apr_status_t darwin_flock_data_set(const void *data, apr_size_t size, + const char *key, + darwin_flock_data_info_t *datainfo); +#endif /* DARWIN */ + APR_DECLARE(apr_status_t) apr_file_lock(apr_file_t *thefile, int type) { +#if defined(DARWIN) + return darwin_file_lock(thefile, type); +#else int rc; - + #if defined(HAVE_FCNTL_H) { struct flock l = { 0 }; int fc; - + l.l_whence = SEEK_SET; /* lock from current point */ l.l_start = 0; /* begin lock at this offset */ l.l_len = 0; /* lock to end of file */ @@ -39,18 +120,18 @@ l.l_type = F_RDLCK; else l.l_type = F_WRLCK; - + fc = (type & APR_FLOCK_NONBLOCK) ? F_SETLK : F_SETLKW; - + /* keep trying if fcntl() gets interrupted (by a signal) */ while ((rc = fcntl(thefile->filedes, fc, &l)) < 0 && errno == EINTR) continue; - + if (rc == -1) { /* on some Unix boxes (e.g., Tru64), we get EACCES instead - * of EAGAIN; we don't want APR_STATUS_IS_EAGAIN() matching EACCES - * since that breaks other things, so fix up the retcode here - */ + * of EAGAIN; we don't want APR_STATUS_IS_EAGAIN() matching EACCES + * since that breaks other things, so fix up the retcode here + */ if (errno == EACCES) { return EAGAIN; } @@ -60,46 +141,50 @@ #elif defined(HAVE_SYS_FILE_H) { int ltype; - + if ((type & APR_FLOCK_TYPEMASK) == APR_FLOCK_SHARED) ltype = LOCK_SH; else ltype = LOCK_EX; if ((type & APR_FLOCK_NONBLOCK) != 0) ltype |= LOCK_NB; - + /* keep trying if flock() gets interrupted (by a signal) */ while ((rc = flock(thefile->filedes, ltype)) < 0 && errno == EINTR) continue; - + if (rc == -1) return errno; } #else #error No file locking mechanism is available. #endif - + return APR_SUCCESS; +#endif /* not DARWIN */ } APR_DECLARE(apr_status_t) apr_file_unlock(apr_file_t *thefile) { +#if defined(DARWIN) + return darwin_file_unlock(thefile); +#else int rc; #if defined(HAVE_FCNTL_H) { struct flock l = { 0 }; - + l.l_whence = SEEK_SET; /* lock from current point */ l.l_start = 0; /* begin lock at this offset */ l.l_len = 0; /* lock to end of file */ l.l_type = F_UNLCK; - + /* keep trying if fcntl() gets interrupted (by a signal) */ while ((rc = fcntl(thefile->filedes, F_SETLKW, &l)) < 0 && errno == EINTR) continue; - + if (rc == -1) return errno; } @@ -108,13 +193,739 @@ /* keep trying if flock() gets interrupted (by a signal) */ while ((rc = flock(thefile->filedes, LOCK_UN)) < 0 && errno == EINTR) continue; - + if (rc == -1) return errno; } #else #error No file locking mechanism is available. #endif + + return APR_SUCCESS; +#endif /* not DARWIN */ +} +#if defined(DARWIN) + +/* lock the file indicated*/ +apr_status_t darwin_file_lock(apr_file_t *thefile, int type) +{ + struct statfs sfb; + darwin_flock_data_info_t *datainfo; + apr_pool_t *temppool; + apr_status_t status; + apr_file_t *lock; + apr_uid_t userid; + apr_gid_t groupid; + apr_finfo_t fileinfo; + char *username; + char *temppath; + char *lockpath; + char *filepath; + char *filename; + char *lockdesc; + char *filedesc; + int *count; + int rc; + + rc = fstatfs(thefile->filedes, &sfb); + if (rc == -1 || apr_strnatcmp(sfb.f_fstypename,"smbfs") != 0) { + // usual fcntl locking since this is not an SMB filesystem + return darwin_lock_fcntl(thefile, type); + } + else { + // use flock since this is an SMB filesystem + // (check first to see if lock already made...) + + // create temp pool + status = apr_pool_create(&temppool, apr_file_pool_get(thefile)); + if (status == APR_SUCCESS) { + + // determine lockpath + status = apr_temp_dir_get((const char **)&temppath, temppool); + if (status == APR_SUCCESS) { + apr_uid_current(&userid, &groupid, temppool); + apr_uid_name_get(&username, userid, temppool); + status = apr_filepath_merge(&lockpath, temppath, + apr_psprintf(temppool, + "%s.%s", + DARWIN_FLOCK_PREFIX, + username), + 0, temppool); + } + + // determine filepath + if (status == APR_SUCCESS) + status = apr_file_name_get((const char **)&filename, thefile); + if (status == APR_SUCCESS) + status = apr_filepath_merge(&filepath, NULL, filename, + APR_FILEPATH_NOTRELATIVE, temppool); + + // create lock file + if (status == APR_SUCCESS) + status = apr_file_open(&lock, + lockpath, + APR_READ | APR_WRITE | APR_CREATE, + APR_UREAD | APR_UWRITE, + temppool); + if (status == APR_SUCCESS) { + // lock file (will block if someone is already in there) + status = darwin_lock_fcntl(lock, APR_FLOCK_EXCLUSIVE); + if (status == APR_SUCCESS) { + // setup with default value + count = NULL; + + // determine lockdesc & filedesc + if ((type & APR_FLOCK_TYPEMASK) == APR_FLOCK_SHARED) { + lockdesc = apr_psprintf(temppool, "%s:%s:%d:x", + DARWIN_FLOCK_PREFIX, filepath, + APR_FLOCK_SHARED); + // ignore error - should we check for one? + } + else { + lockdesc = apr_psprintf(temppool, "%s:%s:%d:%d", + DARWIN_FLOCK_PREFIX, filepath, + type, getpid()); + // ignore error - should we check for one? + } + filedesc = apr_psprintf(temppool, "%s:%04x:%d", + DARWIN_FLOCK_PREFIX, + thefile->filedes, getpid()); + // ignore error - should we check for one? + + // init shared data file + if (darwin_flock_data_init(&datainfo, lock, temppool) + == APR_SUCCESS) { + // get current count + if (darwin_flock_data_get((void **)&count, + (const char *)lockdesc, + datainfo) != APR_SUCCESS) + count = NULL; + } + else { + // data file not available for some reason... + // will try again later on + datainfo = NULL; + } + + if (count == NULL || *count == 0) { + // try lock with flock + status = darwin_lock_flock(thefile, type); + } + + // lock successful - increment count + if (status == APR_SUCCESS) { + if (count == NULL) { + // allocate + count = (int *)apr_palloc(temppool, sizeof(int)); + if (count == NULL) + status = APR_ENOMEM; + else + *count = 0; + } + } + if (status == APR_SUCCESS) + (*count)++; + + // try again if earlier attempt failed (should never happen) + if (status == APR_SUCCESS && datainfo == NULL) + status = darwin_flock_data_init(&datainfo, lock, + temppool); + + // write data to shared data file + if (status == APR_SUCCESS) { + // set lockdesc to value of count + status = darwin_flock_data_set((const void *)count, + sizeof(int), + (const char *)lockdesc, + datainfo); + } + if (status == APR_SUCCESS) { + // set filedesc to lockdesc + status = darwin_flock_data_set((const void *)lockdesc, + strlen(lockdesc) + 1, + (const char *)filedesc, + datainfo); + } + + // done with shared data file + if (datainfo != NULL) + darwin_flock_data_cleanup(datainfo); + + // unlock lock file + darwin_unlock_fcntl(lock); + + } /* end locked section */ + + // close lock file + apr_file_close(lock); + + } /* end usage of lock file */ + + // dispose of temp pool of memory + apr_pool_destroy(temppool); + + } /* end usage of temp pool */ + + // return status + return status; + + } /* end flock for SMB */ +} + +/* lock file with fcntl */ +apr_status_t darwin_lock_fcntl(apr_file_t *thefile, int type) +{ + int rc; + + { + struct flock l = { 0 }; + int fc; + + l.l_whence = SEEK_SET; /* lock from current point */ + l.l_start = 0; /* begin lock at this offset */ + l.l_len = 0; /* lock to end of file */ + if ((type & APR_FLOCK_TYPEMASK) == APR_FLOCK_SHARED) + l.l_type = F_RDLCK; + else + l.l_type = F_WRLCK; + + fc = (type & APR_FLOCK_NONBLOCK) ? F_SETLK : F_SETLKW; + + /* keep trying if fcntl() gets interrupted (by a signal) */ + while ((rc = fcntl(thefile->filedes, fc, &l)) < 0 && errno == EINTR) + continue; + + if (rc == -1) { + /* on some Unix boxes (e.g., Tru64), we get EACCES instead + * of EAGAIN; we don't want APR_STATUS_IS_EAGAIN() matching EACCES + * since that breaks other things, so fix up the retcode here + */ + if (errno == EACCES) { + return EAGAIN; + } + return errno; + } + } return APR_SUCCESS; +} + +/* lock file with flock */ +apr_status_t darwin_lock_flock(apr_file_t *thefile, int type) +{ + int rc; + { + int ltype; + + if ((type & APR_FLOCK_TYPEMASK) == APR_FLOCK_SHARED) + ltype = LOCK_SH; + else + ltype = LOCK_EX; + if ((type & APR_FLOCK_NONBLOCK) != 0) + ltype |= LOCK_NB; + + /* keep trying if flock() gets interrupted (by a signal) */ + while ((rc = flock(thefile->filedes, ltype)) < 0 && errno == EINTR) + continue; + + if (rc == -1) + return errno; + } + + return APR_SUCCESS; } + +/* unlock the file indicated */ +apr_status_t darwin_file_unlock(apr_file_t *thefile) +{ + struct statfs sfb; + darwin_flock_data_info_t *datainfo; + apr_pool_t *temppool; + apr_status_t status; + apr_file_t *lock; + apr_uid_t userid; + apr_gid_t groupid; + apr_finfo_t fileinfo; + char *username; + char *temppath; + char *lockpath; + char *lockdesc; + char *filedesc; + int *count; + int rc; + + rc = fstatfs(thefile->filedes, &sfb); + if (rc == -1 || apr_strnatcmp(sfb.f_fstypename,"smbfs") != 0) + { + // usual fcntl unlocking since this is not an SMB filesystem + return darwin_unlock_fcntl(thefile); + } + else { + // use flock since this is an SMB filesystem + // (check first to see if file needs to be unlocked...) + + // create temp pool + status = apr_pool_create(&temppool, apr_file_pool_get(thefile)); + if (status == APR_SUCCESS) { + // determine lockpath + status = apr_temp_dir_get((const char **)&temppath, temppool); + if (status == APR_SUCCESS) { + apr_uid_current(&userid, &groupid, temppool); + apr_uid_name_get(&username, userid, temppool); + status = apr_filepath_merge(&lockpath, temppath, + apr_psprintf(temppool, "%s.%s", + DARWIN_FLOCK_PREFIX, + username), + 0, temppool); + } + + // create lock file + if (status == APR_SUCCESS) + status = apr_file_open(&lock, + lockpath, + APR_READ | APR_WRITE | APR_CREATE, + APR_UREAD | APR_UWRITE, + temppool); + if (status == APR_SUCCESS) { + // lock file (will block if someone is already in there) + status = darwin_lock_fcntl(lock, APR_FLOCK_EXCLUSIVE); + if (status == APR_SUCCESS) { + // setup with default value + count = NULL; + + // determine filedesc + filedesc = apr_psprintf(temppool, "%s:%04x:%d", + DARWIN_FLOCK_PREFIX, + thefile->filedes, getpid()); + // ignore error - should we check for one? + + // init shared data file + if (darwin_flock_data_init(&datainfo, lock, temppool) + == APR_SUCCESS) { + // get lockdesc for filedesc, then count for lockdesc + if (darwin_flock_data_get((void **)&lockdesc, + (const char *)filedesc, + datainfo) != APR_SUCCESS) + lockdesc = NULL; + if (lockdesc == NULL + || darwin_flock_data_get((void **)&count, + (const char *)lockdesc, + datainfo) != APR_SUCCESS) + count = NULL; + } + + if (count == NULL || *count <= 1) { + // try unlock with flock + status = darwin_unlock_flock(thefile); + } + + // was successful + if (status == APR_SUCCESS && count != NULL && *count != 0) { + // decrease count for this lock + (*count)--; + if (*count == 0) { + // set lockdesc to NULL + status = darwin_flock_data_set(NULL, 0, + (const char *)lockdesc, + datainfo); + } else { + // set lockdesc to value of count + status = darwin_flock_data_set((const void *)count, + sizeof(int), + (const char *)lockdesc, + datainfo); + } + if (status == APR_SUCCESS) { + // set filedesc to NULL + status = darwin_flock_data_set(NULL, 0, + (const char *)filedesc, + datainfo); + } + + } + + // done with shared data file + if (datainfo != NULL) + darwin_flock_data_cleanup(datainfo); + + // unlock lock file + darwin_unlock_fcntl(lock); + + } /* end of locked section */ + + // close lock file + apr_file_close(lock); + + } /* end usage of lock file */ + + // dispose of temp pool of memory + apr_pool_destroy(temppool); + + } /* end usage of temp pool */ + + // return status + return status; + } +} + +/* unlock with fcntl */ +apr_status_t darwin_unlock_fcntl(apr_file_t *thefile) +{ + int rc; + + { + struct flock l = { 0 }; + + l.l_whence = SEEK_SET; /* lock from current point */ + l.l_start = 0; /* begin lock at this offset */ + l.l_len = 0; /* lock to end of file */ + l.l_type = F_UNLCK; + + /* keep trying if fcntl() gets interrupted (by a signal) */ + while ((rc = fcntl(thefile->filedes, F_SETLKW, &l)) < 0 + && errno == EINTR) + continue; + + if (rc == -1) + return errno; + } + + return APR_SUCCESS; +} + +/* unlock with flock */ +apr_status_t darwin_unlock_flock(apr_file_t *thefile) +{ + int rc; + + { + /* keep trying if flock() gets interrupted (by a signal) */ + while ((rc = flock(thefile->filedes, LOCK_UN)) < 0 && errno == EINTR) + continue; + + if (rc == -1) + return errno; + } + + return APR_SUCCESS; +} + +/* initialize shared data file */ +apr_status_t darwin_flock_data_init(darwin_flock_data_info_t **datainfo, + apr_file_t *shared, apr_pool_t *pool) +{ + apr_status_t status; + darwin_flock_data_header_t *dataheader; + apr_finfo_t fileinfo; + apr_off_t headeroffset = 0; + + // allocate memory for dataheader + dataheader = (darwin_flock_data_header_t *)apr_pcalloc(pool, + sizeof(darwin_flock_data_header_t)); + if (dataheader == NULL) + return APR_ENOMEM; + + // allocate memory for datainfo + (*datainfo) = (darwin_flock_data_info_t *)apr_pcalloc(pool, + sizeof(darwin_flock_data_info_t)); + if (*datainfo == NULL) + return APR_ENOMEM; + + // init datainfo + (*datainfo)->datafile = shared; + (*datainfo)->dataheader = dataheader; + (*datainfo)->datapool = pool; + + // get temp memory + status = apr_pool_create(&((*datainfo)->temppool), pool); + if (status != APR_SUCCESS) + return status; + + // check for preexisting data + status = apr_file_info_get(&fileinfo, APR_FINFO_SIZE, shared); + if (status == APR_SUCCESS) { + if (fileinfo.size < sizeof(darwin_flock_data_header_t)) { + // make new header + dataheader->version = DARWIN_FLOCK_DATA_VERSION; + dataheader->firstblockoffset = 0; + dataheader->reserved1 = 0; + dataheader->reserved2 = 0; + + // write out header + status = apr_file_seek(shared, APR_SET, &headeroffset); + if (status == APR_SUCCESS) + status = apr_file_write_full(shared, dataheader, + sizeof(darwin_flock_data_header_t), + NULL); + } + else { + // read in existing header + status = apr_file_seek(shared, APR_SET, &headeroffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(shared, dataheader, + sizeof(darwin_flock_data_header_t), + NULL); + } + } + + return status; +} + +/* truncate shared data file if empty */ +apr_status_t darwin_flock_data_cleanup(darwin_flock_data_info_t *datainfo) +{ + apr_status_t status; + + status = APR_SUCCESS; + + // truncate if no data is stored + if (datainfo->dataheader->firstblockoffset == 0) + status = apr_file_trunc(datainfo->datafile, 0); + + // cleanup memory pool + apr_pool_destroy(datainfo->temppool); + + return status; +} + +/* returns pointer to requested data in shared data file */ +apr_status_t darwin_flock_data_get(void **data, const char *key, + darwin_flock_data_info_t *datainfo) +{ + darwin_flock_data_block_t datablock; + apr_status_t status; + apr_off_t theoffset; + char *buffer; + + // default values + *data = NULL; + status = APR_SUCCESS; + + // get offset of first data block + theoffset = datainfo->dataheader->firstblockoffset; + + // loop until we find the result + while (status == APR_SUCCESS && theoffset != 0 && *data == NULL) { + // read in datablock pointed to by the offset + status = apr_file_seek(datainfo->datafile, APR_SET, &theoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, &datablock, + sizeof(darwin_flock_data_block_t), + NULL); + if (status == APR_SUCCESS && datablock.keysize != 0) { + // allocate memory for the buffer + buffer = (char *)apr_palloc(datainfo->temppool, datablock.keysize); + if (buffer == NULL) + status = APR_ENOMEM; + // read in key + if (status == APR_SUCCESS) + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.keyoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, buffer, + datablock.keysize, NULL); + // compare + if (status == APR_SUCCESS) { + if (apr_strnatcmp(key, (const char *)buffer) == 0) { + // allocate memory for data + *data = apr_palloc(datainfo->datapool, datablock.datasize); + if (*data == NULL) + status = APR_ENOMEM; + // read in data + if (status == APR_SUCCESS) + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.dataoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, *data, + datablock.datasize, NULL); + } + else { + // not a match, so advance to next block + theoffset = datablock.nextblockoffset; + } + } + // clear out temp pool + apr_pool_clear(datainfo->temppool); + } + } + + return status; +} + +/* adds or updates specified data in the shared data file */ +apr_status_t darwin_flock_data_set(const void *data, apr_size_t size, + const char *key, + darwin_flock_data_info_t *datainfo) +{ + darwin_flock_data_block_t datablock; + darwin_flock_data_block_t otherblock; + apr_status_t status; + apr_off_t theoffset; + apr_off_t blockoffset; + apr_off_t nextoffset; + apr_off_t previousoffset; + char *buffer; + + // default values + blockoffset = 0; + status = APR_SUCCESS; + + // get offset of first data block + theoffset = datainfo->dataheader->firstblockoffset; + + // loop until we find the block to update + while (status == APR_SUCCESS && theoffset != 0 && blockoffset == 0) { + // read in datablock pointed to by the offset + status = apr_file_seek(datainfo->datafile, APR_SET, &theoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, &datablock, + sizeof(darwin_flock_data_block_t), + NULL); + if (status == APR_SUCCESS && datablock.keysize != 0) { + // allocate memory for the buffer + buffer = (char *)apr_palloc(datainfo->temppool, datablock.keysize); + if (buffer == NULL) + status = APR_ENOMEM; + // read in key + if (status == APR_SUCCESS) + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.keyoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, buffer, + datablock.keysize, NULL); + // compare + if (status == APR_SUCCESS) { + if (apr_strnatcmp(key, (const char *)buffer) == 0) { + // match found! + blockoffset = theoffset; + } + else { + // not a match, so advance to next block + theoffset = datablock.nextblockoffset; + } + } + // clear out temp pool + apr_pool_clear(datainfo->temppool); + } + } + + /* if blockoffset != 0, then datablock holds the matching block */ + + // bail out if error + if (status != APR_SUCCESS) + return status; + + if (size == 0) { + if (blockoffset != 0) { + // delete block + nextoffset = datablock.nextblockoffset; + previousoffset = datablock.previousblockoffset; + } else { /* blockoffset == 0 */ + // nothing to delete + return status; + } + } + else { /* size != 0 */ + if (blockoffset == 0) { + // init datablock - will be inserted as the first data block + datablock.previousblockoffset = 0; + datablock.nextblockoffset = datainfo->dataheader->firstblockoffset; + datablock.keysize = strlen(key) + 1; + datablock.keyoffset = 0; + datablock.datasize = 0; + datablock.dataoffset = 0; + + // write out key + status = apr_file_seek(datainfo->datafile, APR_END, + &datablock.keyoffset); + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, key, + datablock.keysize, NULL); + } + + // make sure data is big enough + if (status == APR_SUCCESS && datablock.datasize < size) + datablock.dataoffset = 0; + + // set datasize + datablock.datasize = size; + + // write out data + if (status == APR_SUCCESS) + status = apr_file_seek(datainfo->datafile, + (datablock.dataoffset == 0 ? APR_END : + APR_SET), &datablock.dataoffset); + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, data, + datablock.datasize, NULL); + + // write out block + if (status == APR_SUCCESS) + status = apr_file_seek(datainfo->datafile, (blockoffset == 0 ? + APR_END : APR_SET), + &blockoffset); + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, &datablock, + sizeof(darwin_flock_data_block_t), + NULL); + if (status == APR_SUCCESS) { + nextoffset = blockoffset; + previousoffset = blockoffset; + } + } + + // fix next pointer + if (status == APR_SUCCESS) { + if (datablock.previousblockoffset == 0) { + // update header + datainfo->dataheader->firstblockoffset = nextoffset; + theoffset = 0; + status = apr_file_seek(datainfo->datafile, APR_SET, &theoffset); + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, + datainfo->dataheader, + sizeof(darwin_flock_data_header_t), + NULL); + } + else { /* datablock.previousblockoffset != 0 */ + // update block + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.previousblockoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, &otherblock, + sizeof(darwin_flock_data_block_t), + NULL); + if (status == APR_SUCCESS) { + otherblock.nextblockoffset = nextoffset; + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.previousblockoffset); + } + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, &otherblock, + sizeof(darwin_flock_data_block_t), + NULL); + } + } + // fix pointer from next block + if (status == APR_SUCCESS && datablock.nextblockoffset != 0) { + // update block + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.nextblockoffset); + if (status == APR_SUCCESS) + status = apr_file_read_full(datainfo->datafile, &otherblock, + sizeof(darwin_flock_data_block_t), + NULL); + if (status == APR_SUCCESS) { + otherblock.previousblockoffset = previousoffset; + status = apr_file_seek(datainfo->datafile, APR_SET, + &datablock.nextblockoffset); + } + if (status == APR_SUCCESS) + status = apr_file_write_full(datainfo->datafile, &otherblock, + sizeof(darwin_flock_data_block_t), + NULL); + } + + return status; +} +#endif /* DARWIN */ Index: file_io/unix/readwrite.c =================================================================== --- file_io/unix/readwrite.c (revision 453079) +++ file_io/unix/readwrite.c (working copy) @@ -19,6 +19,21 @@ #include "apr_thread_mutex.h" #include "apr_support.h" +/* Mac OS X 10.4 has a bug that results in an I/O error if an attempt to write + * a zero byte buffer to a file on an SMB share is made. This affects Subversion + * in particular. + * + * Code has been added below for Mac OS X 10.4 to check for attempts to write a + * zero byte buffer to a file on an SMB share. These will return with + * APR_SUCCESS instead of attempting the write operation. + * + * -- Brian D. Wells + */ +#if defined(DARWIN) +#include +#include +#endif + /* The only case where we don't use wait_for_io_or_timeout is on * pre-BONE BeOS, so this check should be sufficient and simpler */ #if !BEOS_R5 @@ -156,6 +171,15 @@ { apr_size_t rv; +#if defined(DARWIN) + struct statfs sfb; + + if (*nbytes == 0 && fstatfs(thefile->filedes, &sfb) == 0 && + apr_strnatcmp(sfb.f_fstypename,"smbfs") == 0) { + return APR_SUCCESS; + } +#endif + if (thefile->buffered) { char *pos = (char *)buf; int blocksize;