/*
    CFS0.11 filesystem implementation for FUSE
    Copyright (C) 2005-2009 Kajtar Zsolt (Soci/Singular) <soci@c64.rulez.org>

    This program can be distributed under the terms of the GNU GPL.
    See the file COPYING.
*/
//#define DEBUG_CFS

#define _LARGEFILE64_SOURCE

#define FUSE_USE_VERSION 26

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef linux
/* For pread()/pwrite() */
#define _XOPEN_SOURCE 500
#endif

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <errno.h>
#include <sys/statfs.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>

#define offset_t unsigned

pthread_mutex_t bam_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t dir_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t big_lock=PTHREAD_MUTEX_INITIALIZER;

off_t fs_size=0;//constant
int geometry_head=0;//constant
int geometry_sector=0;//constant
off_t fs_free=0;//protected by bam_lock
int fs_free_updated=0;//protected by bam_lock
struct tm tm_default;//constant

int f1=-1;//constant
typedef struct {
    int valid;
    int bam;
    char name[17];
    offset_t rootdir;
    offset_t deldir;
    offset_t int start;
    offset_t int end;
} T_PART;

typedef struct {
    offset_t treeroot;
    off_t size;
    int partition;
    offset_t dir_sector;
    int dir_offset;
} T_FILE;

typedef struct {
    mode_t attr;
    off_t size;
    int partition;
    int file_offset;
    offset_t dir_sector;
    int empty_offset;
    offset_t empty_sector;
    offset_t this_dir;
    mode_t this_dir_attr;
    offset_t end_of_this_dir;
    offset_t root;
    char name[16+1];
    char type[3+1];
    unsigned char date[4];
} T_RESOLV;

T_PART partitions[16];//constant

void to_petscii(const char *st, char *st2) {
    unsigned char *s=(unsigned char *)st, *s2=(unsigned char *)st2;
    while ((*s2=*s)) {
        if (*s>=0x41 && *s<=0x5b) *s2=*s+0x80;
        if (*s==0x5f) *s2=0xa4;
        if (*s==0xa4) *s2=0x5f;
        if (*s>=0x61 && *s<=0x7b) *s2=*s-0x20;
        if (*s>=0xc1 && *s<=0xdb) *s2=*s-0x60;
        s++;s2++;
    }
}

void to_ascii(const char *st, char *st2) {
    unsigned char *s=(unsigned char *)st, *s2=(unsigned char *)st2;
    while ((*s2=*s)) {
        if (*s>=0xc1 && *s<=0xdb) *s2=*s-0x80;
        if (*s==0xa4) *s2=0x5f;
        if (*s==0x5f) *s2=0xa4;
        if (*s>=0x41 && *s<=0x5b) *s2=*s+0x20;
        if (*s>=0x61 && *s<=0x7b) *s2=*s+0x60;
        s++;s2++;
    }
}

offset_t sector2offset(unsigned char *ez) {
    if (ez[0] & 0x40) {
        return ez[3]+(ez[2] << 8)+(ez[1] << 16)+((ez[0] & 0x0f) << 24);
    } else {
        return (((ez[1] << 8)+ez[2])*geometry_head+(ez[0] & 0x0f))*geometry_sector+ez[3]-1;
    }
}

void offset2sector(unsigned char *ide, offset_t ezt) {
    if (geometry_head & 0x40) {
        ide[3]=ezt;
        ide[2]=ezt >> 8;
        ide[1]=ezt >> 16;
        ide[0]=((ezt >> 24) & 0x0f) | 0x40;
    } else {
        ide[3]=(ezt % geometry_sector)+1;
        ezt/=geometry_sector;
        ide[0]=ezt % geometry_head;
        ezt/=geometry_head;
        ide[1]=ezt >> 8;
        ide[2]=ezt;
    }
}

void offset2sector_insert(unsigned char *ide, offset_t ezt) {
    if (geometry_head & 0x40) {
        ide[3]=ezt;
        ide[2]=ezt >> 8;
        ide[1]=ezt >> 16;
        ide[0]=((ezt >> 24) & 0x0f) | 0x40 | (ide[0] & 0xb0);
    } else {
        ide[3]=(ezt % geometry_sector)+1;
        ezt/=geometry_sector;
        ide[0]=(ezt % geometry_head) | (ide[0] & 0xb0);
        ezt/=geometry_head;
        ide[1]=ezt >> 8;
        ide[2]=ezt;
    }
}

int is_null_sector(unsigned char *ide) {
    return ((ide[0] & 0x4f) | ide[1] | ide[2] | ide[3])==0;
}

void write_sector(unsigned char *ezt, offset_t ide) {
    if (f1<0) return;
    pwrite(f1, ezt, 512, (off_t)ide << 9);
//    fprintf(stderr,"WRITE (%u)\n",ide);
}

void read_sector(unsigned char *ezt, offset_t ide) {
    if (!f1) return;
    pread(f1, ezt, 512, (off_t)ide << 9);
//    fprintf(stderr,"READ (%u)\n",ide);
}

void fill_in_time(unsigned char *sbuff, time_t ee) {
    struct tm ido2;

    localtime_r(&ee, &ido2);

    ido2.tm_mon++;
    sbuff[0]=(ido2.tm_sec % 60) | ((ido2.tm_mon << 4) & 0xc0);
    sbuff[1]=ido2.tm_min | (ido2.tm_mon << 6);
    sbuff[2]=((ido2.tm_year-80) % 100) | ((ido2.tm_hour << 3) & 0xc0);
    sbuff[3]=ido2.tm_mday | (ido2.tm_hour << 5);
}

void read_in_time(struct stat *at, unsigned char *t) {
    struct tm ido2=tm_default;

    ido2.tm_sec=t[0] & 0x3f;
    ido2.tm_min=t[1] & 0x3f;
    ido2.tm_year=(t[2] & 0x3f)+80;
    ido2.tm_mday=t[3] & 0x1f;
    ido2.tm_mon=((t[0] >> 4) & 0x0c) | (t[1] >> 6);
    ido2.tm_hour=((t[2] >> 3) & 0x18) | (t[3] >> 5);
    ido2.tm_mon--;

    at->st_mtime=mktime(&ido2);
    at->st_atime=at->st_mtime;
    at->st_ctime=at->st_mtime;
}

offset_t find_free_sector(int partition, offset_t start, int data) {
    offset_t itt;
    unsigned char bam[512];
    int b,bamm;

    if (start<partitions[partition].start) return 0;
    if (start>partitions[partition].end) return 0;

    bamm=partitions[partition].bam-partitions[partition].start;

    pthread_mutex_lock(&bam_lock);

back:
    itt=start-((start-partitions[partition].start) & 0xfff);
    do {
        read_sector(bam, itt+bamm);
        bam[0]&=0x3f;
        b=data?128:0;
        do {
            if (bam[b >> 3]) {
                if ((bam[b >> 3] << (b & 7)) & 0x80) goto ok;
                b++;
            } else b+=8;
        } while (b<4096);
        itt+=4096;
    } while (itt < partitions[partition].end);

    itt=start-((start-partitions[partition].start) & 0xfff);
    do {
        read_sector(bam, itt+bamm);
        bam[0]&=0x3f;
        b=data?128:0;
        do {
            if (bam[b >> 3]) {
                if ((bam[b >> 3] << (b & 7)) & 0x80) goto ok;
                b++;
            } else b+=8;
        } while (b<4096);
        if (itt<4096) break;
        itt-=4096;
    } while (itt > partitions[partition].start);
    if (data) {
        data=0;
        goto back;
    }
    pthread_mutex_unlock(&bam_lock);
    return 0;
ok:
    bam[b >> 3]&=~(128 >> (b & 7));
    fs_free--;

    write_sector(bam,itt+bamm);

    pthread_mutex_unlock(&bam_lock);

    return itt+b;
}

