/*
 *  proc.c
 *
 *  Copyright (C) 1995, 1996 by Paal-Kr. Engstad and Volker Lendecke
 *
 *  28/06/96 - Fixed long file name support (smb_proc_readdir_long) by Yuri Per
 */

#include <linux/config.h>
#include <linux/fs.h>
#include <linux/smbno.h>
#include <linux/smb_fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <asm/segment.h>
#include <asm/string.h>

#define SMB_VWV(packet)  ((packet) + SMB_HEADER_LEN)
#define SMB_CMD(packet)  (BVAL(packet,8))
#define SMB_WCT(packet)  (BVAL(packet, SMB_HEADER_LEN - 1))
#define SMB_BCC(packet)  smb_bcc(packet)
#define SMB_BUF(packet)  ((packet) + SMB_HEADER_LEN + SMB_WCT(packet) * 2 + 2)

#define SMB_DIRINFO_SIZE 43
#define SMB_STATUS_SIZE  21

static int smb_request_ok(struct smb_server *s, int command, int wct, int bcc);

static inline int
min(int a, int b)
{
	return a < b ? a : b;
}

static void
str_upper(char *name)
{
	while (*name)
	{
		if (*name >= 'a' && *name <= 'z')
			*name -= ('a' - 'A');
		name++;
	}
}

static void
str_lower(char *name)
{
	while (*name)
	{
		if (*name >= 'A' && *name <= 'Z')
			*name += ('a' - 'A');
		name++;
	}
}

/*****************************************************************************/
/*                                                                           */
/*  Encoding/Decoding section                                                */
/*                                                                           */
/*****************************************************************************/

static inline byte *
smb_decode_word(byte * p, word * data)
{
	*data = WVAL(p, 0);
	return p + 2;
}

byte *
smb_encode_smb_length(byte * p, dword len)
{
	BSET(p, 0, 0);
	BSET(p, 1, 0);
	BSET(p, 2, (len & 0xFF00) >> 8);
	BSET(p, 3, (len & 0xFF));
	if (len > 0xFFFF)
	{
		BSET(p, 1, 1);
	}
	return p + 4;
}

static byte *
smb_encode_ascii(byte * p, const byte * name, int len)
{
	*p++ = 4;
	strcpy(p, name);
	return p + len + 1;
}

static byte *
smb_encode_this_name(byte * p, const char *name, const int len)
{
	*p++ = '\\';
	strncpy(p, name, len);
	return p + len;
}

/* I put smb_encode_parents into a separate function so that the
   recursion only takes 16 bytes on the stack per path component on a
   386. */

static byte *
smb_encode_parents(byte * p, struct smb_inode_info *ino)
{
	byte *q;

	if (ino->dir == NULL)
	{
		return p;
	}
	q = smb_encode_parents(p, ino->dir);
	if (q - p + 1 + ino->finfo.len > SMB_MAXPATHLEN)
	{
		return p;
	}
	return smb_encode_this_name(q, ino->finfo.name, ino->finfo.len);
}

static byte *
smb_encode_path(struct smb_server *server,
		byte * p, struct smb_inode_info *dir,
		const char *name, const int len)
{
	byte *start = p;
	if (dir != NULL)
	{
		p = smb_encode_parents(p, dir);
	}
	p = smb_encode_this_name(p, name, len);
	*p++ = 0;
	if (server->protocol <= PROTOCOL_COREPLUS)
	{
		str_upper(start);
	}
	return p;
}

static byte *
smb_decode_data(byte * p, byte * data, word * data_len, int fs)
{
	word len;

	if (!(*p == 1 || *p == 5))
	{
		printk("smb_decode_data: Warning! Data block not starting "
		       "with 1 or 5\n");
	}
	len = WVAL(p, 1);
	p += 3;

	if (fs)
		memcpy_tofs(data, p, len);
	else
		memcpy(data, p, len);

	*data_len = len;

	return p + len;
}

static byte *
smb_name_mangle(byte * p, const byte * name)
{
	int len, pad = 0;

	len = strlen(name);

	if (len < 16)
		pad = 16 - len;

	*p++ = 2 * (len + pad);

	while (*name)
	{
		*p++ = (*name >> 4) + 'A';
		*p++ = (*name & 0x0F) + 'A';
		name++;
	}
	while (pad--)
	{
		*p++ = 'C';
		*p++ = 'A';
	}
	*p++ = '\0';

	return p;
}

/* The following are taken directly from msdos-fs */

/* Linear day numbers of the respective 1sts in non-leap years. */

static int day_n[] =
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 0, 0, 0, 0};
		  /* JanFebMarApr May Jun Jul Aug Sep Oct Nov Dec */


extern struct timezone sys_tz;

static int
utc2local(int time)
{
	return time - sys_tz.tz_minuteswest * 60;
}

static int
local2utc(int time)
{
	return time + sys_tz.tz_minuteswest * 60;
}

/* Convert a MS-DOS time/date pair to a UNIX date (seconds since 1 1 70). */

static int
date_dos2unix(unsigned short time, unsigned short date)
{
	int month, year, secs;

	month = ((date >> 5) & 15) - 1;
	year = date >> 9;
	secs = (time & 31) * 2 + 60 * ((time >> 5) & 63) + (time >> 11) * 3600 + 86400 *
	    ((date & 31) - 1 + day_n[month] + (year / 4) + year * 365 - ((year & 3) == 0 &&
						   month < 2 ? 1 : 0) + 3653);
	/* days since 1.1.70 plus 80's leap day */
	return local2utc(secs);
}

/*****************************************************************************/
/*                                                                           */
/*  Support section.                                                         */
/*                                                                           */
/*****************************************************************************/

dword
smb_len(byte * p)
{
	return ((BVAL(p, 1) & 0x1) << 16L) | (BVAL(p, 2) << 8L) | (BVAL(p, 3));
}

static word
smb_bcc(byte * packet)
{
	int pos = SMB_HEADER_LEN + SMB_WCT(packet) * sizeof(word);
	return WVAL(packet, pos);
}

/* smb_valid_packet: We check if packet fulfills the basic
   requirements of a smb packet */

