 /*
  *  proc.c
  *
  *  Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke
  *  Copyright (C) 1997 by Volker Lendecke
  *
  *  Please add a note about your changes to davfs in the ChangeLog file.
  */

 #include <linux/types.h>
 #include <linux/errno.h>
 #include <linux/slab.h>
 #include <linux/fs.h>
 #include <linux/file.h>
 #include <linux/stat.h>
 #include <linux/fcntl.h>
 #include <linux/dcache.h>
 #include <linux/dirent.h>
 #include <linux/ncp_no.h>
 #include <linux/smp_lock.h>

 #include <asm/string.h>

 #include "davfs.h"
 #include "dav_debug.h"

 /* Features. Undefine if they cause problems, this should perhaps be a
    config option. */
 #define davFS_POSIX_UNLINK 1

 /* Allow dav_retry to be interrupted. Not sure of the benefit ... */
 /* #define dav_RETRY_INTR */

 #define dav_DIRINFO_SIZE 43
 #define dav_STATUS_SIZE  21


 static inline void
 dav_lock_server(struct dav_sb_info *server)
 {
     down(&(server->sem));
 }

 static inline void
 dav_unlock_server(struct dav_sb_info *server)
 {
     up(&(server->sem));
 }


 /*
  * Returns the maximum read or write size for the "payload". Making all of the
  * packet fit within the negotiated max_xmit size.
  *
  * N.B. Since this value is usually computed before locking the server,
  * the server's packet size must never be decreased!
  */
 static inline int
 dav_get_xmitsize(struct dav_sb_info *server, int overhead)
 {
	 TRACE();
	 return 4096;
 }

 /*
  * Calculate the maximum read size
  */
 int
 dav_get_rsize(struct dav_sb_info *server)
 {
     int overhead = 0;
     int size = dav_get_xmitsize(server, overhead);

     TRACE();

     return size;
 }

/*
 * Calculate the maximum write size
 */
int
dav_get_wsize(struct dav_sb_info *server)
{
    int overhead = 0;
    int size = dav_get_xmitsize(server, overhead);
    
    TRACE();
    
    return size;
}

static int
dav_errno(int ret_code) 
{
    int result;
    
    //TRACE();
    
    switch(ret_code) {
    case HTTP_OK:
     case HTTP_CREATED:
     case HTTP_ACCEPTED:
     case HTTP_NO_CONTENT:
     case HTTP_MULTI_STATUS:
	 result = 0;
	 break;

     case HTTP_BAD_REQUEST:
	 result = -EREMOTEIO;
	 break;

     case HTTP_NON_AUTHORITATIVE:
     case HTTP_UNAUTHORIZED:
     case HTTP_FORBIDDEN:
     case HTTP_METHOD_NOT_ALLOWED:
     case HTTP_NOT_ACCEPTABLE:	
	 result = -EACCES;
	 break;

     case HTTP_NOT_FOUND:
	 result =  -ENOENT;
	 break;

     case HTTP_INTERNAL_SERVER_ERROR:
	 result =  -EFAULT;
	 break;

     default :
	 result = -EIO;
     }

     DEBUG1("errno : %d\n", result);
     return result;
 }


/* reverse a string inline. This is used by the dircache walking routines */
static int reverse_string(char *buf, int len)
{
    char c;
    char *end = buf+len-1;
    
    while(buf < end) {
	c = *buf;
	*(buf++) = *end;
	*(end--) = c;
    }
    return len;
}


/*
** atoi
*/
int dav_atoi(char *digit) {
    int ret=0;
    int i = (strlen(digit)-1);
    double weight=1;
    
    for(;i;i--)
        weight*=10;
    
    for(;*digit;digit++, weight/=10) {
        if(!weight)
            weight = 1;
	
        if(*digit-'0'>=0 && *digit-'0' <=9)
            ret+=weight*(*digit-'0');
    }
    
    return ret;
}


/*
** clear buff 
*/
void
dav_clear_sbuf(struct dav_sb_info *server) {
    int ret;
    TRACE();
    
    while((ret= _recvfrom(server->sock, 
			  server->buf, DAV_MAXPATHLEN, MSG_DONTWAIT))>0)
	;
}


static inline int
dav_is_open(struct inode *i)
{
    struct dav_i_info *dii = DAV_INOP(i);
    TRACE();
    DEBUG1("IS OPEN : %d\n", dii->open);

    return dii->open;
}

/*
 * general request 
 * server->req : request body, should fill out
 * server->length : need to read or something 
 * return error number, 0 on sucess 
 */