offset_t find_free_sector2(int partition, offset_t start, unsigned char *bam, offset_t *bam_offset, int data) {
    offset_t itt;
    int b, bamm;

    if (start<partitions[partition].start) return 0;
    if (start>partitions[partition].end) return 0;

    bamm=partitions[partition].bam-partitions[partition].start;

back:
    itt=start-((start-partitions[partition].start) & 0xfff);
    do {
        if ((itt+bamm)!=*bam_offset) {
            if (*bam_offset) write_sector(bam, *bam_offset);

            read_sector(bam, itt+bamm);
            *bam_offset=itt+bamm;
        }
        bam[0]&=0x3f;
        b=data?128:0;
        do {
            if (bam[b >> 3]) {
                if ((bam[b >> 3] << (b & 7)) & 0x80) goto ok;
                b++;
            } else b+=8;
        } while (b<4096);
        itt+=4096;
    } while (itt < partitions[partition].end);

    itt=start-((start-partitions[partition].start) & 0xfff);
    do {
        if ((itt+bamm)!=*bam_offset) {
            if (*bam_offset) write_sector(bam, *bam_offset);

            read_sector(bam, itt+bamm);
            *bam_offset=itt+bamm;
        }
        bam[0]&=0x3f;
        b=data?128:0;
        do {
            if (bam[b >> 3]) {
                if ((bam[b >> 3] << (b & 7)) & 0x80) goto ok;
                b++;
            } else b+=8;
        } while (b<4096);
        if (itt<4096) break;
        itt-=4096;
    } while (itt > partitions[partition].start);
    if (data) {
        data=0;
        goto back;
    }
    return 0;
ok:
    bam[b >> 3]&=~(128 >> (b & 7));
    fs_free--;

    return itt+b;
}

void free_this_sector(int partition, offset_t thiss) {
    offset_t itt;
    unsigned char bam[512];
    int b;

    if (thiss<partitions[partition].start) return;
    if (thiss>partitions[partition].end) return;

    b=(thiss-partitions[partition].start) & 0xfff;
    itt=thiss-b;
    itt+=partitions[partition].bam-partitions[partition].start;

    if (b>1) {
        pthread_mutex_lock(&bam_lock);
        read_sector(bam, itt);

        if (~bam[b >> 3] & (128 >> (b & 7))) fs_free++;
        bam[b >> 3]|=128 >> (b & 7);

        write_sector(bam,itt);
        pthread_mutex_unlock(&bam_lock);
    }

    return;
}

void free_this_sector2(int partition, offset_t thiss, unsigned char *bam, offset_t *bam_offset) {
    offset_t itt;
    int b;

    if (thiss<partitions[partition].start) return;
    if (thiss>partitions[partition].end) return;

    b=(thiss-partitions[partition].start) & 0xfff;
    itt=thiss-b;
    itt+=partitions[partition].bam-partitions[partition].start;

    if (itt!=*bam_offset) {
        if (*bam_offset) write_sector(bam, *bam_offset);

        read_sector(bam, itt);
        *bam_offset=itt;
    }

    if (b>1) {
        if (~bam[b >> 3] & (128 >> (b & 7))) fs_free++;
        bam[b >> 3]|=128 >> (b & 7);
    }

    return;
}

#define RESOLV_DIR2 0
#define RESOLV_DIR 1
#define RESOLV_FILE 2
#define RESOLV_ALL 3
int resolv_path(const char *path, T_RESOLV *at, int smode) {
    offset_t itt;
    const char *p=path, *p2;
    int i,j;
    unsigned char next_sector[4];
    unsigned char sbuff[512];
    int cmode=0;
    int nameok, first;
    char c64name[17], pcname[17];

    at->date[0]=0;
    at->date[1]=64;
    at->date[2]=0;
    at->date[3]=1;
    at->root=0;
    at->empty_sector=0;
    at->partition=0;
    at->attr=S_IFDIR | 0755;
    at->this_dir_attr=S_IFDIR | 0755;
    at->size=512;
    at->file_offset=-1;
    while (*p=='/') p++;
    if (!*p) return 0;

    i=atol(p)-1;
    if (i<0 || i>15) return -1;
    if (!partitions[i].valid) return -1;
    itt=partitions[i].rootdir;
    at->partition=i;
    while (*p!='/' && *p) p++;
    at->root=itt;
    if (!*p) return 0;

nextdir:
    at->empty_sector=0;
    at->this_dir=itt;
    while (*p=='/') p++;
    p2=p;
    while (*p2!='/' && *p2) p2++;
    if (*p2) cmode=RESOLV_DIR2; else cmode=smode;
    first=1;
next:
    at->end_of_this_dir=itt;
    read_sector(sbuff, itt);
    if (first) {at->this_dir_attr=(((sbuff[0x18] >> 3) & 7)*0111) & 0755; first=0;}

    for (i=0;i<512;i+=32) {
        next_sector[3-(i >> 7)]=(next_sector[3-(i >> 7)] << 2) | ((sbuff[i+0x14] >> 4) & 3);
        if ((sbuff[i+0x18] & 0x80)==0 || (sbuff[i+0x18] & 0x07)==0) {
            if ((sbuff[i+0x18] & 0x87)==0 && at->empty_sector==0) {
                at->empty_offset=i;
                at->empty_sector=itt;
            }
            continue;
        }
//        fprintf(stderr,"%d \n",cmode);
        if ((sbuff[i+0x18] & 0x87)==0x83 && cmode==RESOLV_FILE) continue;
        if ((sbuff[i+0x18] & 0x87)!=0x83 && (cmode==RESOLV_DIR || cmode==RESOLV_DIR2)) continue;
        memcpy(c64name, sbuff+i, 16);c64name[16]=0;
        to_ascii(c64name, pcname);
        j=0;
        while (pcname[j]==p[j] && pcname[j] && j<16 && p[j] && p[j]!='/' && p[j]!=',') j++;
        if (pcname[j] && j!=16) continue;
        if (p[j]!='/' && p[j]!=',' && p[j] && j!=16) continue;

        while (p[j] && p[j]!='/' && p[j]!=',') j++;
        if (!p[j] || p[j]=='/')
            switch (sbuff[i+0x18] & 0x87) {
            case 0x82:nameok=!memcmp(sbuff+i+0x19,"REL",3);break;
            case 0x83:nameok=!memcmp(sbuff+i+0x19,"DIR",3);break;
            case 0x84:nameok=!memcmp(sbuff+i+0x19,"LNK",3);break;
            default:nameok=!memcmp(sbuff+i+0x19,"PRG",3);
            }
        else {
            int k;
            j++;
            k=0;
            if (p[j]=='p' && (p[j+1]=='/' || !p[j+1]) && !memcmp(sbuff+i+0x19,"PRG",3)) nameok=1;
            else if (p[j]=='s' && (p[j+1]=='/' || !p[j+1]) && !memcmp(sbuff+i+0x19,"SEQ",3)) nameok=1;
            else if (p[j]=='u' && (p[j+1]=='/' || !p[j+1]) && !memcmp(sbuff+i+0x19,"USR",3)) nameok=1;
            else {
                memcpy(c64name, sbuff+i+0x19, 3);c64name[3]=0;
                to_ascii(c64name, pcname);
                while (pcname[k]==p[j+k] && pcname[k] && k<3 && p[j+k] && p[j+k]!='/') k++;
                nameok=(!pcname[k] || k==3);
                j+=k;
            }
            while (p[j] && p[j]!='/') j++;
        }
        if (nameok && !p[j]) {
            at->dir_sector=itt;
            at->file_offset=i;
            itt=sector2offset(sbuff+i+0x14);
            at->size=sbuff[i+0x10]+(sbuff[i+0x11] << 8)+(sbuff[i+0x12] << 16)+(sbuff[i+0x13] << 24);
            memcpy(at->date,sbuff+i+0x1c,4);
            at->attr=(((sbuff[i+0x18] >> 3) & 7)*0111) & 0755;
            if ((sbuff[i+0x18] & 0x07)==0x02) at->size&=0xffffff;
            switch (sbuff[i+0x18] & 0x07) {
            case 3:at->attr|=S_IFDIR;at->size=512;break;
            case 4:at->attr|=S_IFLNK;break;
            default:at->attr|=S_IFREG;
            }
            at->root=itt;
            return 0;
        }
        if (nameok && p[j]=='/') {
            p+=j;
            itt=sector2offset(sbuff+i+0x14);
            goto nextdir;
        }
    }
    if (!is_null_sector(next_sector)) {
        itt=sector2offset(next_sector);
        goto next;
    }

    i=0;
    while (p[i] && p[i]!='/' && p[i]!=',' && i<16) {
        pcname[i]=p[i];
        i++;
    }
    pcname[i]=0;to_petscii(pcname, at->name);
    while (p[i] && p[i]!=',') i++;
    if (p[i]==',') {
        int j=0;
        i++;
        while (p[i] && p[i]!='/' && j<3) {
            pcname[j]=p[i];
            i++;j++;
        }
        pcname[j]=0;to_petscii(pcname, at->type);
    } else memcpy(at->type,"PRG",4);

    return -1;
}

