/*
* 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);
}