afpfs-ng-mac/lib/lowlevel.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((mode_t *)&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;
}