static int cfs011_getattr(const char *path, struct stat *stbuf)
{
    T_RESOLV at;

#ifdef DEBUG_CFS
    fprintf(stderr,"getattr %s\n",path);
#endif

    memset(stbuf,0,sizeof(struct stat));
    stbuf->st_uid=fuse_get_context()->uid;
    stbuf->st_gid=fuse_get_context()->gid;
    stbuf->st_blksize=512;

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        if (resolv_path(path, &at, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -ENOENT;
        }
    }
    pthread_mutex_unlock(&dir_lock);

    read_in_time(stbuf, at.date);
    stbuf->st_mode=at.attr;
    stbuf->st_size=at.size;
    stbuf->st_ino=at.root/512;
    stbuf->st_blocks=(stbuf->st_size/512)+(stbuf->st_size/512/128)+1;

    if (at.root==0) {
        stbuf->st_nlink=3;
	return 0;
    }

    stbuf->st_nlink = (stbuf->st_mode & S_IFDIR)?3:1;
    return 0;
}

static int cfs011_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                       off_t offset, struct fuse_file_info *fi)
{
    char name[16+4+1];
    int i,j;
    offset_t itt;
    unsigned char next_sector[4];
    unsigned char sbuff[512];
    struct stat st;
    T_RESOLV at;
    char c64name[17];

    (void) offset;
    (void) fi;

#ifdef DEBUG_CFS
    fprintf(stderr,"readdir %s\n",path);
#endif

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        pthread_mutex_unlock(&dir_lock);
        return -ENOENT;
    }

    if (at.root==0) {
        for (i=0;i<16;i++) {
	    if (!partitions[i].valid) continue; // valid?
            sprintf(name,"%02d %s", i+1, partitions[i].name);
            st.st_ino=partitions[i].rootdir/512;
            st.st_mode=S_IFDIR | 0755;
	    filler(buf, name, &st, 0);
        }
        pthread_mutex_unlock(&dir_lock);
	return 0;
    }
    itt=at.root;
next:
    read_sector(sbuff, itt);

    for (i=0;i<512;i+=32) {
        next_sector[3-(i >> 7)]=(next_sector[3-(i >> 7)] << 2) | ((sbuff[i+0x14] >> 4) & 3);
        if ((sbuff[i+0x18] & 0x87)==0x03) {
            st.st_ino=sector2offset(sbuff+i+0x10);
            st.st_mode=S_IFDIR | 0755;
            read_in_time(&st, sbuff+i+0x1c);
            filler(buf, ".", &st, 0);
            st.st_ino=sector2offset(sbuff+i+0x14);
            filler(buf, "..", &st, 0);
            continue;
        }
        if ((sbuff[i+0x18] & 0x80)==0 || (sbuff[i+0x18] & 0x07)==0) continue;
        memcpy(c64name, sbuff+i, 16);c64name[16]=0;
        to_ascii(c64name, name);
        if ((sbuff[i+0x18] & 0x07)!=3 && (sbuff[i+0x18] & 0x07)!=4) {
            j=strlen(name);
            name[j++]=',';
            memcpy(c64name, sbuff+i+0x19, 3);c64name[3]=0;
            to_ascii(c64name, name+j);
        }
        read_in_time(&st, sbuff+i+0x1c);
        st.st_ino=sector2offset(sbuff+i+0x14);
        st.st_mode=(((sbuff[i+0x18] >> 3) & 7)*0111) & 0755;
        switch (sbuff[i+0x18] & 0x07) {
        case 1:
	case 2:st.st_mode|=S_IFREG;break;
        case 3:st.st_mode|=S_IFDIR;break;
        case 4:st.st_mode|=S_IFLNK;break;
        }
        filler(buf, name, &st, 0);
    }
    if (!is_null_sector(next_sector)) {
        itt=sector2offset(next_sector);
        goto next;
    }
    pthread_mutex_unlock(&dir_lock);
    return 0;
}

static int cfs011_open(const char *path, struct fuse_file_info *fi)
{
    T_FILE *file;
    T_RESOLV at;

//    if ((fi->flags & O_ACCMODE)!=O_RDONLY) return -EROFS;

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path,&at, RESOLV_FILE)) {
        pthread_mutex_unlock(&dir_lock);
        return -ENOENT;
    }
    pthread_mutex_unlock(&dir_lock);
    if (at.root==0) return -ENOENT;
    file=malloc(sizeof(T_FILE));
    file->treeroot=at.root;
    file->size=at.size;
    file->partition=at.partition;
    file->dir_sector=at.dir_sector;
    file->dir_offset=at.file_offset;
    fi->fh=(uint64_t) (uintptr_t) file;

    return 0;
}