static int
smb_valid_packet(byte * packet)
{
	DDPRINTK("len: %d, wct: %d, bcc: %d\n",
		 smb_len(packet), SMB_WCT(packet), SMB_BCC(packet));
	return (packet[4] == 0xff
		&& packet[5] == 'S'
		&& packet[6] == 'M'
		&& packet[7] == 'B'
		&& (smb_len(packet) + 4 == SMB_HEADER_LEN
		    + SMB_WCT(packet) * 2 + SMB_BCC(packet)));
}

/* smb_verify: We check if we got the answer we expected, and if we
   got enough data. If bcc == -1, we don't care. */

static int
smb_verify(byte * packet, int command, int wct, int bcc)
{
	return (SMB_CMD(packet) == command &&
		SMB_WCT(packet) >= wct &&
		(bcc == -1 || SMB_BCC(packet) >= bcc)) ? 0 : -EIO;
}

static int
smb_errno(int errcls, int error)
{
	if (errcls == ERRDOS)
		switch (error)
		{
		case ERRbadfunc:
			return EINVAL;
		case ERRbadfile:
			return ENOENT;
		case ERRbadpath:
			return ENOENT;
		case ERRnofids:
			return EMFILE;
		case ERRnoaccess:
			return EACCES;
		case ERRbadfid:
			return EBADF;
		case ERRbadmcb:
			return EREMOTEIO;
		case ERRnomem:
			return ENOMEM;
		case ERRbadmem:
			return EFAULT;
		case ERRbadenv:
			return EREMOTEIO;
		case ERRbadformat:
			return EREMOTEIO;
		case ERRbadaccess:
			return EACCES;
		case ERRbaddata:
			return E2BIG;
		case ERRbaddrive:
			return ENXIO;
		case ERRremcd:
			return EREMOTEIO;
		case ERRdiffdevice:
			return EXDEV;
		case ERRnofiles:
			return 0;
		case ERRbadshare:
			return ETXTBSY;
		case ERRlock:
			return EDEADLK;
		case ERRfilexists:
			return EEXIST;
		case 87:
			return 0;	/* Unknown error!! */
			/* This next error seems to occur on an mv when
			 * the destination exists */
		case 183:
			return EEXIST;
		default:
			return EIO;
	} else if (errcls == ERRSRV)
		switch (error)
		{
		case ERRerror:
			return ENFILE;
		case ERRbadpw:
			return EINVAL;
		case ERRbadtype:
			return EIO;
		case ERRaccess:
			return EACCES;
		default:
			return EIO;
	} else if (errcls == ERRHRD)
		switch (error)
		{
		case ERRnowrite:
			return EROFS;
		case ERRbadunit:
			return ENODEV;
		case ERRnotready:
			return EUCLEAN;
		case ERRbadcmd:
			return EIO;
		case ERRdata:
			return EIO;
		case ERRbadreq:
			return ERANGE;
		case ERRbadshare:
			return ETXTBSY;
		case ERRlock:
			return EDEADLK;
		default:
			return EIO;
	} else if (errcls == ERRCMD)
		return EIO;
	return 0;
}

static void
smb_lock_server(struct smb_server *server)
{
	while (server->lock)
		sleep_on(&server->wait);
	server->lock = 1;
}

static void
smb_unlock_server(struct smb_server *server)
{
	if (server->lock != 1)
	{
		printk("smb_unlock_server: was not locked!\n");
	}
	server->lock = 0;
	wake_up(&server->wait);
}

/* smb_request_ok: We expect the server to be locked. Then we do the
   request and check the answer completely. When smb_request_ok
   returns 0, you can be quite sure that everything went well. When
   the answer is <=0, the returned number is a valid unix errno. */

static int
smb_request_ok(struct smb_server *s, int command, int wct, int bcc)
{
	int result = 0;
	s->rcls = 0;
	s->err = 0;

	if (smb_request(s) < 0)
	{
		DPRINTK("smb_request failed\n");
		result = -EIO;
	} else if (smb_valid_packet(s->packet) != 0)
	{
		DPRINTK("not a valid packet!\n");
		result = -EIO;
	} else if (s->rcls != 0)
	{
		result = -smb_errno(s->rcls, s->err);
	} else if (smb_verify(s->packet, command, wct, bcc) != 0)
	{
		DPRINTK("smb_verify failed\n");
		result = -EIO;
	}
	return result;
}

/* smb_retry: This function should be called when smb_request_ok has
   indicated an error. If the error was indicated because the
   connection was killed, we try to reconnect. If smb_retry returns 0,
   the error was indicated for another reason, so a retry would not be
   of any use. */

static int
smb_retry(struct smb_server *server)
{
	if (server->state != CONN_INVALID)
	{
		return 0;
	}
	if (smb_release(server) < 0)
	{
		DPRINTK("smb_retry: smb_release failed\n");
		server->state = CONN_RETRIED;
		return 0;
	}
	if (smb_proc_reconnect(server) < 0)
	{
		DPRINTK("smb_proc_reconnect failed\n");
		server->state = CONN_RETRIED;
		return 0;
	}
	server->state = CONN_VALID;
	return 1;
}

static int
smb_request_ok_unlock(struct smb_server *s, int command, int wct, int bcc)
{
	int result = smb_request_ok(s, command, wct, bcc);

	smb_unlock_server(s);

	return result;
}

/* smb_setup_header: We completely set up the packet. You only have to
   insert the command-specific fields */

__u8 *
smb_setup_header(struct smb_server * server, byte command, word wct, word bcc)
{
	dword xmit_len = SMB_HEADER_LEN + wct * sizeof(word) + bcc + 2;
	byte *p = server->packet;
	byte *buf = server->packet;

	p = smb_encode_smb_length(p, xmit_len - 4);

	BSET(p, 0, 0xff);
	BSET(p, 1, 'S');
	BSET(p, 2, 'M');
	BSET(p, 3, 'B');
	BSET(p, 4, command);

	p += 5;
	memset(p, '\0', 19);
	p += 19;
	p += 8;

	WSET(buf, smb_tid, server->tid);
	WSET(buf, smb_pid, server->pid);
	WSET(buf, smb_uid, server->server_uid);
	WSET(buf, smb_mid, server->mid);

	if (server->protocol > PROTOCOL_CORE)
	{
		BSET(buf, smb_flg, 0x8);
		WSET(buf, smb_flg2, 0x3);
	}
	*p++ = wct;		/* wct */
	p += 2 * wct;
	WSET(p, 0, bcc);
	return p + 2;
}

