Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include <u.h>
#include <libc.h>
#include <String.h>
#include "ftpfs.h"

enum
{
        Chunk=          1024,           /* chunk size for buffered data */
        Nfile=          128,            /* maximum number of cached files */
};

/* a file (with cached data) */
struct File
{
        char    *mem;           /* part of file cached in memory */
        ulong   len;            /* length of cached data */
        long    off;            /* current offset into tpath */
        short   fd;             /* fd to cache file */
        char    inuse;
        char    dirty;
        ulong   atime;          /* time of last access */
        Node    *node;
        char    *template;
};

static File     files[Nfile];
static ulong    now;
static int      ntmp;

/*
 *  lookup a file, create one if not found.  if there are no
 *  free files, free the last oldest clean one.
 */
static File*
fileget(Node *node)
{
        File *fp;
        File *oldest;

        fp = node->fp;
        if(fp)
                return fp;

        oldest = 0;
        for(fp = files; fp < &files[Nfile]; fp++){
                if(fp->inuse == 0)
                        break;
                if(fp->dirty == 0 && (oldest == 0 || oldest->atime > fp->atime))
                        oldest = fp;
        }

        if(fp == &files[Nfile]){
                uncache(oldest->node);
                fp = oldest;
        }
        node->fp = fp;
        fp->node = node;
        fp->atime = now++;
        fp->inuse = 1;
        fp->fd = -1;
        if(fp->mem){
                free(fp->mem);
                fp->mem = nil;
        }
        return fp;
}

/*
 *  free a cached file
 */
void
filefree(Node *node)
{
        File *fp;

        fp = node->fp;
        if(fp == 0)
                return;

        if(fp->fd > 0){
                ntmp--;
                close(fp->fd);
                remove(fp->template);
                free(fp->template);
                fp->template = 0;
        }
        fp->fd = -1;
        if(fp->mem){
                free(fp->mem);
                fp->mem = nil;
        }
        fp->len = 0;
        fp->inuse = 0;
        fp->dirty = 0;

        node->fp = 0;
}

/*
 *  satisfy read first from in memory chunk and then from temporary
 *  file.  It's up to the caller to make sure that the file is valid.
 */
int
fileread(Node *node, char *a, long off, int n)
{
        int sofar;
        int i;
        File *fp;

        fp = node->fp;
        if(fp == 0)
                fatal("fileread");

        if(off + n > fp->len)
                n = fp->len - off;

        for(sofar = 0; sofar < n; sofar += i, off += i, a += i){
                if(off >= fp->len)
                        return sofar;
                if(off < Chunk){
                        i = n;
                        if(off + i > Chunk)
                                i = Chunk - off;
                        memmove(a, fp->mem + off, i);
                        continue;
                }
                if(fp->off != off)
                        if(seek(fp->fd, off, 0) < 0){
                                fp->off = -1;
                                return -1;
                        }
                i = read(fp->fd, a, n-sofar);
                if(i < 0){
                        fp->off = -1;
                        return -1;
                }
                if(i == 0)
                        break;
                fp->off = off + i;
        }
        return sofar;
}

void
uncachedir(Node *parent, Node *child)
{
        Node *sp;

        if(parent == 0 || parent == child)
                return;
        for(sp = parent->children; sp; sp = sp->sibs)
                if(sp->opens == 0)
                if(sp != child)
                if(sp->fp != nil)
                if(sp->fp->dirty == 0)
                if(sp->fp->fd >= 0){
                        filefree(sp);
                        UNCACHED(sp);
                }
}

static int
createtmp(File *fp)
{
        char template[32];

        strcpy(template, "/tmp/ftpXXXXXXXXXXX");
        mktemp(template);
        if(strcmp(template, "/") == 0){
                fprint(2, "ftpfs can't create tmp file %s: %r\n", template);
                return -1;
        }
        if(ntmp >= 16)
                uncachedir(fp->node->parent, fp->node);

        fp->fd = create(template, ORDWR|ORCLOSE, 0600);
        fp->template = strdup(template);
        fp->off = 0;
        ntmp++;
        return fp->fd;
}

/*
 *  write cached data (first Chunk bytes stay in memory)
 */
int
filewrite(Node *node, char *a, long off, int n)
{
        int i, sofar;
        File *fp;

        fp = fileget(node);

        if(fp->mem == nil){
                fp->mem = malloc(Chunk);
                if(fp->mem == nil)
                        return seterr("out of memory");
        }

        for(sofar = 0; sofar < n; sofar += i, off += i, a += i){
                if(off < Chunk){
                        i = n;
                        if(off + i > Chunk)
                                i = Chunk - off;
                        memmove(fp->mem + off, a, i);
                        continue;
                }
                if(fp->fd < 0)
                        if(createtmp(fp) < 0)
                                return seterr("can't create temp file");
                if(fp->off != off)
                        if(seek(fp->fd, off, 0) < 0){
                                fp->off = -1;
                                return seterr("can't seek temp file");
                        }
                i = write(fp->fd, a, n-sofar);
                if(i <= 0){
                        fp->off = -1;
                        return seterr("can't write temp file");
                }
                fp->off = off + i;
        }

        if(off > fp->len)
                fp->len = off;
        if(off > node->d->length)
                node->d->length = off;
        return sofar;
}

/*
 *  mark a file as dirty
 */
void
filedirty(Node *node)
{
        File *fp;

        fp = fileget(node);
        fp->dirty = 1;
}

/*
 *  mark a file as clean
 */
void
fileclean(Node *node)
{
        if(node->fp)
                node->fp->dirty = 0;
}

int
fileisdirty(Node *node)
{
        return node->fp && node->fp->dirty;
}