static int cfs011_mknod(const char *path, mode_t mode, dev_t rdev)
{
    unsigned char next_sector[4];
    offset_t empty;
    unsigned char sbuff[512];
    T_RESOLV at;
    int i;
    time_t ee;
    (void) rdev;

    pthread_mutex_lock(&big_lock);

#ifdef DEBUG_CFS
    fprintf(stderr,"mknod %s\n",path);
#endif

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_FILE)) {

        if ((at.this_dir_attr & S_IWUSR)==0) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -EACCES;
        }

        for (i=0;i<16;i++) {
            if (at.name[i]=='*' || at.name[i]=='?' || at.name[i]==':' || at.name[i]=='=') {
                pthread_mutex_unlock(&dir_lock);
                pthread_mutex_unlock(&big_lock);
                return -EINVAL;
            }
            if (!at.name[i]) break;
        }
        for (i=0;i<3;i++) {
            if (at.type[i]=='*' || at.type[i]=='?' || at.type[i]==':' || at.type[i]=='=' || at.type[i]==',' || at.type[i]=='<' || at.type[i]=='>' || at.type[i]==' ') {
                pthread_mutex_unlock(&dir_lock);
                pthread_mutex_unlock(&big_lock);
                return -EINVAL;
            }
            if (!at.type[i]) break;
        }
        if (!strcmp(at.type,"DEL") || !strcmp(at.type,"REL") ||
            !strcmp(at.type,"DIR") || !strcmp(at.type,"LNK") ||
            (at.type[0] && at.type[0]!='P' && at.type[0]!='S' && at.type[0]!='U' && !at.type[1])) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -EINVAL;
        }
        if (!strcmp(at.type,"P")) strcpy(at.type,"PRG"); else
            if (!strcmp(at.type,"S")) strcpy(at.type,"SEQ"); else
                if (!strcmp(at.type,"U")) strcpy(at.type,"USR");

        if (!(S_IFREG & mode)) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -ENOSYS;
        }

        if (at.empty_sector==0) {
            empty=find_free_sector(at.partition, at.this_dir, 0);
            if (!empty) {
                pthread_mutex_unlock(&dir_lock);
                pthread_mutex_unlock(&big_lock);
                return -ENOSPC;
            }
            memset(sbuff,0,512);
            write_sector(sbuff,empty);
            at.empty_sector=empty;
            at.empty_offset=0;

            read_sector(sbuff,at.end_of_this_dir);
            offset2sector(next_sector, empty);
            for (i=0;i<512;i+=32) {
                sbuff[i+0x14]=(sbuff[i+0x14] & 0xcf) | ((next_sector[3-(i >> 7)] >> 2) & 0x30);
                next_sector[3-(i >> 7)]<<=2;
            }
            write_sector(sbuff,at.end_of_this_dir);
        }

        empty=find_free_sector(at.partition, at.empty_sector, 0);
        if (!empty) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -ENOSPC;
        }

        memset(sbuff,0,512);
        write_sector(sbuff,empty);

        read_sector(sbuff,at.empty_sector);
        memcpy(sbuff+at.empty_offset,at.name,16);
        memset(sbuff+at.empty_offset+0x10,0,4);
        offset2sector_insert(sbuff+at.empty_offset+0x14, empty);
        sbuff[at.empty_offset+0x18]=0xc1 | ((mode & 0700) >> 3);
        memcpy(sbuff+at.empty_offset+0x19,at.type,3);

        ee=time(NULL);
        fill_in_time(sbuff+at.empty_offset+0x1c, ee);

        write_sector(sbuff,at.empty_sector);
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return 0;
    }
    pthread_mutex_unlock(&dir_lock);
    pthread_mutex_unlock(&big_lock);
    return -EEXIST;
}

static int cfs011_read(const char *path, char *buf, size_t size, off_t offset,
                    struct fuse_file_info *fi)
{
    unsigned char next_sector[4];
    off_t last_offset;
    off_t fsize;
    offset_t root,itt;
    unsigned char sbuff[512],tbuff[512];
    int read;
    T_FILE *file;
    (void) path;

    pthread_mutex_lock(&big_lock);

    read=0;
    last_offset=1;
    file=(T_FILE *) (uintptr_t) fi->fh;

    root=file->treeroot;

    read_sector(sbuff,file->dir_sector);

    fsize=sbuff[file->dir_offset+0x10];
    fsize+=sbuff[file->dir_offset+0x11] << 8;
    fsize+=sbuff[file->dir_offset+0x12] << 16;
    if ((sbuff[file->dir_offset+0x18] & 0x87)!=0x82)
        fsize+=sbuff[file->dir_offset+0x13] << 24;

    if (offset>=fsize) {
        pthread_mutex_unlock(&big_lock);
        return 0;
    }
    if ((offset+size)>fsize) size=fsize-offset;

    do {
        unsigned int m=offset/512/128;
        unsigned int m2=0;
        unsigned char level=0;
        int i;

        if ((last_offset^offset) & 0xffff01ff) {
            last_offset=offset & 0xffff0000;

            while (m) {
                m--;
                m2<<=1;m2|=m & 1;m>>=1;
                m2<<=1;m2|=m & 1;m>>=1;
                m2<<=1;m2|=m & 1;m>>=1;
                level++;
            }

            //start from root
            itt=root;//fprintf(stderr,"%d (%o)",level,m2);

            while (level) {
                read_sector(tbuff, itt);

                for (i=0;i<16;i++) next_sector[3-(i >> 2)]=(next_sector[3-(i >> 2)] << 2) | ((tbuff[(m2 & 0x07)*64+i*4] & 0x30) >> 4);

                if (is_null_sector(next_sector)) {// it does not exists yet! (empty sector)
                    memset(tbuff,0,512);goto hole;
                } else itt=sector2offset(next_sector);

                m2>>=3;
                level--;
            }

            read_sector(tbuff, itt);
        }
        hole:

        {
            int i;
            unsigned char buf2[512];

            if (is_null_sector(tbuff+((offset/512) & 0x7f)*4)) {
                memset(sbuff, 0, 512);
            } else {
                itt=sector2offset(tbuff+((offset/512) & 0x7f)*4);
                read_sector(buf2, itt);
                for (i=0;i<128;i++) {
                    sbuff[i]=buf2[i*4];
                    sbuff[i+128]=buf2[i*4+1];
                    sbuff[i+256]=buf2[i*4+2];
                    sbuff[i+384]=buf2[i*4+3];
                }
            }
        }
        if (offset & 511) {
            if (size<(512-(offset & 511))) {
                memcpy(buf,sbuff+(offset & 511),size);
                read+=size;
                buf+=size;
                offset+=size;
                size=0;
            } else {
                memcpy(buf,sbuff+(offset & 511),512-(offset & 511));
                read+=512-(offset & 511);
                buf+=512-(offset & 511);
                size-=512-(offset & 511);
                offset+=512-(offset & 511);
            }
        } else {
            if (size<512) {
                memcpy(buf,sbuff,size);
                read+=size;
                buf+=size;
                offset+=size;
                size=0;
            }
            else {
                memcpy(buf,sbuff,512);
                read+=512;
                buf+=512;
                offset+=512;
                size-=512;
            }
        }

    } while (size);

    pthread_mutex_unlock(&big_lock);
    return read;
}