/* smb_setup_header_exclusive waits on server->lock and locks the
   server, when it's free. You have to unlock it manually when you're
   finished with server->packet! */

static byte *
smb_setup_header_exclusive(struct smb_server *server,
			   byte command, word wct, word bcc)
{
	smb_lock_server(server);
	return smb_setup_header(server, command, wct, bcc);
}

static void
smb_setup_bcc(struct smb_server *server, byte * p)
{
	__u8 *packet = server->packet;
	__u8 *pbcc = packet + SMB_HEADER_LEN + 2 * SMB_WCT(packet);
	__u16 bcc = p - (pbcc + 2);

	WSET(pbcc, 0, bcc);
	smb_encode_smb_length(packet,
			      SMB_HEADER_LEN + 2 * SMB_WCT(packet) - 2 + bcc);
}


/*****************************************************************************/
/*                                                                           */
/*  File operation section.                                                  */
/*                                                                           */
/*****************************************************************************/

int
smb_proc_open(struct smb_server *server,
	      struct smb_inode_info *dir, const char *name, int len,
	      struct smb_dirent *entry)
{
	int error;
	char *p;
	char *buf;
	const word o_attr = aSYSTEM | aHIDDEN | aDIR;

	DPRINTK("smb_proc_open: name=%s\n", name);

	smb_lock_server(server);

	if (entry->opened != 0)
	{
		/* Somebody else opened the file while we slept */
		smb_unlock_server(server);
		return 0;
	}
      retry:
	buf = server->packet;
	p = smb_setup_header(server, SMBopen, 2, 0);
	WSET(buf, smb_vwv0, 0x42);	/* read/write */
	WSET(buf, smb_vwv1, o_attr);
	*p++ = 4;
	p = smb_encode_path(server, p, dir, name, len);
	smb_setup_bcc(server, p);

	if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0)
	{

		if (smb_retry(server))
		{
			goto retry;
		}
		if ((error != -EACCES) && (error != -ETXTBSY)
		    && (error != -EROFS))
		{
			smb_unlock_server(server);
			return error;
		}
		p = smb_setup_header(server, SMBopen, 2, 0);
		WSET(buf, smb_vwv0, 0x40);	/* read only */
		WSET(buf, smb_vwv1, o_attr);
		*p++ = 4;
		p = smb_encode_path(server, p, dir, name, len);
		smb_setup_bcc(server, p);

		if ((error = smb_request_ok(server, SMBopen, 7, 0)) != 0)
		{
			if (smb_retry(server))
			{
				goto retry;
			}
			smb_unlock_server(server);
			return error;
		}
	}
	/* We should now have data in vwv[0..6]. */

	entry->fileid = WVAL(buf, smb_vwv0);
	entry->attr = WVAL(buf, smb_vwv1);
	entry->f_ctime = entry->f_atime =
	    entry->f_mtime = local2utc(DVAL(buf, smb_vwv2));
	entry->f_size = DVAL(buf, smb_vwv4);
	entry->access = WVAL(buf, smb_vwv6);

	entry->opened = 1;
	entry->access &= 3;

	smb_unlock_server(server);

	DPRINTK("smb_proc_open: entry->access = %d\n", entry->access);
	return 0;
}

int
smb_proc_close(struct smb_server *server,
	       __u16 fileid, __u32 mtime)
{
	char *buf;

	smb_setup_header_exclusive(server, SMBclose, 3, 0);
	buf = server->packet;
	WSET(buf, smb_vwv0, fileid);
	DSET(buf, smb_vwv1, utc2local(mtime));

	return smb_request_ok_unlock(server, SMBclose, 0, 0);
}

/* In smb_proc_read and smb_proc_write we do not retry, because the
   file-id would not be valid after a reconnection. */

/* smb_proc_read: fs indicates if it should be copied with
   memcpy_tofs. */

int
smb_proc_read(struct smb_server *server, struct smb_dirent *finfo,
	      off_t offset, long count, char *data, int fs)
{
	word returned_count, data_len;
	char *buf;
	int error;

	smb_setup_header_exclusive(server, SMBread, 5, 0);
	buf = server->packet;

	WSET(buf, smb_vwv0, finfo->fileid);
	WSET(buf, smb_vwv1, count);
	DSET(buf, smb_vwv2, offset);
	WSET(buf, smb_vwv4, 0);

	if ((error = smb_request_ok(server, SMBread, 5, -1)) < 0)
	{
		smb_unlock_server(server);
		return error;
	}
	returned_count = WVAL(buf, smb_vwv0);

	smb_decode_data(SMB_BUF(server->packet), data, &data_len, fs);

	smb_unlock_server(server);

	if (returned_count != data_len)
	{
		printk("smb_proc_read: Warning, returned_count != data_len\n");
		printk("smb_proc_read: ret_c=%d, data_len=%d\n",
		       returned_count, data_len);
	}
	return data_len;
}

int
smb_proc_write(struct smb_server *server, struct smb_dirent *finfo,
	       off_t offset, int count, const char *data)
{
	int res = 0;
	char *buf;
	byte *p;

	p = smb_setup_header_exclusive(server, SMBwrite, 5, count + 3);
	buf = server->packet;
	WSET(buf, smb_vwv0, finfo->fileid);
	WSET(buf, smb_vwv1, count);
	DSET(buf, smb_vwv2, offset);
	WSET(buf, smb_vwv4, 0);

	*p++ = 1;
	WSET(p, 0, count);
	memcpy_fromfs(p + 2, data, count);

	if ((res = smb_request_ok(server, SMBwrite, 1, 0)) >= 0)
	{
		res = WVAL(buf, smb_vwv0);
	}
	smb_unlock_server(server);

	return res;
}

