mirror of
https://github.com/mabam/afpfs-ng-mac.git
synced 2024-11-17 10:05:40 +00:00
693 lines
15 KiB
C
693 lines
15 KiB
C
|
|
|
|
/*
|
|
lowlevel.c: some functions that abstract common operations; used
|
|
so the same code can be used between meta and normal files
|
|
|
|
Copyright (C) 2008 Alex deVries <alexthepuffin@gmail.com>
|
|
|
|
This program can be distributed under the terms of the GNU GPL.
|
|
See the file COPYING.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#ifdef __linux__
|
|
#include <asm/fcntl.h>
|
|
#else
|
|
#include <fcntl.h>
|
|
#endif
|
|
#include "afpfs-ng/afp.h"
|
|
#include "afpfs-ng/afp_protocol.h"
|
|
#include "afpfs-ng/codepage.h"
|
|
#include "afpfs-ng/utils.h"
|
|
#include "afpfs-ng/midlevel.h"
|
|
#include "lib/forklist.h"
|
|
#include "did.h"
|
|
#include "users.h"
|
|
|
|
static void set_nonunix_perms(unsigned int * mode, struct afp_file_info *fp)
|
|
{
|
|
if (fp->isdir)
|
|
*mode = 0700 | S_IFDIR;
|
|
else
|
|
*mode = 0600 | S_IFREG;
|
|
}
|
|
|
|
int ll_handle_unlocking(struct afp_volume * volume,unsigned short forkid,
|
|
uint64_t offset, uint64_t sizetorequest)
|
|
{
|
|
uint64_t generated_offset;
|
|
int rc;
|
|
|
|
if (volume->extra_flags & VOLUME_EXTRA_FLAGS_NO_LOCKING)
|
|
return 0;
|
|
|
|
if (volume->server->using_version->av_number < 30)
|
|
rc=afp_byterangelock(volume,ByteRangeLock_Unlock,
|
|
forkid,offset, sizetorequest,
|
|
(uint32_t *) &generated_offset);
|
|
else
|
|
rc=afp_byterangelockext(volume,ByteRangeLock_Unlock,
|
|
forkid,offset, sizetorequest,&generated_offset);
|
|
switch(rc) {
|
|
case kFPNoErr:
|
|
break;
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
case kFPRangeNotLocked:
|
|
default:
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int ll_handle_locking(struct afp_volume * volume,unsigned short forkid,
|
|
uint64_t offset, uint64_t sizetorequest)
|
|
{
|
|
|
|
#define MAX_LOCKTRYCOUNT 10
|
|
|
|
int rc=0;
|
|
int try=0;
|
|
uint64_t generated_offset;
|
|
|
|
if (volume->extra_flags & VOLUME_EXTRA_FLAGS_NO_LOCKING)
|
|
return 0;
|
|
|
|
while (try<MAX_LOCKTRYCOUNT) {
|
|
try++;
|
|
if (volume->server->using_version->av_number < 30)
|
|
rc=afp_byterangelock(volume,ByteRangeLock_Lock,
|
|
forkid,offset, sizetorequest,
|
|
(uint32_t *) &generated_offset);
|
|
else
|
|
rc=afp_byterangelockext(volume,ByteRangeLock_Lock,
|
|
forkid,offset, sizetorequest,&generated_offset);
|
|
switch(rc) {
|
|
case kFPNoErr:
|
|
goto done;
|
|
case kFPNoMoreLocks: /* Max num of locks on server */
|
|
case kFPLockErr: /*Some or all of the requested range is locked
|
|
by another user. */
|
|
|
|
sleep(1);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
done:
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* zero_file()
|
|
*
|
|
* This function will truncate the fork given to zero bytes in length.
|
|
* This has been abstracted because there is some differences in the
|
|
* expectation of Ext or not Ext. */
|
|
|
|
int ll_zero_file(struct afp_volume * volume, unsigned short forkid,
|
|
unsigned int resource)
|
|
{
|
|
unsigned int bitmap;
|
|
int ret;
|
|
|
|
/* The Airport Extreme 7.1.1 will crash if you send it
|
|
* DataForkLenBit. Netatalk replies with an error if you
|
|
* send it ExtDataForkLenBit. So we need to choose. */
|
|
|
|
if ((volume->server->using_version->av_number < 30) ||
|
|
(volume->server->server_type==AFPFS_SERVER_TYPE_NETATALK))
|
|
bitmap=(resource ?
|
|
kFPRsrcForkLenBit : kFPDataForkLenBit);
|
|
else
|
|
bitmap=(resource ?
|
|
kFPExtRsrcForkLenBit : kFPExtDataForkLenBit);
|
|
|
|
ret=afp_setforkparms(volume,forkid,bitmap,0);
|
|
switch (ret) {
|
|
case kFPAccessDenied:
|
|
ret=EACCES;
|
|
break;
|
|
case kFPVolLocked:
|
|
case kFPLockErr:
|
|
ret=EBUSY;
|
|
break;
|
|
case kFPDiskFull:
|
|
ret=ENOSPC;
|
|
break;
|
|
case kFPBitmapErr:
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
ret=EIO;
|
|
break;
|
|
default:
|
|
ret=0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* get_directory_entry is used to abstract afp_getfiledirparms
|
|
* * because in AFP<3.0 there is only afp_getfileparms and afp_getdirparms.
|
|
* */
|
|
|
|
int ll_get_directory_entry(struct afp_volume * volume,
|
|
char * basename,
|
|
unsigned int dirid,
|
|
unsigned int filebitmap, unsigned int dirbitmap,
|
|
struct afp_file_info *p)
|
|
{
|
|
int ret;
|
|
char tmpname[AFP_MAX_PATH];
|
|
|
|
memcpy(tmpname,p->basename,AFP_MAX_PATH);
|
|
ret =afp_getfiledirparms(volume,dirid,
|
|
filebitmap,dirbitmap,basename,p);
|
|
memcpy(p->basename,tmpname,AFP_MAX_PATH);
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
int ll_open(struct afp_volume * volume, const char *path, int flags,
|
|
struct afp_file_info *fp)
|
|
{
|
|
|
|
/* FIXME: doesn't handle create properly */
|
|
|
|
int ret, dsi_ret,rc;
|
|
int create_file=0;
|
|
//char converted_path[AFP_MAX_PATH];
|
|
unsigned char aflags = AFP_OPENFORK_ALLOWREAD;
|
|
|
|
if (flags & O_RDONLY) aflags|=AFP_OPENFORK_ALLOWREAD;
|
|
if (flags & O_WRONLY) aflags|=AFP_OPENFORK_ALLOWWRITE;
|
|
if (flags & O_RDWR)
|
|
aflags |= (AFP_OPENFORK_ALLOWREAD | AFP_OPENFORK_ALLOWWRITE);
|
|
|
|
|
|
if ((aflags&AFP_OPENFORK_ALLOWWRITE) &
|
|
(volume_is_readonly(volume))) {
|
|
ret=EPERM;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
O_NOBLOCK - todo: it is unclear how to this in fuse.
|
|
*/
|
|
|
|
/* The following flags don't need any attention here:
|
|
O_ASYNC - not relevant for files
|
|
O_APPEND
|
|
O_NOATIME - we have no way to handle this anyway
|
|
*/
|
|
|
|
|
|
/*this will be used later for caching*/
|
|
#ifdef __linux__
|
|
fp->sync=((unsigned char)(flags & (O_SYNC | O_DIRECT)));
|
|
#else
|
|
fp->sync=(flags & (O_SYNC));
|
|
#endif
|
|
|
|
/* See if we need to create the file */
|
|
if (aflags & AFP_OPENFORK_ALLOWWRITE) {
|
|
if (create_file) {
|
|
/* Create the file */
|
|
if (flags & O_EXCL) {
|
|
ret=EEXIST;
|
|
goto error;
|
|
}
|
|
rc=afp_createfile(volume,kFPSoftCreate,fp->did,fp->basename);
|
|
}
|
|
}
|
|
|
|
if (
|
|
#ifdef __linux__
|
|
(flags & O_LARGEFILE) &&
|
|
#endif
|
|
(volume->server->using_version->av_number<30))
|
|
{
|
|
switch(ll_get_directory_entry(volume,fp->basename,fp->did,
|
|
kFPParentDirIDBit|kFPNodeIDBit|
|
|
(fp->resource ? kFPRsrcForkLenBit : kFPDataForkLenBit),
|
|
0,fp)) {
|
|
case kFPAccessDenied:
|
|
ret=EACCES;
|
|
goto error;
|
|
case kFPObjectNotFound:
|
|
ret=ENOENT;
|
|
goto error;
|
|
case kFPNoErr:
|
|
break;
|
|
case kFPBitmapErr:
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
default:
|
|
ret=EIO;
|
|
goto error;
|
|
}
|
|
|
|
if ((fp->resource ? (fp->resourcesize>=(AFP_MAX_AFP2_FILESIZE-1)) :
|
|
( fp->size>=AFP_MAX_AFP2_FILESIZE-1))) {
|
|
/* According to p.30, if the server doesn't support >4GB files
|
|
and the file being opened is >4GB, then resourcesize or size
|
|
will return 4GB. How can it return 4GB in 32 its? I
|
|
suspect it actually returns 4GB-1.
|
|
*/
|
|
ret=EOVERFLOW;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
|
|
try_again:
|
|
dsi_ret=afp_openfork(volume,fp->resource?1:0,fp->did,
|
|
aflags,fp->basename,fp);
|
|
|
|
switch (dsi_ret) {
|
|
case kFPAccessDenied:
|
|
ret=EACCES;
|
|
goto error;
|
|
case kFPObjectNotFound:
|
|
if ((flags & O_CREAT) &&
|
|
(ml_creat(volume,path,0644)==0)) {
|
|
/* FIXME 0644 is just made up */
|
|
goto try_again;
|
|
} else {
|
|
ret=ENOENT;
|
|
goto error;
|
|
}
|
|
case kFPObjectLocked:
|
|
ret=EROFS;
|
|
goto error;
|
|
case kFPObjectTypeErr:
|
|
ret=EISDIR;
|
|
goto error;
|
|
case kFPParamErr:
|
|
ret=EACCES;
|
|
goto error;
|
|
case kFPTooManyFilesOpen:
|
|
ret=EMFILE;
|
|
goto error;
|
|
case kFPVolLocked:
|
|
case kFPDenyConflict:
|
|
case kFPMiscErr:
|
|
case kFPBitmapErr:
|
|
case -1:
|
|
ret=EFAULT;
|
|
goto error;
|
|
case 0:
|
|
ret=0;
|
|
break;
|
|
default:
|
|
ret=EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
add_opened_fork(volume, fp);
|
|
|
|
if ((flags & O_TRUNC) && (!create_file)) {
|
|
|
|
/* This is the case where we want to truncate the
|
|
the file and it already exists. */
|
|
if ((ret=ll_zero_file(volume,fp->forkid,fp->resource)))
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
return -ret;
|
|
}
|
|
|
|
|
|
int ll_read(struct afp_volume * volume,
|
|
char *buf, size_t size, off_t offset,
|
|
struct afp_file_info *fp, int * eof)
|
|
{
|
|
int bytesleft=size;
|
|
int totalsize=0;
|
|
int ret=0;
|
|
int rc;
|
|
unsigned int bufsize=min(volume->server->rx_quantum,size);
|
|
struct afp_rx_buffer buffer;
|
|
|
|
*eof=0;
|
|
|
|
buffer.data = buf;
|
|
buffer.maxsize=bufsize;
|
|
buffer.size=0;
|
|
/* Lock the range */
|
|
if (ll_handle_locking(volume, fp->forkid,offset,size)) {
|
|
/* There was an irrecoverable error when locking */
|
|
ret=EBUSY;
|
|
goto error;
|
|
}
|
|
|
|
if (volume->server->using_version->av_number < 30)
|
|
rc=afp_read(volume, fp->forkid,offset,size,&buffer);
|
|
else
|
|
rc=afp_readext(volume, fp->forkid,offset,size,&buffer);
|
|
|
|
if (ll_handle_unlocking(volume, fp->forkid,offset,size)) {
|
|
/* Somehow, we couldn't unlock the range. */
|
|
ret=EIO;
|
|
goto error;
|
|
}
|
|
switch(rc) {
|
|
case kFPAccessDenied:
|
|
ret=EACCES;
|
|
goto error;
|
|
case kFPLockErr:
|
|
ret=EBUSY;
|
|
goto error;
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
ret=EIO;
|
|
goto error;
|
|
case kFPEOFErr:
|
|
*eof=1;
|
|
break;
|
|
case kFPNoErr:
|
|
break;
|
|
}
|
|
|
|
bytesleft-=buffer.size;
|
|
totalsize+=buffer.size;
|
|
return totalsize;
|
|
error:
|
|
return -ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ll_readdir(struct afp_volume * volume, const char *path,
|
|
struct afp_file_info **fb, int resource)
|
|
{
|
|
struct afp_file_info * p, * filebase=NULL, *base, *last=NULL;
|
|
unsigned short reqcount=20; /* Get them in batches of 20 */
|
|
unsigned long startindex=1;
|
|
int rc=0, ret=0, exit=0;
|
|
unsigned int filebitmap, dirbitmap;
|
|
char basename[AFP_MAX_PATH];
|
|
char converted_name[AFP_MAX_PATH];
|
|
unsigned int dirid;
|
|
|
|
if (invalid_filename(volume->server,path))
|
|
return -ENAMETOOLONG;
|
|
|
|
if (get_dirid(volume, path, basename, &dirid)<0)
|
|
return -ENOENT;
|
|
|
|
/* We need to handle length bits differently for AFP < 3.0 */
|
|
|
|
filebitmap=kFPAttributeBit | kFPParentDirIDBit |
|
|
kFPCreateDateBit | kFPModDateBit |
|
|
kFPBackupDateBit|
|
|
kFPNodeIDBit;
|
|
dirbitmap=kFPAttributeBit | kFPParentDirIDBit |
|
|
kFPCreateDateBit | kFPModDateBit |
|
|
kFPBackupDateBit|
|
|
kFPNodeIDBit | kFPOffspringCountBit|
|
|
kFPOwnerIDBit|kFPGroupIDBit;
|
|
if (volume->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) {
|
|
dirbitmap|=kFPUnixPrivsBit;
|
|
filebitmap|=kFPUnixPrivsBit;
|
|
}
|
|
|
|
if (volume->attributes & kSupportsUTF8Names ) {
|
|
dirbitmap|=kFPUTF8NameBit;
|
|
filebitmap|=kFPUTF8NameBit;
|
|
} else {
|
|
dirbitmap|=kFPLongNameBit| kFPShortNameBit;
|
|
filebitmap|=kFPLongNameBit| kFPShortNameBit;
|
|
}
|
|
if (volume->server->using_version->av_number<30) {
|
|
filebitmap |=(resource ? kFPRsrcForkLenBit:kFPDataForkLenBit);
|
|
} else {
|
|
filebitmap |=(resource ? kFPRsrcForkLenBit:kFPExtDataForkLenBit);
|
|
}
|
|
|
|
while (!exit) {
|
|
|
|
/* FIXME: check AFP version */
|
|
/* this function will allocate and generate a linked list
|
|
of files */
|
|
if (volume->server->using_version->av_number<30) {
|
|
rc = afp_enumerate(volume,dirid,
|
|
filebitmap, dirbitmap,reqcount,
|
|
startindex,basename,&base);
|
|
} else {
|
|
rc = afp_enumerateext2(volume,dirid,
|
|
filebitmap, dirbitmap,reqcount,
|
|
startindex,basename,&base);
|
|
}
|
|
|
|
switch(rc) {
|
|
case -1:
|
|
ret=EIO;
|
|
goto error;
|
|
case 0:
|
|
case kFPObjectNotFound:
|
|
if (filebase==NULL) filebase=base;
|
|
else last->next=base;
|
|
for (p=base; p; p=p->next) {
|
|
startindex++;
|
|
last=p;
|
|
}
|
|
if (rc==kFPObjectNotFound) exit=1;
|
|
break;
|
|
case kFPAccessDenied:
|
|
ret=EACCES;
|
|
goto error;
|
|
case kFPDirNotFound:
|
|
ret=ENOENT;
|
|
exit++;
|
|
break;
|
|
case kFPBitmapErr:
|
|
case kFPMiscErr:
|
|
case kFPObjectTypeErr:
|
|
case kFPParamErr:
|
|
case kFPCallNotSupported:
|
|
ret=EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
for (p=filebase; p; p=p->next) {
|
|
/* Convert all the names back to precomposed */
|
|
convert_path_to_unix(
|
|
volume->server->path_encoding,
|
|
converted_name,p->name, AFP_MAX_PATH);
|
|
startindex++;
|
|
}
|
|
|
|
if (volume->server->using_version->av_number<30) {
|
|
for (p=filebase; p; p=p->next) {
|
|
set_nonunix_perms(&p->unixprivs.permissions, p);
|
|
}
|
|
}
|
|
|
|
*fb=filebase;
|
|
|
|
return 0;
|
|
error:
|
|
return -ret;
|
|
|
|
}
|
|
|
|
|
|
int ll_getattr(struct afp_volume * volume, const char *path, struct stat *stbuf,
|
|
int resource)
|
|
{
|
|
struct afp_file_info fp;
|
|
unsigned int dirid;
|
|
int rc;
|
|
unsigned int filebitmap, dirbitmap;
|
|
char basename[AFP_MAX_PATH];
|
|
unsigned int creation_date;
|
|
unsigned int modification_date;
|
|
|
|
memset(stbuf, 0, sizeof(struct stat));
|
|
|
|
if ((volume->server) &&
|
|
(invalid_filename(volume->server,path)))
|
|
return -ENAMETOOLONG;
|
|
|
|
if (get_dirid(volume, path, basename, &dirid)<0) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
dirbitmap=kFPAttributeBit
|
|
| kFPCreateDateBit | kFPModDateBit|
|
|
kFPNodeIDBit |
|
|
kFPParentDirIDBit | kFPOffspringCountBit;
|
|
filebitmap=kFPAttributeBit |
|
|
kFPCreateDateBit | kFPModDateBit |
|
|
kFPNodeIDBit |
|
|
kFPFinderInfoBit |
|
|
kFPParentDirIDBit;
|
|
|
|
if (volume->server->using_version->av_number < 30) {
|
|
if (path[0]=='/' && path[1]=='\0') {
|
|
/* This will sound odd, but when referring to /, AFP 2.x
|
|
clients check on a 'file' with the volume name. */
|
|
snprintf(basename,AFP_MAX_PATH,"%s",
|
|
volume->volume_name);
|
|
dirid=1;
|
|
}
|
|
filebitmap |=(resource ? kFPRsrcForkLenBit:kFPDataForkLenBit);
|
|
|
|
} else {
|
|
filebitmap |=(resource ? kFPExtRsrcForkLenBit:kFPExtDataForkLenBit);
|
|
}
|
|
|
|
if (volume->extra_flags & VOLUME_EXTRA_FLAGS_VOL_SUPPORTS_UNIX) {
|
|
dirbitmap|= kFPUnixPrivsBit;
|
|
filebitmap|= kFPUnixPrivsBit;
|
|
} else {
|
|
dirbitmap|=kFPOwnerIDBit | kFPGroupIDBit;
|
|
}
|
|
|
|
rc=afp_getfiledirparms(volume,dirid,filebitmap,dirbitmap,
|
|
(char *) basename,&fp);
|
|
|
|
switch(rc) {
|
|
|
|
case kFPAccessDenied:
|
|
return -EACCES;
|
|
case kFPObjectNotFound:
|
|
return -ENOENT;
|
|
case kFPNoErr:
|
|
break;
|
|
case kFPBitmapErr:
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
default:
|
|
return -EIO;
|
|
}
|
|
|
|
if (volume->server->using_version->av_number>=30 && fp.unixprivs.permissions != 0)
|
|
stbuf->st_mode |= fp.unixprivs.permissions;
|
|
else
|
|
set_nonunix_perms((unsigned int *)&stbuf->st_mode,&fp);
|
|
|
|
stbuf->st_uid=fp.unixprivs.uid;
|
|
stbuf->st_gid=fp.unixprivs.gid;
|
|
|
|
if (translate_uidgid_to_client(volume,
|
|
&stbuf->st_uid,&stbuf->st_gid)) {
|
|
return -EIO;
|
|
}
|
|
if (stbuf->st_mode & S_IFDIR) {
|
|
stbuf->st_nlink = fp.offspring +2;
|
|
stbuf->st_size = (fp.offspring *34) + 24;
|
|
/* This slight voodoo was taken from Mac OS X 10.2 */
|
|
} else {
|
|
stbuf->st_nlink = 1;
|
|
stbuf->st_size = (resource ? fp.resourcesize : fp.size);
|
|
stbuf->st_blksize = 4096;
|
|
stbuf->st_blocks = (stbuf->st_size) / 4096;
|
|
}
|
|
|
|
if ((volume->server->using_version->av_number<30) &&
|
|
(stbuf->st_mode & S_IFDIR)) {
|
|
/* AFP 2.x doesn't give ctime and mtime for directories*/
|
|
creation_date=volume->server->connect_time;
|
|
modification_date=volume->server->connect_time;
|
|
} else {
|
|
creation_date=fp.creation_date;
|
|
modification_date=fp.modification_date;
|
|
}
|
|
|
|
#ifdef __linux__
|
|
stbuf->st_ctim.tv_sec=creation_date;
|
|
stbuf->st_mtim.tv_sec=modification_date;
|
|
#else
|
|
stbuf->st_ctime=creation_date;
|
|
stbuf->st_mtime=modification_date;
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
int ll_write(struct afp_volume * volume,
|
|
const char *data, size_t size, off_t offset,
|
|
struct afp_file_info * fp, size_t * totalwritten)
|
|
{
|
|
|
|
int ret,err=0;
|
|
uint64_t sizetowrite, ignored;
|
|
uint32_t ignored32;
|
|
unsigned int max_packet_size=volume->server->tx_quantum;
|
|
off_t o=0;
|
|
*totalwritten=0;
|
|
|
|
if (!fp) return -EBADF;
|
|
|
|
/* Get a lock */
|
|
if (ll_handle_locking(volume, fp->forkid,offset,size)) {
|
|
/* There was an irrecoverable error when locking */
|
|
ret=EBUSY;
|
|
goto error;
|
|
}
|
|
|
|
ret=0;
|
|
while (*totalwritten < size) {
|
|
sizetowrite=max_packet_size;
|
|
if ((size-*totalwritten)<max_packet_size)
|
|
sizetowrite=size-*totalwritten;
|
|
|
|
if (volume->server->using_version->av_number < 30)
|
|
ret=afp_write(volume, fp->forkid,
|
|
offset+o,sizetowrite,
|
|
(char *) data+o,&ignored32);
|
|
else
|
|
ret=afp_writeext(volume, fp->forkid,
|
|
offset+o,sizetowrite,
|
|
(char *) data+o,&ignored);
|
|
ret=0;
|
|
*totalwritten+=sizetowrite;
|
|
switch(ret) {
|
|
case kFPAccessDenied:
|
|
err=EACCES;
|
|
goto error;
|
|
case kFPDiskFull:
|
|
err=ENOSPC;
|
|
goto error;
|
|
case kFPLockErr:
|
|
case kFPMiscErr:
|
|
case kFPParamErr:
|
|
err=EINVAL;
|
|
goto error;
|
|
}
|
|
o+=sizetowrite;
|
|
}
|
|
if (ll_handle_unlocking(volume, fp->forkid,offset,size)) {
|
|
/* Somehow, we couldn't unlock the range. */
|
|
ret=EIO;
|
|
goto error;
|
|
}
|
|
return 0;
|
|
|
|
error:
|
|
return -err;
|
|
|
|
|
|
}
|
|
|
|
|