static int cfs011_write(const char *path, const char *buf, size_t size,
                     off_t offset, struct fuse_file_info *fi)
{
    unsigned char next_sector[4];
    off_t last_offset;
    off_t fsize;
    offset_t itt,empty;
    unsigned char sbuff[512],tbuff[512],bam[512];
    offset_t bam_offset;
    int write, first;
    T_FILE *file;
    (void) path;

#ifdef DEBUG_CFS
    fprintf(stderr,"write %d\n", size);
#endif

    pthread_mutex_lock(&big_lock);

    bam_offset=0;
    write=0;
    last_offset=0;
    file=(T_FILE *) (uintptr_t) fi->fh;
    first=1;

    pthread_mutex_lock(&bam_lock);

    do {
        unsigned int m=offset/512/128;
        unsigned int m2=0;
        unsigned char level=0;
        int i;

        if (((last_offset^offset) & 0xffff0000) || first) {
            if (!first) {
                if (bam_offset) write_sector(bam, bam_offset);
                write_sector(tbuff, itt);
            }
            last_offset=offset & 0xffff0000;first=0;

            while (m) {
                m--;
                m2<<=1;m2|=m & 1;m>>=1;
                m2<<=1;m2|=m & 1;m>>=1;
                m2<<=1;m2|=m & 1;m>>=1;
                level++;
            }

            //start from root
            itt=file->treeroot;//fprintf(stderr,"%d (%o)",level,m2);

            while (level) {
                read_sector(tbuff, itt);

                for (i=0;i<16;i++)
                    next_sector[3-(i >> 2)]=(next_sector[3-(i >> 2)] << 2) | ((tbuff[(m2 & 7)*64+i*4] & 0x30) >> 4);

                if (is_null_sector(next_sector)) {// it does not exists yet! (empty sector)
                    empty=find_free_sector2(file->partition, itt, bam, &bam_offset, 0);
                    if (!empty) {
                        pthread_mutex_unlock(&bam_lock);
                        pthread_mutex_unlock(&big_lock);
                        return -ENOSPC;
                    }
                    memset(sbuff,0,512);
                    write_sector(sbuff, empty);
                    offset2sector(next_sector, empty);

                    for (i=0;i<16;i++) {
                        tbuff[(m2 & 7)*64+i*4]=(tbuff[(m2 & 7)*64+i*4] & 0xcf) | ((next_sector[3-(i >> 2)] >> 2) & 0x30);
                        next_sector[3-(i >> 2)]<<=2;
                    }
                    if (bam_offset) write_sector(bam, bam_offset);
                    write_sector(tbuff, itt);
                    itt=empty;
                } else itt=sector2offset(next_sector);

                m2>>=3;
                level--;
            }

            read_sector(tbuff, itt);
        }

        empty=sector2offset(tbuff+((offset >> 7) & 0x1fc));

        if (is_null_sector(tbuff+((offset >> 7) & 0x1fc))) {
            empty=find_free_sector2(file->partition, itt, bam, &bam_offset, 1);
            if (!empty) {
                pthread_mutex_unlock(&bam_lock);
                pthread_mutex_unlock(&big_lock);
                return -ENOSPC;
            }

            offset2sector_insert(tbuff+((offset >> 7) & 0x1fc), empty);

            if ((offset & 511) || size<512) memset(sbuff,0,512);
        } else {
            if ((offset & 511) || size<512) read_sector(sbuff, empty);
        }
        if (offset & 511) {
            if (size<(512-(offset & 511))) {
                unsigned i;
                for (i=(offset & 511);i<((offset & 511)+size);i++) sbuff[(i >> 7)+((i << 2) & 0x1fc)]=buf[i-(offset & 511)];
                write+=size;
                buf+=size;
                offset+=size;
                size=0;
            } else {
                unsigned i;
                for (i=(offset & 511);i<512;i++) sbuff[(i >> 7)+((i << 2) & 0x1fc)]=buf[i-(offset & 511)];
                write+=512-(offset & 511);
                buf+=512-(offset & 511);
                size-=512-(offset & 511);
                offset+=512-(offset & 511);
            }
        } else {
            if (size<512) {
                unsigned i;
                for (i=0;i<size;i++) sbuff[(i >> 7)+((i << 2) & 0x1fc)]=buf[i];
                write+=size;
                buf+=size;
                offset+=size;
                size=0;
            } else {
                unsigned i;
                for (i=0;i<512;i++) sbuff[(i >> 7)+((i << 2) & 0x1fc)]=buf[i];
                write+=512;
                buf+=512;
                offset+=512;
                size-=512;
            }
        }
        write_sector(sbuff, empty);
        if (bam_offset) write_sector(bam, bam_offset);

    } while (size);
    
    pthread_mutex_unlock(&bam_lock);
    write_sector(tbuff, itt);

    pthread_mutex_lock(&dir_lock);
    read_sector(sbuff,file->dir_sector);

    fsize=sbuff[file->dir_offset+0x10];
    fsize+=sbuff[file->dir_offset+0x11] << 8;
    fsize+=sbuff[file->dir_offset+0x12] << 16;
    if ((sbuff[file->dir_offset+0x18] & 0x87)!=0x82)
        fsize+=sbuff[file->dir_offset+0x13] << 24;

    if (fsize<offset) {
        file->size=offset;

        sbuff[file->dir_offset+0x10]=file->size;
        sbuff[file->dir_offset+0x11]=file->size >> 8;
        sbuff[file->dir_offset+0x12]=file->size >> 16;
        if ((sbuff[file->dir_offset+0x18] & 0x87)!=0x82)
            sbuff[file->dir_offset+0x13]=file->size >> 24;
    
        write_sector(sbuff,file->dir_sector);
    }
    pthread_mutex_unlock(&dir_lock);

    pthread_mutex_unlock(&big_lock);
    return write;
}

static int cfs011_chmod(const char *path, mode_t mode)
{
    T_RESOLV at;
    unsigned char sbuff[512];

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        if (resolv_path(path, &at, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -ENOENT;
        }
    }
    if (at.root==0) {
        pthread_mutex_unlock(&dir_lock);
        return -ENOENT;
    }
    if (at.root==partitions[at.partition].deldir) {
        pthread_mutex_unlock(&dir_lock);
        return -ENOENT;
    }
    if (at.root==partitions[at.partition].rootdir) {
        pthread_mutex_unlock(&dir_lock);
        return 0;
    }

    read_sector(sbuff,at.dir_sector);

    sbuff[at.file_offset+0x18]=(sbuff[at.file_offset+0x18] & 0xc7) | ((mode & 0700) >> 3);

    write_sector(sbuff,at.dir_sector);

    if ((sbuff[at.file_offset+0x18] & 0x87)==0x83) {
        read_sector(sbuff,at.root);
        sbuff[0x18]=(sbuff[0x18] & 0xc7) | ((mode & 0700) >> 3);
        write_sector(sbuff,at.root);
    }
    pthread_mutex_unlock(&dir_lock);

    return 0;
}

static int cfs011_chown(const char *path, uid_t uid, gid_t gid)
{
    T_RESOLV at;
    (void) uid;
    (void) gid;

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        if (resolv_path(path, &at, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -ENOENT;
        }
    }
    pthread_mutex_unlock(&dir_lock);

    if (at.root==0) return -ENOENT;
    return 0;
}