int dav_request(struct dav_sb_info *server, int count, const char *data) {
    int result = -EIO;
    char *ret_code, *ret_length;
    
    /* clear socket like flush */
    dav_clear_sbuf(server);

    DEBUG1("req: %s\n", server->req);
    
    /* 
    ** send request 
    ** request should be filled before calling this function
    */
    if(dav_sendline_raw(server->sock, server->req)<0)
	goto out;

    /* send data if have*/
    if(count)
	result=dav_send_raw(server->sock, (unsigned char *)data, count);
    
    /* read 20bytes head from socket */
    if(dav_readline_raw(server, server->buf, DAV_HEAD_SIZE)<0) 
	goto out;
    
    /* parse head data */
    ret_code = strtok(server->buf, " \t\n\r");
    ret_length = strtok(NULL, " \t\n\r");
    
    if(!ret_code || !ret_length)
	goto out;

    server->length = dav_atoi(ret_length);
    result =  dav_errno(dav_atoi(ret_code));
    
 out:
    return result;
}

/*
 * smb_build_path: build the path to entry and name storing it in buf.
 * The path returned will have the trailing '\0'.
 */
static int dav_build_path(struct dav_sb_info *server, char * buf,
			   struct dentry * entry, char *base_path)
{
    char *path = buf;
    int ret = strlen(base_path);
    
    TRACE();
    
    /* memset */
    memset(buf, 0x00, DAV_MAXPATHLEN);

    if (!entry) 
	return 0;
    
    /*
      * Build the path string walking the tree backward from end to ROOT
      * and store it in reversed order [see reverse_string()]
      */
#define ALPHA_COM_LEN 50
    for (;!IS_ROOT(entry);entry = entry->d_parent) {
	if (strlen(path) + 
	    entry->d_name.len + ret + ALPHA_COM_LEN > DAV_MAXPATHLEN)
	    return -ENAMETOOLONG;
	
	reverse_string((char*)entry->d_name.name, entry->d_name.len);
	strncpy(path, entry->d_name.name, entry->d_name.len);
	reverse_string((char*)entry->d_name.name, entry->d_name.len);
	
	path+=entry->d_name.len;
	*path++='/';
    }
    
    reverse_string(base_path, ret);
    strncpy(path, base_path, ret);
    reverse_string(base_path, ret);
    path+=ret;
    
    reverse_string(buf, path-buf);
    
    return path-buf;
}

 /*
  * We're called with the server locked, and we leave it that way.
  *
  * get whole file from http and save it.
  * return file id.
  */
int
dav_proc_open(struct dav_sb_info *server, struct dentry *dentry, int wish)
{
     struct inode *ino = dentry->d_inode;
     struct dav_i_info *dii = DAV_INOP(ino);
     int result=-EIO;
     mm_segment_t fs;
     
     TRACE();
     
     fs = get_fs();
     set_fs(get_ds());

     /* make path */
     dav_build_path(server, server->buf, dentry, server->path);

      /* make open request */
     sprintf(server->req, "open\t%s\t%d", server->buf, wish);
     
     result = dav_request(server, 0, NULL);
     if(result<0) 
	 goto out;
     
     dii->fileid = server->length;
     dii->open++;
     DEBUG2("PROC_FILEID = %ld\n", dii->fileid);
     
 out:
     set_fs(fs);
     
     return result;
}

 /*
  *
  */
int
dav_proc_read(struct inode  *inode, off_t offset, int count, char *data)
{
    int result = -EIO;
    struct dav_sb_info *server = dav_server_from_inode(inode);
    struct dav_i_info *dii = DAV_INOP(inode);
    mm_segment_t fs;
    
    TRACE();
    
    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());
    
    sprintf(server->req, "read\t%ld\t%ld:%d", 
	    dii->fileid, offset, count);
    
    result = dav_request(server, 0, NULL);
    if(result<0) 
	goto out;
    
    if(server->length) 
	result=dav_receive_raw(server->sock, data, server->length);
 out:
    set_fs(fs);
    dav_unlock_server(server);
    return result;
}

int
dav_proc_write(struct inode *inode, off_t offset, int count, const char *data)
{
    int result = -EIO;
    struct dav_i_info *dii = DAV_INOP(inode);
    struct dav_sb_info *server = dav_server_from_inode(inode);
    mm_segment_t fs;
    
    TRACE();

    dav_lock_server(server);
    
    fs = get_fs();
    set_fs(get_ds());
        
    /* make write command */
    sprintf(server->req, "write\t%ld\t%ld:%d", 
	    dii->fileid, offset, count);
    
    /* do request */
    result = dav_request(server, count, data);
    
    set_fs(fs);
    dav_unlock_server(server);
    return result;
}