int
smb_proc_create(struct inode *dir, const char *name, int len,
		word attr, time_t ctime)
{
	int error;
	char *p;
	struct smb_server *server = SMB_SERVER(dir);
	char *buf;
	__u16 fileid;

	smb_lock_server(server);
      retry:
	buf = server->packet;
	p = smb_setup_header(server, SMBcreate, 3, 0);
	WSET(buf, smb_vwv0, attr);
	DSET(buf, smb_vwv1, utc2local(ctime));
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(dir), name, len);
	smb_setup_bcc(server, p);

	if ((error = smb_request_ok(server, SMBcreate, 1, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
		smb_unlock_server(server);
		return error;
	}
	fileid = WVAL(buf, smb_vwv0);
	smb_unlock_server(server);

	smb_proc_close(server, fileid, CURRENT_TIME);

	return 0;
}

int
smb_proc_mv(struct inode *odir, const char *oname, const int olen,
	    struct inode *ndir, const char *nname, const int nlen)
{
	char *p;
	struct smb_server *server = SMB_SERVER(odir);
	int result;

	smb_lock_server(server);

      retry:
	p = smb_setup_header(server, SMBmv, 1, 0);
	WSET(server->packet, smb_vwv0, aSYSTEM | aHIDDEN);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(odir), oname, olen);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(ndir), nname, nlen);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBmv, 0, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_mkdir(struct inode *dir, const char *name, const int len)
{
	char *p;
	int result;
	struct smb_server *server = SMB_SERVER(dir);

	smb_lock_server(server);

      retry:
	p = smb_setup_header(server, SMBmkdir, 0, 0);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(dir), name, len);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBmkdir, 0, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_rmdir(struct inode *dir, const char *name, const int len)
{
	char *p;
	int result;
	struct smb_server *server = SMB_SERVER(dir);

	smb_lock_server(server);

      retry:
	p = smb_setup_header(server, SMBrmdir, 0, 0);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(dir), name, len);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBrmdir, 0, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_unlink(struct inode *dir, const char *name, const int len)
{
	char *p;
	struct smb_server *server = SMB_SERVER(dir);
	int result;

	smb_lock_server(server);

      retry:
	p = smb_setup_header(server, SMBunlink, 1, 0);
	WSET(server->packet, smb_vwv0, aSYSTEM | aHIDDEN);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(dir), name, len);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBunlink, 0, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_trunc(struct smb_server *server, word fid, dword length)
{
	char *p;
	char *buf;
	int result;

	smb_lock_server(server);

      retry:
	buf = server->packet;
	p = smb_setup_header(server, SMBwrite, 5, 0);
	WSET(buf, smb_vwv0, fid);
	WSET(buf, smb_vwv1, 0);
	DSET(buf, smb_vwv2, length);
	WSET(buf, smb_vwv4, 0);
	p = smb_encode_ascii(p, "", 0);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBwrite, 1, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

static void
smb_init_dirent(struct smb_server *server, struct smb_dirent *entry)
{
	memset(entry, 0, sizeof(struct smb_dirent));

	entry->f_nlink = 1;
	entry->f_uid = server->m.uid;
	entry->f_gid = server->m.gid;
	entry->f_blksize = 512;
}

static void
smb_finish_dirent(struct smb_server *server, struct smb_dirent *entry)
{
	if ((entry->attr & aDIR) != 0)
	{
		entry->f_mode = server->m.dir_mode;
		entry->f_size = 512;
	} else
	{
		entry->f_mode = server->m.file_mode;
	}

	if (entry->attr & aRONLY)
		entry->f_mode &= ~0222;

	if ((entry->f_blksize != 0) && (entry->f_size != 0))
	{
		entry->f_blocks =
		    (entry->f_size - 1) / entry->f_blksize + 1;
	} else
	{
		entry->f_blocks = 0;
	}
	return;
}

void
smb_init_root_dirent(struct smb_server *server, struct smb_dirent *entry)
{
	smb_init_dirent(server, entry);
	entry->attr = aDIR;
	entry->f_ino = 1;
	smb_finish_dirent(server, entry);
}


static char *
smb_decode_dirent(struct smb_server *server, char *p, struct smb_dirent *entry)
{
	smb_init_dirent(server, entry);

	p += SMB_STATUS_SIZE;	/* reserved (search_status) */
	entry->attr = BVAL(p, 0);
	entry->f_mtime = entry->f_atime = entry->f_ctime =
	    date_dos2unix(WVAL(p, 1), WVAL(p, 3));
	entry->f_size = DVAL(p, 5);
	entry->len = strlen(p + 9);
	if (entry->len > 12)
	{
		entry->len = 12;
	}
	memcpy(entry->name, p + 9, entry->len);
	entry->name[entry->len] = '\0';
	while (entry->len > 2)
	{
		/* Pathworks fills names with spaces */
		entry->len -= 1;
		if (entry->name[entry->len] == ' ')
		{
			entry->name[entry->len] = '\0';
		}
	}
	switch (server->case_handling)
	{
	case CASE_UPPER:
		str_upper(entry->name);
		break;
	case CASE_LOWER:
		str_lower(entry->name);
		break;
	default:
		break;
	}
	DPRINTK("smb_decode_dirent: name = %s\n", entry->name);
	smb_finish_dirent(server, entry);
	return p + 22;
}

/* This routine is used to read in directory entries from the network.
   Note that it is for short directory name seeks, i.e.: protocol <
   PROTOCOL_LANMAN2 */

static int
smb_proc_readdir_short(struct smb_server *server, struct inode *dir, int fpos,
		       int cache_size, struct smb_dirent *entry)
{
	char *p;
	char *buf;
	int error;
	int result;
	int i;
	int first, total_count;
	struct smb_dirent *current_entry;
	word bcc;
	word count;
	char status[SMB_STATUS_SIZE];
	int entries_asked = (server->max_xmit - 100) / SMB_DIRINFO_SIZE;

	DPRINTK("SMB call  readdir %d @ %d\n", cache_size, fpos);

	smb_lock_server(server);

      retry:
	buf = server->packet;
	first = 1;
	total_count = 0;
	current_entry = entry;

	while (1)
	{
		if (first == 1)
		{
			p = smb_setup_header(server, SMBsearch, 2, 0);
			WSET(buf, smb_vwv0, entries_asked);
			WSET(buf, smb_vwv1, aDIR);
			*p++ = 4;
			p = smb_encode_path(server, p, SMB_INOP(dir), "*.*", 3);
			*p++ = 5;
			WSET(p, 0, 0);
			p += 2;
		} else
		{
			p = smb_setup_header(server, SMBsearch, 2, 0);
			WSET(buf, smb_vwv0, entries_asked);
			WSET(buf, smb_vwv1, aDIR);
			p = smb_encode_ascii(p, "", 0);
			*p++ = 5;
			WSET(p, 0, SMB_STATUS_SIZE);
			p += 2;
			memcpy(p, status, SMB_STATUS_SIZE);
			p += SMB_STATUS_SIZE;
		}

		smb_setup_bcc(server, p);

		if ((error = smb_request_ok(server, SMBsearch, 1, -1)) < 0)
		{
			if ((server->rcls == ERRDOS)
			    && (server->err == ERRnofiles))
			{
				result = total_count - fpos;
				goto unlock_return;
			} else
			{
				if (smb_retry(server))
				{
					goto retry;
				}
				result = error;
				goto unlock_return;
			}
		}
		p = SMB_VWV(server->packet);
		p = smb_decode_word(p, &count);
		p = smb_decode_word(p, &bcc);

		first = 0;

		if (count <= 0)
		{
			result = total_count - fpos;
			goto unlock_return;
		}
		if (bcc != count * SMB_DIRINFO_SIZE + 3)
		{
			result = -EIO;
			goto unlock_return;
		}
		p += 3;		/* Skipping VBLOCK header
				   (5, length lo, length hi). */

		/* Read the last entry into the status field. */
		memcpy(status,
		       SMB_BUF(server->packet) + 3 +
		       (count - 1) * SMB_DIRINFO_SIZE,
		       SMB_STATUS_SIZE);

		/* Now we are ready to parse smb directory entries. */

		for (i = 0; i < count; i++)
		{
			if (total_count < fpos)
			{
				p += SMB_DIRINFO_SIZE;
				DDPRINTK("smb_proc_readdir: skipped entry.\n");
				DDPRINTK("                  total_count = %d\n"
					 "                i = %d, fpos = %d\n",
					 total_count, i, fpos);
			} else if (total_count >= fpos + cache_size)
			{
				result = total_count - fpos;
				goto unlock_return;
			} else
			{
				p = smb_decode_dirent(server, p,
						      current_entry);
				current_entry->f_pos = total_count;
				DDPRINTK("smb_proc_readdir: entry->f_pos = "
					 "%lu\n", entry->f_pos);
				current_entry += 1;
			}
			total_count += 1;
		}
	}
      unlock_return:
	smb_unlock_server(server);
	return result;
}

/* interpret a long filename structure - this is mostly guesses at the
   moment.  The length of the structure is returned.  The structure of
   a long filename depends on the info level. 260 is used by NT and 2
   is used by OS/2. */

static char *
smb_decode_long_dirent(struct smb_server *server, char *p,
		       struct smb_dirent *entry, int level)
{
	char *result;

	smb_init_dirent(server, entry);

	switch (level)
	{
		/* We might add more levels later... */
	case 1:
		entry->len = BVAL(p, 26);
		strncpy(entry->name, p + 27, entry->len);
		entry->name[entry->len] = '\0';
		entry->f_size = DVAL(p, 16);
		entry->attr = BVAL(p, 24);

		entry->f_ctime = date_dos2unix(WVAL(p, 6), WVAL(p, 4));
		entry->f_atime = date_dos2unix(WVAL(p, 10), WVAL(p, 8));
		entry->f_mtime = date_dos2unix(WVAL(p, 14), WVAL(p, 12));
		result = p + 28 + BVAL(p, 26);
		break;

	default:
		DPRINTK("Unknown long filename format %d\n", level);
		result = p + WVAL(p, 0);
	}

	switch (server->case_handling)
	{
	case CASE_UPPER:
		str_upper(entry->name);
		break;
	case CASE_LOWER:
		str_lower(entry->name);
		break;
	default:
		break;
	}

	smb_finish_dirent(server, entry);
	return result;
}

int
smb_proc_readdir_long(struct smb_server *server, struct inode *dir, int fpos,
		      int cache_size, struct smb_dirent *cache)
{
	/* NT uses 260, OS/2 uses 2. Both accept 1. */
	const int info_level = 1;
	const int max_matches = 512;

	char *p;
	char *lastname;
	int lastname_len;
	int i;
	int first, entries, entries_seen;

	unsigned char *resp_data = NULL;
	unsigned char *resp_param = NULL;
	int resp_data_len = 0;
	int resp_param_len = 0;

	__u16 command;

	int result;

	int ff_resume_key = 0;
	int ff_searchcount = 0;
	int ff_eos = 0;
	int ff_lastname = 0;
	int ff_dir_handle = 0;
	int loop_count = 0;

	char param[SMB_MAXPATHLEN + 2 + 12];
	int mask_len;
	unsigned char *mask = &(param[12]);

	mask_len = smb_encode_path(server, mask,
				   SMB_INOP(dir), "*", 1) - mask;

	mask[mask_len] = 0;
	mask[mask_len + 1] = 0;

	DPRINTK("smb_readdir_long cache=%d, fpos=%d, mask=%s\n",
		cache_size, fpos, mask);

	smb_lock_server(server);

      retry:

	first = 1;
	entries = 0;
	entries_seen = 2;

	while (ff_eos == 0)
	{
		loop_count += 1;
		if (loop_count > 200)
		{
			printk("smb_proc_readdir_long: "
			       "Looping in FIND_NEXT??\n");
			break;
		}
		if (first != 0)
		{
			command = TRANSACT2_FINDFIRST;
			WSET(param, 0, aSYSTEM | aHIDDEN | aDIR);
			WSET(param, 2, max_matches);	/* max count */
			WSET(param, 4, 8 + 4 + 2);	/* resume required +
							   close on end +
							   continue */
			WSET(param, 6, info_level);
			DSET(param, 8, 0);
		} else
		{
			command = TRANSACT2_FINDNEXT;
			DPRINTK("hand=0x%X resume=%d ff_lastname=%d mask=%s\n",
			     ff_dir_handle, ff_resume_key, ff_lastname, mask);
			WSET(param, 0, ff_dir_handle);
			WSET(param, 2, max_matches);	/* max count */
			WSET(param, 4, info_level);
			DSET(param, 6, ff_resume_key);	/* ff_resume_key */
			WSET(param, 10, 8 + 4 + 2);	/* resume required +
							   close on end +
							   continue */
#ifdef CONFIG_SMB_WIN95
			/* Windows 95 is not able to deliver answers
			   to FIND_NEXT fast enough, so sleep 0.2 seconds */
			current->timeout = jiffies + HZ / 5;
			current->state = TASK_INTERRUPTIBLE;
			schedule();
			current->timeout = 0;
#endif
		}

		result = smb_trans2_request(server, command,
					    0, NULL, 12 + mask_len + 2, param,
					    &resp_data_len, &resp_data,
					    &resp_param_len, &resp_param);

		if (result < 0)
		{
			if (smb_retry(server))
			{
				goto retry;
			}
			DPRINTK("smb_proc_readdir_long: "
				"got error from trans2_request\n");
			break;
		}
		if (server->rcls != 0)
		{
			result = -EIO;
			break;
		}
		/* parse out some important return info */
		if (first != 0)
		{
			ff_dir_handle = WVAL(resp_param, 0);
			ff_searchcount = WVAL(resp_param, 2);
			ff_eos = WVAL(resp_param, 4);
			ff_lastname = WVAL(resp_param, 8);
		} else
		{
			ff_searchcount = WVAL(resp_param, 0);
			ff_eos = WVAL(resp_param, 2);
			ff_lastname = WVAL(resp_param, 6);
		}

		if (ff_searchcount == 0)
		{
			break;
		}
		/* point to the data bytes */
		p = resp_data;

		/* we might need the lastname for continuations */
		lastname = "";
		lastname_len = 0;
		if (ff_lastname > 0)
		{
			switch (info_level)
			{
			case 260:
				lastname = p + ff_lastname;
				lastname_len = resp_data_len - ff_lastname;
				ff_resume_key = 0;
				break;
			case 1:
				lastname = p + ff_lastname + 1;
				lastname_len = BVAL(p, ff_lastname);
				ff_resume_key = 0;
				break;
			}
		}
		lastname_len = min(lastname_len, 256);
		strncpy(mask, lastname, lastname_len);
		mask[lastname_len] = '\0';

		/* Now we are ready to parse smb directory entries. */

		for (i = 0; i < ff_searchcount; i++)
		{
			struct smb_dirent *entry = &(cache[entries]);

			p = smb_decode_long_dirent(server, p,
						   entry, info_level);

			DDPRINTK("smb_readdir_long: got %s\n", entry->name);

			if ((entry->name[0] == '.')
			    && ((entry->name[1] == '\0')
				|| ((entry->name[1] == '.')
				    && (entry->name[2] == '\0'))))
			{
				/* ignore . and .. from the server */
				continue;
			}
			if (entries_seen >= fpos)
			{
				entry->f_pos = entries_seen;
				entries += 1;
			}
			if (entries >= cache_size)
			{
				goto finished;
			}
			entries_seen += 1;
		}

		DPRINTK("received %d entries (eos=%d resume=%d)\n",
			ff_searchcount, ff_eos, ff_resume_key);

		first = 0;
	}

      finished:
	smb_unlock_server(server);
	return entries;
}

int
smb_proc_readdir(struct smb_server *server, struct inode *dir, int fpos,
		 int cache_size, struct smb_dirent *entry)
{
	if (server->protocol >= PROTOCOL_LANMAN2)
		return smb_proc_readdir_long(server, dir, fpos, cache_size,
					     entry);
	else
		return smb_proc_readdir_short(server, dir, fpos, cache_size,
					      entry);
}

static int
smb_proc_getattr_core(struct inode *dir, const char *name, int len,
		      struct smb_dirent *entry)
{
	int result;
	char *p;
	struct smb_server *server = SMB_SERVER(dir);
	char *buf;

	smb_lock_server(server);

	DDPRINTK("smb_proc_getattr: %s\n", name);

      retry:
	buf = server->packet;
	p = smb_setup_header(server, SMBgetatr, 0, 0);
	*p++ = 4;
	p = smb_encode_path(server, p, SMB_INOP(dir), name, len);
	smb_setup_bcc(server, p);

	if ((result = smb_request_ok(server, SMBgetatr, 10, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
		smb_unlock_server(server);
		return result;
	}
	entry->attr = WVAL(buf, smb_vwv0);
	entry->f_ctime = entry->f_atime =
	    entry->f_mtime = local2utc(DVAL(buf, smb_vwv1));

	entry->f_size = DVAL(buf, smb_vwv3);
	smb_unlock_server(server);
	return 0;
}

int
smb_proc_getattr(struct inode *dir, const char *name, int len,
		 struct smb_dirent *entry)
{
	struct smb_server *server = SMB_SERVER(dir);
        int result;

	smb_init_dirent(server, entry);
        result = smb_proc_getattr_core(dir, name, len, entry);
	smb_finish_dirent(server, entry);

	entry->len = len;
	memcpy(entry->name, name, len);
	/* entry->name is null terminated from smb_init_dirent */

	return result;
}

int
smb_proc_setattr(struct smb_server *server,
                 struct inode *i, struct smb_dirent *new_finfo)
{
	char *p;
	char *buf;
	int result;

	smb_lock_server(server);

      retry:
	buf = server->packet;
	p = smb_setup_header(server, SMBsetatr, 8, 0);
	WSET(buf, smb_vwv0, new_finfo->attr);
	DSET(buf, smb_vwv1, 0);
	DSET(buf, smb_vwv3, 0);
	DSET(buf, smb_vwv5, 0);
	WSET(buf, smb_vwv7, 0);
	*p++ = 4;
	p = smb_encode_path(server, p,
			    SMB_INOP(i)->dir, SMB_INOP(i)->finfo.name,
			    SMB_INOP(i)->finfo.len);

	smb_setup_bcc(server, p);
	if ((result = smb_request_ok(server, SMBsetatr, 0, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_dskattr(struct super_block *super, struct smb_dskattr *attr)
{
	int error;
	char *p;
	struct smb_server *server = &(SMB_SBP(super)->s_server);

	smb_lock_server(server);

      retry:
	smb_setup_header(server, SMBdskattr, 0, 0);

	if ((error = smb_request_ok(server, SMBdskattr, 5, 0)) < 0)
	{
		if (smb_retry(server))
		{
			goto retry;
		}
		smb_unlock_server(server);
		return error;
	}
	p = SMB_VWV(server->packet);
	p = smb_decode_word(p, &attr->total);
	p = smb_decode_word(p, &attr->allocblocks);
	p = smb_decode_word(p, &attr->blocksize);
	p = smb_decode_word(p, &attr->free);
	smb_unlock_server(server);
	return 0;
}

/*****************************************************************************/
/*                                                                           */
/*  Mount/umount operations.                                                 */
/*                                                                           */
/*****************************************************************************/

struct smb_prots
{
	enum smb_protocol prot;
	const char *name;
};

/* smb_proc_reconnect: We expect the server to be locked, so that you
   can call the routine from within smb_retry. The socket must be
   created, like after a user-level socket()-call. It may not be
   connected. */

int
smb_proc_reconnect(struct smb_server *server)
{
	struct smb_prots prots[] =
	{
		{PROTOCOL_CORE, "PC NETWORK PROGRAM 1.0"},
		{PROTOCOL_COREPLUS, "MICROSOFT NETWORKS 1.03"},
#ifdef LANMAN1
		{PROTOCOL_LANMAN1, "MICROSOFT NETWORKS 3.0"},
		{PROTOCOL_LANMAN1, "LANMAN1.0"},
#endif
#ifdef LANMAN2
		{PROTOCOL_LANMAN2, "LM1.2X002"},
#endif
#ifdef NT1
		{PROTOCOL_NT1, "NT LM 0.12"},
		{PROTOCOL_NT1, "NT LANMAN 1.0"},
#endif
		{-1, NULL}};
	char dev[] = "A:";
	int i, plength;
	int max_xmit = 1024;	/* Space needed for first request. */
	int given_max_xmit = server->m.max_xmit;
	int result;
	byte *p;

	if ((result = smb_connect(server)) < 0)
	{
		DPRINTK("smb_proc_reconnect: could not smb_connect\n");
		goto fail;
	}
	/* Here we assume that the connection is valid */
	server->state = CONN_VALID;

	if (server->packet != NULL)
	{
		smb_vfree(server->packet);
		server->packet = NULL;
		server->packet_size = 0;
	}
	server->packet = smb_vmalloc(max_xmit);

	if (server->packet == NULL)
	{
		printk("smb_proc_connect: No memory! Bailing out.\n");
		result = -ENOMEM;
		goto fail;
	}
	server->packet_size = server->max_xmit = max_xmit;

	/*
	 * Start with an RFC1002 session request packet.
	 */
	p = server->packet + 4;

	p = smb_name_mangle(p, server->m.server_name);
	p = smb_name_mangle(p, server->m.client_name);

	smb_encode_smb_length(server->packet,
			      (void *) p - (void *) (server->packet));

	server->packet[0] = 0x81;	/* SESSION REQUEST */

	if (smb_catch_keepalive(server) < 0)
	{
		printk("smb_proc_connect: could not catch_keepalives\n");
	}
	if ((result = smb_request(server)) < 0)
	{
		DPRINTK("smb_proc_connect: Failed to send SESSION REQUEST.\n");
		smb_dont_catch_keepalive(server);
		goto fail;
	}
	if (server->packet[0] != 0x82)
	{
		printk("smb_proc_connect: Did not receive positive response "
		       "(err = %x)\n",
		       server->packet[0]);
		smb_dont_catch_keepalive(server);
		result = -EIO;
		goto fail;
	}
	DPRINTK("smb_proc_connect: Passed SESSION REQUEST.\n");

	/* Now we are ready to send a SMB Negotiate Protocol packet. */
	memset(server->packet, 0, SMB_HEADER_LEN);

	plength = 0;
	for (i = 0; prots[i].name != NULL; i++)
	{
		plength += strlen(prots[i].name) + 2;
	}

	smb_setup_header(server, SMBnegprot, 0, plength);

	p = SMB_BUF(server->packet);

	for (i = 0; prots[i].name != NULL; i++)
	{
		*p++ = 2;
		strcpy(p, prots[i].name);
		p += strlen(prots[i].name) + 1;
	}

	if ((result = smb_request_ok(server, SMBnegprot, 1, -1)) < 0)
	{
		DPRINTK("smb_proc_connect: Failure requesting SMBnegprot\n");
		smb_dont_catch_keepalive(server);
		goto fail;
	} else
	{
		DDPRINTK("smb_proc_connect: Request SMBnegprot..");
	}

	DDPRINTK("Verified!\n");

	p = SMB_VWV(server->packet);
	p = smb_decode_word(p, (word *) & i);
	server->protocol = prots[i].prot;

	DPRINTK("smb_proc_connect: Server wants %s protocol.\n",
		prots[i].name);

	if (server->protocol >= PROTOCOL_LANMAN1)
	{

		word passlen = strlen(server->m.password);
		word userlen = strlen(server->m.username);

		DPRINTK("smb_proc_connect: password = %s\n",
			server->m.password);
		DPRINTK("smb_proc_connect: usernam = %s\n",
			server->m.username);
		DPRINTK("smb_proc_connect: blkmode = %d\n",
			WVAL(server->packet, smb_vwv5));

		if (server->protocol >= PROTOCOL_NT1)
		{
			server->max_xmit = DVAL(server->packet, smb_vwv3 + 1);
			server->maxmux = WVAL(server->packet, smb_vwv1 + 1);
			server->maxvcs = WVAL(server->packet, smb_vwv2 + 1);
			server->blkmode = DVAL(server->packet, smb_vwv9 + 1);
			server->sesskey = DVAL(server->packet, smb_vwv7 + 1);
		} else
		{
			server->max_xmit = WVAL(server->packet, smb_vwv2);
			server->maxmux = WVAL(server->packet, smb_vwv3);
			server->maxvcs = WVAL(server->packet, smb_vwv4);
			server->blkmode = WVAL(server->packet, smb_vwv5);
			server->sesskey = DVAL(server->packet, smb_vwv6);
		}

		if (server->max_xmit < given_max_xmit)
		{
			/* We do not distinguish between the client
			   requests and the server response. */
			given_max_xmit = server->max_xmit;
		}
		if (server->protocol >= PROTOCOL_NT1)
		{
			char *workgroup = server->m.domain;
			char *OS_id = "Unix";
			char *client_id = "ksmbfs";

			smb_setup_header(server, SMBsesssetupX, 13,
					 5 + userlen + passlen +
					 strlen(workgroup) + strlen(OS_id) +
					 strlen(client_id));

			WSET(server->packet, smb_vwv0, 0x00ff);
			WSET(server->packet, smb_vwv1, 0);
			WSET(server->packet, smb_vwv2, given_max_xmit);
			WSET(server->packet, smb_vwv3, 2);
			WSET(server->packet, smb_vwv4, server->pid);
			DSET(server->packet, smb_vwv5, server->sesskey);
			WSET(server->packet, smb_vwv7, passlen + 1);
			WSET(server->packet, smb_vwv8, 0);
			WSET(server->packet, smb_vwv9, 0);

			p = SMB_BUF(server->packet);
			strcpy(p, server->m.password);
			p += passlen + 1;
			strcpy(p, server->m.username);
			p += userlen + 1;
			strcpy(p, workgroup);
			p += strlen(p) + 1;
			strcpy(p, OS_id);
			p += strlen(p) + 1;
			strcpy(p, client_id);
		} else
		{
			smb_setup_header(server, SMBsesssetupX, 10,
					 2 + userlen + passlen);

			WSET(server->packet, smb_vwv0, 0x00ff);
			WSET(server->packet, smb_vwv1, 0);
			WSET(server->packet, smb_vwv2, given_max_xmit);
			WSET(server->packet, smb_vwv3, 2);
			WSET(server->packet, smb_vwv4, server->pid);
			DSET(server->packet, smb_vwv5, server->sesskey);
			WSET(server->packet, smb_vwv7, passlen + 1);
			WSET(server->packet, smb_vwv8, 0);
			WSET(server->packet, smb_vwv9, 0);

			p = SMB_BUF(server->packet);
			strcpy(p, server->m.password);
			p += passlen + 1;
			strcpy(p, server->m.username);
		}

		if ((result = smb_request_ok(server, SMBsesssetupX, 3, 0)) < 0)
		{
			DPRINTK("smb_proc_connect: SMBsessetupX failed\n");
			smb_dont_catch_keepalive(server);
			goto fail;
		}
		smb_decode_word(server->packet + 32, &(server->server_uid));
	} else
	{
		server->max_xmit = 0;
		server->maxmux = 0;
		server->maxvcs = 0;
		server->blkmode = 0;
		server->sesskey = 0;
	}

	/* Fine! We have a connection, send a tcon message. */

	smb_setup_header(server, SMBtcon, 0,
			 6 + strlen(server->m.service) +
			 strlen(server->m.password) + strlen(dev));

	p = SMB_BUF(server->packet);
	p = smb_encode_ascii(p, server->m.service, strlen(server->m.service));
	p = smb_encode_ascii(p, server->m.password, strlen(server->m.password));
	p = smb_encode_ascii(p, dev, strlen(dev));

	if ((result = smb_request_ok(server, SMBtcon, 2, 0)) < 0)
	{
		DPRINTK("smb_proc_connect: SMBtcon not verified.\n");
		smb_dont_catch_keepalive(server);
		goto fail;
	}
	DDPRINTK("OK! Managed to set up SMBtcon!\n");

	p = SMB_VWV(server->packet);

	if (server->protocol <= PROTOCOL_COREPLUS)
	{
		word max_xmit;

		p = smb_decode_word(p, &max_xmit);
		server->max_xmit = max_xmit;

		if (server->max_xmit > given_max_xmit)
		{
			server->max_xmit = given_max_xmit;
		}
	} else
	{
		p += 2;
	}

	p = smb_decode_word(p, &server->tid);

	/* Ok, everything is fine. max_xmit does not include */
	/* the TCP-SMB header of 4 bytes. */
	server->max_xmit += 4;

	DPRINTK("max_xmit = %d, tid = %d\n", server->max_xmit, server->tid);

	/* Now make a new packet with the correct size. */
	smb_vfree(server->packet);
	server->packet = NULL;

	server->packet = smb_vmalloc(server->max_xmit);
	if (server->packet == NULL)
	{
		printk("smb_proc_connect: No memory left in end of "
		       "connection phase :-(\n");
		smb_dont_catch_keepalive(server);
		goto fail;
	}
	server->packet_size = server->max_xmit;

	DPRINTK("smb_proc_connect: Normal exit\n");
	return 0;

      fail:
	server->state = CONN_INVALID;
	return result;
}

/* smb_proc_reconnect: server->packet is allocated with
   server->max_xmit bytes if and only if we return >= 0 */
int
smb_proc_connect(struct smb_server *server)
{
	int result;
	smb_lock_server(server);

	result = smb_proc_reconnect(server);

	if ((result < 0) && (server->packet != NULL))
	{
		smb_vfree(server->packet);
		server->packet = NULL;
	}
	smb_unlock_server(server);
	return result;
}

int
smb_proc_disconnect(struct smb_server *server)
{
	smb_setup_header_exclusive(server, SMBtdis, 0, 0);
	return smb_request_ok_unlock(server, SMBtdis, 0, 0);
}