static int cfs011_utime(const char *path, struct utimbuf *buf)
{
    T_RESOLV at;
    unsigned char sbuff[512];
    (void) buf;

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        if (resolv_path(path, &at, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -ENOENT;
        }
    }
    if (at.root==0) {
        pthread_mutex_unlock(&dir_lock);
        return -ENOENT;
    }
    if (at.root==partitions[at.partition].rootdir) {
        pthread_mutex_unlock(&dir_lock);
        return 0;
    }

    read_sector(sbuff,at.dir_sector);

    fill_in_time(sbuff+at.file_offset+0x1c, buf->modtime);

    write_sector(sbuff,at.dir_sector);
    pthread_mutex_unlock(&dir_lock);

    return 0;
}

static int cfs011_symlink(const char *from, const char *to)
{
    unsigned char next_sector[4];
    offset_t empty;
    T_RESOLV at;
    unsigned char sbuff[512];
    int i;
    time_t ee;

    pthread_mutex_lock(&big_lock);

    pthread_mutex_lock(&dir_lock);
    if (!resolv_path(to, &at, RESOLV_FILE)) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -EEXIST;
    }

    for (i=0;i<16;i++) {
        if (at.name[i]=='*' || at.name[i]=='?' || at.name[i]==':' || at.name[i]=='=' || at.name[i]==',') {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -EINVAL;
        }
        if (!at.name[i]) break;
    }

    if (at.empty_sector==0) {
        int i;
        empty=find_free_sector(at.partition, at.this_dir, 0);
        if (!empty) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -ENOSPC;
        }
        memset(sbuff,0,512);
        write_sector(sbuff,empty);
        at.empty_sector=empty;
        at.empty_offset=0;

        read_sector(sbuff,at.end_of_this_dir);
        offset2sector(next_sector, empty);
        for (i=0;i<512;i+=32) {
            sbuff[i+0x14]=(sbuff[i+0x14] & 0xcf) | ((next_sector[3-(i >> 7)] >> 2) & 0x30);
            next_sector[3-(i >> 7)]<<=2;
        }
        write_sector(sbuff,at.end_of_this_dir);
    }

    empty=find_free_sector(at.partition, at.empty_sector, 0);
    if (!empty) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOSPC;
    }

    memset(sbuff,0,512);
    to_petscii(from, (char *)sbuff);

    write_sector(sbuff,empty);

    read_sector(sbuff,at.empty_sector);

    memcpy(sbuff+at.empty_offset,&at.name,16);
    memset(sbuff+at.empty_offset+0x10,0,4);
    sbuff[at.empty_offset+0x10]=strlen(from);
    offset2sector_insert(sbuff+at.empty_offset+0x14, empty);
    sbuff[at.empty_offset+0x18]=0xfc;
    memcpy(sbuff+at.empty_offset+0x19,"LNK",3);

    ee=time(NULL);
    fill_in_time(sbuff+at.empty_offset+0x1c, ee);

    write_sector(sbuff,at.empty_sector);
    pthread_mutex_unlock(&dir_lock);

    pthread_mutex_unlock(&big_lock);
    return 0;
}

static int cfs011_rename(const char *from, const char *to)
{
    T_RESOLV at,at2;
    unsigned char sbuff[512];
    int i;
//    fprintf(stderr,"%s %s\n",from,to);

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(from, &at2, RESOLV_DIR)) {
        if (resolv_path(from, &at2, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -ENOENT;
        }
    }
    if (at2.attr & S_IFDIR) {
        if (!resolv_path(to, &at, RESOLV_DIR)) {
            pthread_mutex_unlock(&dir_lock);
            return -EEXIST;
        }
    } else {
        if (!resolv_path(to, &at, RESOLV_FILE)) {
            pthread_mutex_unlock(&dir_lock);
            return -EEXIST;
        }
    }
    if (at.partition!=at2.partition) {
        pthread_mutex_unlock(&dir_lock);
        return -EXDEV;
    }
    if (at.this_dir!=at2.this_dir) {
        pthread_mutex_unlock(&dir_lock);
        return -EXDEV;
    }

    for (i=0;i<16;i++) {
        if (at.name[i]=='*' || at.name[i]=='?' || at.name[i]==':' || at.name[i]=='=' || at.name[i]==',') {
            pthread_mutex_unlock(&dir_lock);
            return -EINVAL;
        }
        if (!at.name[i]) break;
    }
    for (i=0;i<3;i++) {
        if (at.type[i]=='*' || at.type[i]=='?' || at.type[i]==':' || at.type[i]=='=' || at.type[i]==',' || at.type[i]=='<' || at.type[i]=='>' || at.type[i]==' ') {
            pthread_mutex_unlock(&dir_lock);
            return -EINVAL;
        }
        if (!at.type[i]) break;
    }
    if (!strcmp(at.type,"DEL") || !strcmp(at.type,"REL") ||
        !strcmp(at.type,"DIR") || !strcmp(at.type,"LNK") ||
        (at.type[0] && at.type[0]!='P' && at.type[0]!='S' && at.type[0]!='U' && !at.type[1])) {
        pthread_mutex_unlock(&dir_lock);
        return -EINVAL;
    }
    if (!strcmp(at.type,"P")) strcpy(at.type,"PRG"); else
        if (!strcmp(at.type,"S")) strcpy(at.type,"SEQ"); else
            if (!strcmp(at.type,"U")) strcpy(at.type,"USR");

    read_sector(sbuff,at2.dir_sector);
    at.empty_sector=at2.dir_sector;
    at.empty_offset=at2.file_offset;

    memcpy(sbuff+at.empty_offset,&at.name,16);
    if ((sbuff[at.empty_offset+0x18] & 0x07)==1)
        memcpy(sbuff+at.empty_offset+0x19,&at.type,3);

    write_sector(sbuff,at.empty_sector);
    pthread_mutex_unlock(&dir_lock);

    return 0;
}