/*
** create file
** we should not use dav_i_info->fileid, here.
** just pass to *fileid
** 
** dav_inisiate will crate dav_i_info with fileid
**
*/
int
dav_proc_create(struct dentry *dentry, __u16 attr, time_t ctime, long *fileid)
{
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();
    
    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());
    
    /* make path */
    dav_build_path(server, server->buf, dentry, server->path);
    
    sprintf(server->req, "create\t%s", server->buf);
    
    result = dav_request(server, 0, NULL);
    if(result<0) 
	goto out;
    
    *fileid = server->length;
    DEBUG2("PROC_FILEID = %ld\n", *fileid);
    
 out:
    set_fs(fs);
    dav_unlock_server(server);

    return result;
}

int
dav_proc_mv(struct dentry *old_dentry, struct dentry *new_dentry)
{
    struct dav_sb_info *server = dav_server_from_dentry(old_dentry);
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();
  
    dav_lock_server(server);
  
    fs = get_fs();
    set_fs(get_ds());
    
    /* make command with old path */
    dav_build_path(server, server->buf, old_dentry, server->path);
    
    sprintf(server->req, "mv\t%s\t", server->buf);
    
    /* meke destination path */
    dav_build_path(server, server->buf, new_dentry, server->path);
    
    strcat(server->req, server->buf);
    
    result = dav_request(server, 0, NULL);

    set_fs(fs);
    dav_unlock_server(server);
    return result;
}

int
dav_proc_mkdir(struct dentry *dentry)
{
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();
   
    dav_lock_server(server);
 
    fs = get_fs();
    set_fs(get_ds());
    
    /* make path */
    result  = dav_build_path(server, server->buf, dentry, server->path);
    
    result = -EIO;
    
    sprintf(server->req, "mkdir\t%s", server->buf);
    
    result = dav_request(server, 0, NULL);

    set_fs(fs);
    dav_unlock_server(server);
    return result;

}

int
dav_proc_rmdir(struct dentry *dentry)
{
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();
    
    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());
    
    dav_build_path(server, server->buf, dentry, server->path);
    
    sprintf(server->req, "rmdir\t%s", server->buf);
        
    result = dav_request(server, 0, NULL);
    
    set_fs(fs);
    dav_unlock_server(server);
    return result;
}


int
dav_proc_unlink(struct dentry *dentry)
{
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();

    dav_lock_server(server);
    
    fs = get_fs();
    set_fs(get_ds());
    
    /* make path */
    dav_build_path(server, server->buf, dentry, server->path);
    sprintf(server->req, "unlink\t%s", server->buf);

    result = dav_request(server, 0, NULL);

    set_fs(fs);
    dav_unlock_server(server);
    return result;
}


int
dav_proc_trunc(struct dav_sb_info *server, long fid, __u32 length)
{
    int result=0;
    mm_segment_t fs;    
    TRACE();

    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());

    sprintf(server->req, "trunc\t%ld\t%d", fid, length);
    result = dav_request(server, 0, NULL);
    
    DEBUG1("TRUNC FID : %ld LENGHT: %d\n", fid, length);
    
    dav_unlock_server(server);
    return result;
}

static void
dav_init_dirent(struct dav_sb_info *server, struct dav_fattr *fattr)
{
	memset(fattr, 0, sizeof(struct dav_fattr));
	TRACE();

	fattr->f_nlink = 1;
	fattr->f_uid = server->uid;
	fattr->f_gid = server->gid;
	fattr->f_blksize = 4096;

        DEBUG1("UID : %d(%d)\n", server->uid, server->gid);
}

static void
dav_finish_dirent(struct dav_sb_info *server, struct dav_fattr *fattr)
{
	TRACE();
	fattr->f_mode = server->file_mode;

	if (fattr->attr & aDIR)
	{
	    fattr->f_mode = server->dir_mode;
	    fattr->f_size = 512;
	} else {
	    /* normal file mode */
	    fattr->f_mode &= ~(S_IXGRP | S_IXOTH);
        }
	
	/* no write permission for user */
	if(fattr->f_gid) {
	    fattr->f_mode &= ~S_IWOTH | S_IXGRP;
	} else {
	    fattr->f_mode &= ~(S_IWGRP | S_IWOTH);
	}

	/* Check the read-only flag */
	if (fattr->attr & aRONLY)
	    fattr->f_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
	
	fattr->f_blocks = 0;
	if ((fattr->f_blksize != 0) && (fattr->f_size != 0)) {
	    fattr->f_blocks =
	      (fattr->f_size - 1) / fattr->f_blksize + 1;
	}
	return;
}