static int cfs011_mkdir(const char *path, mode_t mode)
{
    unsigned char next_sector[4];
    offset_t empty;
    T_RESOLV at;
    unsigned char sbuff[512];
    int i,j;
    time_t ee;

    pthread_mutex_lock(&big_lock);

#ifdef DEBUG_CFS
    fprintf(stderr,"mkdir %s\n",path);
#endif

    pthread_mutex_lock(&dir_lock);
    if (!resolv_path(path, &at, RESOLV_DIR)) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -EEXIST;
    }

    if ((at.this_dir_attr & S_IWUSR)==0) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -EACCES;
    }

    for (i=0;i<16;i++) {
        if (at.name[i]=='*' || at.name[i]=='?' || at.name[i]==':' || at.name[i]=='=' || at.name[i]==',') {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -EINVAL;
        }
        if (!at.name[i]) break;
    }

    if (at.empty_sector==0) {
        int i;
        empty=find_free_sector(at.partition, at.this_dir, 0);
        if (!empty) {
            pthread_mutex_unlock(&dir_lock);
            pthread_mutex_unlock(&big_lock);
            return -ENOSPC;
        }
        memset(sbuff,0,512);
        write_sector(sbuff,empty);
        at.empty_sector=empty;
        at.empty_offset=0;

        read_sector(sbuff,at.end_of_this_dir);
        offset2sector(next_sector, empty);
        for (i=0;i<512;i+=32) {
            sbuff[i+0x14]=(sbuff[i+0x14] & 0xcf) | ((next_sector[3-(i >> 7)] >> 2) & 0x30);
            next_sector[3-(i >> 7)]<<=2;
        }
        write_sector(sbuff,at.end_of_this_dir);
    }

    empty=find_free_sector(at.partition, at.empty_sector, 0);
    if (!empty) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOSPC;
    }

    for (j=i=0;i<16;i++) {
        if (at.name[j]) sbuff[i]=at.name[j++];
        else sbuff[i]=0x20;
    }
    offset2sector(sbuff+0x10,empty);
    offset2sector(sbuff+0x14,at.this_dir);
    sbuff[0x18]=0x43 | ((mode & 0700) >> 3);
    memcpy(sbuff+0x19,"DIR",3);
    memset(sbuff+0x1c,0,4);
    memset(sbuff+0x20,0,480);

    write_sector(sbuff,empty);

    read_sector(sbuff,at.empty_sector);

    memcpy(sbuff+at.empty_offset,&at.name,16);
    memset(sbuff+at.empty_offset+0x10,0,4);
    offset2sector_insert(sbuff+at.empty_offset+0x14, empty);
    sbuff[at.empty_offset+0x18]=0xc3 | ((mode & 0700) >> 3);
    memcpy(sbuff+at.empty_offset+0x19,"DIR",3);

    ee=time(NULL);
    fill_in_time(sbuff+at.empty_offset+0x1c, ee);

    write_sector(sbuff,at.empty_sector);
    pthread_mutex_unlock(&dir_lock);

    pthread_mutex_unlock(&big_lock);
    return 0;
}

void recurs_truncate(int partition, offset_t itt, unsigned char *bam, offset_t *bam_offset, int level, unsigned int pos, unsigned int size, int kill) {
    unsigned char sbuff[512], sbuff2[512], cbuff[512];
    int i,j;
    int mod;
    unsigned int pos2;
    unsigned char next_sector[4];

    if (level>8) return;

    read_sector(sbuff, itt);

    if (kill) free_this_sector2(partition, itt, bam, bam_offset);
        
    mod=0;
    if (!kill) {
        memcpy(sbuff2, sbuff, 512);
        for (i=0;i<512;i+=4) {
            if (pos+i*(512/4)>=size) {
                sbuff2[i]&=0xb0;
                sbuff2[i+1]=sbuff2[i+2]=sbuff2[i+3]=0;
                mod=1;
            }
            if ((size & 511) && (pos+i*(512/4))==(size & 0xfffffe00)) {
                if (!is_null_sector(sbuff+i)) {
                    offset_t itt;
                    itt=sector2offset(sbuff+i);
                    read_sector(cbuff, itt);
                    memset(cbuff+(size & 511), 0, 512-(size & 511));
                    write_sector(cbuff, itt);
                }
            }
        }
        for (i=0;i<512;i+=64) {
            pos2=(pos << 3)+0x10000+(i & 64)*(0x40000/64)+(i & 128)*(0x20000/128)+(i & 256)*(0x10000/256);
            if (pos2>=size) {
                for (j=0;j<16;j++) sbuff2[i+j*4]&=0xcf;
                mod=1;
            }
        }
    }
    if (mod) write_sector(sbuff2, itt);

    for (i=0;i<512;i+=64) {
        for (j=0;j<16;j++) {
            next_sector[3-(j >> 2)]=(next_sector[3-(j >> 2)] << 2) | ((sbuff[i+j*4] & 0x30) >> 4);
            if (pos+i*(512/4)+j*512>=size) {
                if (!is_null_sector(sbuff+i+j*4)) {
                    free_this_sector2(partition, sector2offset(sbuff+i+j*4), bam, bam_offset);
                }
            }
        }
        pos2=(pos << 3)+0x10000+(i & 64)*(0x40000/64)+(i & 128)*(0x20000/128)+(i & 256)*(0x10000/256);
        if (!is_null_sector(next_sector)) recurs_truncate(partition, sector2offset(next_sector), bam, bam_offset, level+1, pos2, size, kill || (pos2>=size));
    }
}

static int cfs011_truncate(const char *path, off_t size)
{
    T_RESOLV at;
    unsigned char sbuff[512],bam[512];
    offset_t bam_offset;
    unsigned int size2;

    pthread_mutex_lock(&big_lock);

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_FILE)) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }
    if (at.root==0) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }

    read_sector(sbuff,at.dir_sector);

    size2=sbuff[at.file_offset+0x10];
    size2+=sbuff[at.file_offset+0x11] << 8;
    size2+=sbuff[at.file_offset+0x12] << 16;
    if ((sbuff[at.file_offset+0x18] & 0x87)!=0x82)
        size2+=sbuff[at.file_offset+0x13] << 24;

    switch (sbuff[at.file_offset+0x18] & 0x87) {
    case 0x82:
        if (size>0xffffff) size=0xffffff;
    case 0x81:
        if (size<size2) {
            bam_offset=0;
            pthread_mutex_lock(&bam_lock);
            recurs_truncate(at.partition, at.root, bam, &bam_offset, 0, 0, size, 0);

            if (bam_offset) write_sector(bam, bam_offset);
            pthread_mutex_unlock(&bam_lock);
        }
        break;
    case 0x84:
        if (size>0x200) size=0x200;
        break;
    }

    sbuff[at.file_offset+0x10]=size;
    sbuff[at.file_offset+0x11]=size >> 8;
    sbuff[at.file_offset+0x12]=size >> 16;
    if ((sbuff[at.file_offset+0x18] & 0x87)!=0x82)
        sbuff[at.file_offset+0x13]=size >> 24;

    write_sector(sbuff,at.dir_sector);
    pthread_mutex_unlock(&dir_lock);

    pthread_mutex_unlock(&big_lock);
    return 0;
}

void recurs_unlink(int partition, offset_t itt, unsigned char *bam, offset_t *bam_offset, int level) {
    unsigned char sbuff[512];
    int i,j;
    unsigned char next_sector[4];

    if (level>8) return;

    read_sector(sbuff, itt);

    free_this_sector2(partition, itt, bam, bam_offset);

    for (i=0;i<512;i+=64) {
        for (j=0;j<16;j++) {
            next_sector[3-(j >> 2)]=(next_sector[3-(j >> 2)] << 2) | ((sbuff[i+j*4] & 0x30) >> 4);
            if (!is_null_sector(sbuff+i+j*4))
                free_this_sector2(partition, sector2offset(sbuff+i+j*4), bam, bam_offset);
        }
        if (!is_null_sector(next_sector)) recurs_unlink(partition, sector2offset(next_sector), bam, bam_offset, level+1);
    }
}

static int cfs011_unlink(const char *path)
{
    T_RESOLV at;
    unsigned char sbuff[512],bam[512];
    int type;
    offset_t bam_offset;

    pthread_mutex_lock(&big_lock);

#ifdef DEBUG_CFS
    fprintf(stderr,"unlink %s\n",path);
#endif

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_FILE)) {
        pthread_mutex_unlock(&dir_lock);
	pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }
    if (at.root==0) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }

    read_sector(sbuff,at.dir_sector);

    type=sbuff[at.file_offset+0x18] & 0x87;
    sbuff[at.file_offset+0x18]&=0x78;

    write_sector(sbuff,at.dir_sector);
    pthread_mutex_unlock(&dir_lock);

    switch (type) {
    case 0x80:break;
    case 0x81:
    case 0x82:
        bam_offset=0;
        pthread_mutex_lock(&bam_lock);
        recurs_unlink(at.partition, at.root, bam, &bam_offset, 0);
        if (bam_offset) write_sector(bam, bam_offset);
        pthread_mutex_unlock(&bam_lock);
        break;
    case 0x84:free_this_sector(at.partition, at.root);break;
    }

    pthread_mutex_unlock(&big_lock);
    return 0;
}

static int cfs011_rmdir(const char *path)
{
    offset_t itt2;
    T_RESOLV at;
    unsigned char sbuff[512];
    unsigned char next_sector[4];
    int i;

    pthread_mutex_lock(&big_lock);

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_DIR)) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }
    if (at.root==0) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }
    if (at.root==partitions[at.partition].deldir) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }

    itt2=at.root;
    for (;;) {
        read_sector(sbuff,itt2);
        for (i=0;i<512;i+=32) {
            next_sector[3-(i >> 7)]=(next_sector[3-(i >> 7)] << 2) | ((sbuff[i+0x14] >> 4) & 3);
            if ((sbuff[i+0x18] & 0x80)!=0x00 && (sbuff[i+0x18] & 0x87)!=0x03) {
                pthread_mutex_unlock(&big_lock);
                return -ENOTEMPTY;
            }
        }
        if (is_null_sector(next_sector)) break;
        itt2=sector2offset(next_sector);
    }

    read_sector(sbuff,at.dir_sector);

    sbuff[at.file_offset+0x18]&=0x78;

    write_sector(sbuff,at.dir_sector);
    pthread_mutex_unlock(&dir_lock);

    itt2=at.root;
    for (;;) {
        read_sector(sbuff, itt2);
        for (i=0;i<512;i+=32) {
            next_sector[3-(i >> 7)]=(next_sector[3-(i >> 7)] << 2) | ((sbuff[i+0x14] >> 4) & 3);
        }
        free_this_sector(at.partition, itt2);
        if (is_null_sector(next_sector)) break;
        itt2=sector2offset(next_sector);
    }

    pthread_mutex_unlock(&big_lock);
    return 0;
}

static int cfs011_statfs(const char *path, struct statvfs *stbuf)
{
    int i,j;
    offset_t itt;
    unsigned char sbuff[512];
    (void) path;

    pthread_mutex_lock(&bam_lock);
    if (!fs_free_updated) {
        for (i=0;i<16;i++) if (partitions[i].valid) {
            fs_size+=partitions[i].end-partitions[i].start;

            itt=partitions[i].bam;
            do {
                read_sector(sbuff, itt);
                for (j=2;j<4096;j++) if (sbuff[j >> 3] & (128 >> (j & 7))) fs_free++;
                itt+=4096;
                fs_size-=2;
            } while (itt < partitions[i].end);
        }

        fs_free_updated=1;
    }
    stbuf->f_bfree=fs_free;
    stbuf->f_bavail=fs_free;
    pthread_mutex_unlock(&bam_lock);
    //stbuf->f_type=0x64;
    stbuf->f_bsize=512;
    stbuf->f_blocks=fs_size;
    stbuf->f_files=0;
    stbuf->f_ffree=0;
    stbuf->f_namemax=20;
    return 0;
}

static int cfs011_fsync(const char *path, int isdatasync,
                     struct fuse_file_info *fi)
{
    (void) path;
    (void) isdatasync;
    (void) fi;
    return 0;
}

static int cfs011_readlink(const char *path, char *buf, size_t size)
{
    T_RESOLV at;
    unsigned char sbuff[512];

    pthread_mutex_lock(&big_lock);

    pthread_mutex_lock(&dir_lock);
    if (resolv_path(path, &at, RESOLV_FILE)) {
        pthread_mutex_unlock(&dir_lock);
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }
    pthread_mutex_unlock(&dir_lock);
    if (at.root==0) {
        pthread_mutex_unlock(&big_lock);
        return -ENOENT;
    }

    read_sector(sbuff,at.root);
    to_ascii((char *)sbuff, buf);
    pthread_mutex_unlock(&big_lock);
    return 0;
}

static int cfs011_release(const char *path, struct fuse_file_info *fi)
{
    (void) path;

    free((T_FILE *) (uintptr_t) fi->fh);
    return 0;
}

static void *cfs011_init(struct fuse_conn_info *conn) {
    int i;
    unsigned char sbuff[512];
    time_t ee;
    char c64name[17];

    ee=time(NULL);
    localtime_r(&ee, &tm_default);

#ifdef DEBUG_CFS
    fprintf(stderr,"init\n");
#endif

    read_sector(sbuff, 0);
    geometry_head=(sbuff[0x04] & 0x4f)+1;
    geometry_sector=sbuff[0x07];
    read_sector(sbuff, sector2offset(sbuff+0x18));

    for (i=0;i<16;i++) {
        partitions[i].valid=(sbuff[i*32+0x10] & 0x80) && (sbuff[i*32+0x14] & 0xB0)==0x10;
        partitions[i].rootdir=sector2offset(sbuff+i*32+0x1c);
        partitions[i].start=sector2offset(sbuff+i*32+0x10);
        partitions[i].end=sector2offset(sbuff+i*32+0x14);
        partitions[i].deldir=sector2offset(sbuff+i*32+0x18);
        partitions[i].bam=(sbuff[i*32+0x1c] >> 7)+partitions[i].start;
        memcpy(c64name, sbuff+i*32, 16);
        c64name[16]=0;
        to_ascii(c64name, partitions[i].name);
    }
    return NULL;
}

static void cfs011_destroy(void *userdata) {
    if (f1>=0) close(f1);
}

static struct fuse_operations cfs011_oper = {
    .getattr	= cfs011_getattr,
    .readlink	= cfs011_readlink,
    .readdir	= cfs011_readdir,
    .mknod	= cfs011_mknod,
    .mkdir	= cfs011_mkdir,
    .symlink	= cfs011_symlink,
    .unlink	= cfs011_unlink,
    .rmdir	= cfs011_rmdir,
    .rename	= cfs011_rename,
    .chmod	= cfs011_chmod,
    .chown	= cfs011_chown,
    .truncate	= cfs011_truncate,
    .utime	= cfs011_utime,
    .open	= cfs011_open,
    .read	= cfs011_read,
    .write	= cfs011_write,
    .statfs	= cfs011_statfs,
    .release	= cfs011_release,
    .fsync	= cfs011_fsync,
    .init       = cfs011_init,
    .destroy    = cfs011_destroy,
};

int main(int argc, char *argv[])
{
    printf("FUSECFS011 v1.5 (C) 2005-2010 Kajtar Zsolt (Soci/Singular)\n");
    if (argc<2) {
        printf("Usage: %s <device> <mount_point> [<FUSE library options>]\nUnmount: fusermount -u <mount_point>\n", argv[0]);
	return 1;
    }

    f1=open(argv[1], O_RDWR | O_LARGEFILE);
    if (f1<0) {
        printf("Cannot open image file \"%s\"\n", argv[1]);
	return 1;
    }
    argv[1]=argv[0];

    return fuse_main(argc-1, argv+1, &cfs011_oper, NULL);
}