void
dav_init_root_dirent(struct dav_sb_info *server, struct dav_fattr *fattr)
{
	TRACE();
	dav_init_dirent(server, fattr);
	fattr->attr = aDIR;
	fattr->f_ino = 2; /* traditional root inode number */
	fattr->f_mtime = CURRENT_TIME;
	dav_finish_dirent(server, fattr);
}


int
dav_proc_fill_fattr(struct dav_finfo *finfo, struct dav_fattr *fattr) {

    fattr->f_size  = finfo->f_size;    
    fattr->f_atime = finfo->f_atime;
    fattr->f_mtime = finfo->f_mtime;
    fattr->f_ctime = finfo->f_ctime;

    if(finfo->f_isdir) 
	fattr->attr = aDIR;
    return 0;
}


int
dav_proc_fill_finfo( struct dav_fattr *fattr, struct dav_finfo *finfo) {

    finfo->f_size  = fattr->f_size;    
    finfo->f_atime = fattr->f_atime;
    finfo->f_mtime = fattr->f_mtime;
    finfo->f_ctime = fattr->f_ctime;

    if(fattr->attr == aDIR) 
	finfo->f_isdir = 1; 
    return 0;
}


/* findfirst/findnext flags */
#define dav_CLOSE_AFTER_FIRST (1<<0)
#define dav_CLOSE_IF_END (1<<1)
#define dav_REQUIRE_RESUME_KEY (1<<2)
#define dav_CONTINUE_BIT (1<<3)

int
dav_proc_readdir(struct file *filp, void *dirent, filldir_t filldir,
		 struct dav_cache_control *ctl)
{
    struct dentry *dir = filp->f_dentry;
    struct dav_sb_info *server = dav_server_from_dentry(dir);
    struct dav_fattr fattr;
    struct dav_finfo finfo;
    int ff_searchcount = 0;
    int i, result=-EIO;
    mm_segment_t fs;
    static struct qstr star = { "*", 1, 0 };
    
    TRACE();
    
    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());

    /* make ls command */
    dav_build_path(server, server->buf, dir, server->path);
    sprintf(server->req, "ls\t%s", server->buf);
    result = dav_request(server, 0, NULL);
    if(result<0) 
	goto out;

    ff_searchcount = server->length/sizeof(finfo);
    
    /* no result , go home */
    if(!ff_searchcount) 
	goto out;
    
    for(i=0, result=0;i<ff_searchcount;i++) {
	memset(&finfo, 0x00, sizeof(finfo));
	
	/* read finfo */
	if(dav_receive_raw(server->sock, (unsigned char*)&finfo, sizeof(finfo))<0)
	    goto out;
	
	if(!finfo.f_name_len) {
	    continue;
	}

	star.name = finfo.f_name;
	star.len  = finfo.f_name_len;
	
	dav_init_dirent(server, &fattr);
	
	/* fill fattr using finfo */
	dav_proc_fill_fattr(&finfo, &fattr);
	
	dav_finish_dirent(server, &fattr);
	
	if (!dav_fill_cache(filp, dirent, filldir, ctl, 
			    &star, &fattr))
	    result++;
    }
 out:
    dav_unlock_server(server);
    set_fs(fs);
    return result;	    
}

int
dav_proc_getattr(struct dentry *dir, struct dav_fattr *fattr)
{
    struct dav_sb_info *server = dav_server_from_dentry(dir);
    struct dav_finfo finfo;
    mm_segment_t fs;
    int result = -EIO;

    TRACE();

    dav_lock_server(server);
    
    fs = get_fs();
    set_fs(get_ds());

    dav_build_path(server, server->buf, dir, server->path);
    sprintf(server->req, "attr\t%s", server->buf);
    
    result = dav_request(server, 0, NULL);
    if(result<0) 
	goto out;
    
    /* read finfo */
    if(dav_receive_raw(server->sock, (unsigned char*)&finfo, server->length)<0)
	goto out;
    
    /* init */
    dav_init_dirent(server, fattr);
    
    /* fill fattr using finfo */
    dav_proc_fill_fattr(&finfo, fattr);

    /* finish dirent */
    dav_finish_dirent(server, fattr);
    
 out:
    dav_unlock_server(server);
    set_fs(fs);
    return result;
}

/*
**
 */
int
dav_proc_setattr(struct dentry *dir, struct dav_fattr *fattr)
{
    struct dav_sb_info *server = dav_server_from_dentry(dir);
    struct dav_finfo finfo;
    mm_segment_t fs;
    int result = -EIO;
    
    TRACE();

    dav_lock_server(server);

    fs = get_fs();
    set_fs(get_ds());

    /* fill finfo using fattr */
    dav_proc_fill_finfo(fattr, &finfo);
    
    dav_build_path(server, server->buf, dir, server->path);
    sprintf(server->req, "setattr\t%s\t%d", server->buf, sizeof(finfo));
    
    result = dav_request(server, sizeof(finfo), (char*)&finfo);
        
    set_fs(fs);
    dav_unlock_server(server);
    return result;
}

int
dav_proc_disconnect(struct dav_sb_info *server)
{
    mm_segment_t fs;
    int result = -EIO;
    
    TRACE();
    
    dav_lock_server(server);
    
    fs = get_fs();
    set_fs(get_ds());

    sprintf(server->req, "quit");
    result = dav_request(server, 0, NULL);
        
    set_fs(fs);
    dav_unlock_server(server);

    return result;
}


int
dav_close(struct inode *ino)
{
    struct dav_sb_info *server = dav_server_from_inode(ino);
    struct dav_i_info *dii = DAV_INOP(ino);
    int result=0;
    mm_segment_t fs;
    
    TRACE();

    dav_lock_server(server);

    /* not open */
    if (!dav_is_open(ino) || !dii->fileid) 
	goto ret;

    result=-EIO;
 
    fs = get_fs();
    set_fs(get_ds());
    
    sprintf(server->req, "close\t%ld", dii->fileid);
    result = dav_request(server, 0, NULL);
    
    set_fs(fs);
    
    /*  
    dii->fileid = 0;
    */
    dii->open = 0;

ret:
    dav_unlock_server(server);
    
    return result;
}

/*
 * This is used to close a file following a failed instantiate.
 * Since we don't have an inode, we can't use any of the above.
 */
int
dav_close_fileid(struct dentry *dentry, long fileid)
{
    int result= 0;
    struct dav_i_info *dii = DAV_INOP(dentry->d_inode);
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    mm_segment_t fs;
    
    TRACE();

    dav_lock_server(server);

    /* not open */
    if (!dii->open || !fileid) 
	goto ret;

    result=-EIO;
 
    fs = get_fs();
    set_fs(get_ds());
    
    sprintf(server->req, "close\t%ld", fileid);
    result = dav_request(server, 0, NULL);
    
    set_fs(fs);
    
    dii->open = 0;

ret:
    dav_unlock_server(server);
    return result;
}


int
dav_open(struct dentry *dentry, int wish)
{
    struct inode *inode = dentry->d_inode;
    int result;
    TRACE();
    
    result = -ENOENT;
    if (!inode) {   
	printk(KERN_ERR "dav_open: no inode for dentry %s/%s\n",
	       DENTRY_PATH(dentry));
	goto out;
    }
    
    if (!dav_is_open(inode)) {
	struct dav_sb_info *server = DAV_SERVER(inode);
	dav_lock_server(server);
	result = 0;
	if (!dav_is_open(inode))
	    result = dav_proc_open(server, dentry, wish);
	dav_unlock_server(server);
	if (result) {
	    PARANOIA("%s/%s open failed, result=%d\n",
		     DENTRY_PATH(dentry), result);
	    goto out;
	}
	/*
	 * A successful open means the path is still valid ...
	 */
	 dav_renew_times(dentry);
    }
    
    /*
     * Check whether the access is compatible with the desired mode.
     */
    result = 0;
 
 out:
    return result;
}


int
dav_proc_statfs(struct dentry *dentry, struct statfs *buf)
{
    struct dav_sb_info *server = dav_server_from_dentry(dentry);
    struct dav_statfs ds;
    int result=-EIO;
    mm_segment_t fs;
    
    TRACE();

    /* reset a ds struct */
    memset(&ds, 0x00, sizeof(ds));

    dav_lock_server(server);
  
    fs = get_fs();
    set_fs(get_ds());
    
    /* make command with old path */
    dav_build_path(server, server->buf, dentry, server->path);
    
    sprintf(server->req, "statfs\t%s\t", server->buf);
    result = dav_request(server, 0, NULL);

    
    if(result) {
	/* On error , set default value */

	buf->f_blocks = 0;
	buf->f_bfree = 0;
	buf->f_bavail = 0;
    } else {
	if(server->length) 
	    result=dav_receive_raw(server->sock, (void*)&ds, server->length);
	buf->f_blocks = ds.f_blocks;
	buf->f_bavail = buf->f_bfree = ds.f_bfree; 
    }

    buf->f_bsize = 4098;
    buf->f_namelen = 256;
    set_fs(fs);
    dav_unlock_server(server);
    
    /* Always return OK */ 
    return 0;
}


