summaryrefslogtreecommitdiffstats
path: root/fs/smbfs/proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/smbfs/proc.c')
-rw-r--r--fs/smbfs/proc.c386
1 files changed, 255 insertions, 131 deletions
diff --git a/fs/smbfs/proc.c b/fs/smbfs/proc.c
index f0444f97d..9bfdc90a0 100644
--- a/fs/smbfs/proc.c
+++ b/fs/smbfs/proc.c
@@ -9,7 +9,7 @@
#include <linux/types.h>
#include <linux/errno.h>
-#include <linux/malloc.h>
+#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/stat.h>
@@ -78,6 +78,7 @@ str_upper(char *name, int len)
}
}
+#if 0
static void
str_lower(char *name, int len)
{
@@ -88,6 +89,7 @@ str_lower(char *name, int len)
name++;
}
}
+#endif
/* reverse a string inline. This is used by the dircache walking routines */
static void reverse_string(char *buf, int len)
@@ -358,6 +360,72 @@ date_unix2dos(struct smb_sb_info *server,
*date = nl_day - day_n[month - 1] + 1 + (month << 5) + (year << 9);
}
+/* The following are taken from fs/ntfs/util.c */
+
+/*
+ * Convert the NT UTC (based 1601-01-01, in hundred nanosecond units)
+ * into Unix UTC (based 1970-01-01, in seconds).
+ *
+ * This is very gross because
+ * 1: We must do 64-bit division on a 32-bit machine
+ * 2: We can't use libgcc for long long operations in the kernel
+ * 3: Floating point math in the kernel would corrupt user data
+ */
+static time_t
+smb_ntutc2unixutc(struct smb_sb_info *server, u64 ntutc)
+{
+ const unsigned int D = 10000000;
+ unsigned int H = (unsigned int)(ntutc >> 32);
+ unsigned int L = (unsigned int)ntutc;
+ unsigned int numerator2;
+ unsigned int lowseconds;
+ unsigned int result;
+
+ /*
+ * It is best to subtract 0x019db1ded53e8000 first.
+ * Then the 1601-based date becomes a 1970-based date.
+ */
+ if (L < (unsigned)0xd53e8000) H--;
+ L -= (unsigned)0xd53e8000;
+ H -= (unsigned)0x019db1de;
+
+ /*
+ * Now divide 64-bit numbers on a 32-bit machine :-)
+ * With the subtraction already done, the result fits in 32 bits.
+ * The numerator fits in 56 bits and the denominator fits
+ * in 24 bits, so we can shift by 8 bits to make this work.
+ */
+
+ numerator2 = (H<<8) | (L>>24);
+ result = (numerator2 / D); /* shifted 24 right!! */
+ lowseconds = result << 24;
+
+ numerator2 = ((numerator2-result*D)<<8) | ((L>>16)&0xff);
+ result = (numerator2 / D); /* shifted 16 right!! */
+ lowseconds |= result << 16;
+
+ numerator2 = ((numerator2-result*D)<<8) | ((L>>8)&0xff);
+ result = (numerator2 / D); /* shifted 8 right!! */
+ lowseconds |= result << 8;
+
+ numerator2 = ((numerator2-result*D)<<8) | (L&0xff);
+ result = (numerator2 / D); /* not shifted */
+ lowseconds |= result;
+
+ return lowseconds;
+}
+
+#if 0
+/* Convert the Unix UTC into NT time */
+static u64
+smb_unixutc2ntutc(struct smb_sb_info *server, time_t t)
+{
+ /* Note: timezone conversion is probably wrong. */
+ return ((utc2local(server, t) + (u64)(369*365+89)*24*3600) * 10000000);
+}
+#endif
+
+
/*****************************************************************************/
/* */
/* Support section. */
@@ -421,22 +489,16 @@ fail:
}
/*
- * Returns the maximum read or write size for the current packet size
- * and max_xmit value.
+ * 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 int
+static inline int
smb_get_xmitsize(struct smb_sb_info *server, int overhead)
{
- int size = server->packet_size;
-
- /*
- * Start with the smaller of packet size and max_xmit ...
- */
- if (size > server->opt.max_xmit)
- size = server->opt.max_xmit;
- return size - overhead;
+ return server->opt.max_xmit - overhead;
}
/*
@@ -783,6 +845,23 @@ smb_newconn(struct smb_sb_info *server, struct smb_conn_opt *opt)
server->opt.protocol, server->opt.max_xmit, server->conn_pid,
server->opt.capabilities);
+ /* Make sure we can fit a message of the negotiated size in our
+ packet buffer. */
+ if (server->opt.max_xmit > server->packet_size) {
+ int len = smb_round_length(server->opt.max_xmit);
+ char *buf = smb_vmalloc(len);
+ if (buf) {
+ server->packet = buf;
+ server->packet_size = len;
+ } else {
+ /* else continue with the too small buffer? */
+ PARANOIA("Failed to allocate new packet buffer: "
+ "max_xmit=%d, packet_size=%d\n",
+ server->opt.max_xmit, server->packet_size);
+ server->opt.max_xmit = server->packet_size;
+ }
+ }
+
out:
#ifdef SMB_RETRY_INTR
wake_up_interruptible(&server->wait);
@@ -1025,7 +1104,7 @@ smb_proc_close_inode(struct smb_sb_info *server, struct inode * ino)
result = smb_proc_close(server, ino->u.smbfs_i.fileid,
ino->i_mtime);
- ino->u.smbfs_i.cache_valid &= ~SMB_F_LOCALWRITE;
+ ino->u.smbfs_i.flags &= ~SMB_F_LOCALWRITE;
/*
* Force a revalidation after closing ... some servers
* don't post the size until the file has been closed.
@@ -1115,7 +1194,7 @@ smb_proc_read(struct inode *inode, off_t offset, int count, char *data)
out:
VERBOSE("ino=%ld, fileid=%d, count=%d, result=%d\n",
- inode->ino, inode->u.smbfs_i.fileid, count, result);
+ inode->i_ino, inode->u.smbfs_i.fileid, count, result);
smb_unlock_server(server);
return result;
}
@@ -1128,7 +1207,7 @@ smb_proc_write(struct inode *inode, off_t offset, int count, const char *data)
__u8 *p;
VERBOSE("ino=%ld, fileid=%d, count=%d@%ld, packet_size=%d\n",
- inode->ino, inode->u.smbfs_i.fileid, count, offset,
+ inode->i_ino, inode->u.smbfs_i.fileid, count, offset,
server->packet_size);
smb_lock_server(server);
@@ -1355,14 +1434,13 @@ smb_proc_trunc(struct smb_sb_info *server, __u16 fid, __u32 length)
smb_lock_server(server);
retry:
- p = smb_setup_header(server, SMBwrite, 5, 0);
+ p = smb_setup_header(server, SMBwrite, 5, 3);
WSET(server->packet, smb_vwv0, fid);
WSET(server->packet, smb_vwv1, 0);
DSET(server->packet, smb_vwv2, length);
WSET(server->packet, smb_vwv4, 0);
- *p++ = 4;
- *p++ = 0;
- smb_setup_bcc(server, p);
+ *p++ = 1;
+ WSET(p, 0, 0);
if ((result = smb_request_ok(server, SMBwrite, 1, 0)) < 0) {
if (smb_retry(server))
@@ -1419,38 +1497,47 @@ smb_init_root_dirent(struct smb_sb_info *server, struct smb_fattr *fattr)
}
/*
- * Note that we are now returning the name as a reference to avoid
- * an extra copy, and that the upper/lower casing is done in place.
+ * Decode a dirent for old protocols
+ *
+ * qname is filled with the decoded, and possibly translated, name.
+ * fattr receives decoded attributes
*
* Bugs Noted:
* (1) Pathworks servers may pad the name with extra spaces.
*/
-static __u8 *
-smb_decode_dirent(struct smb_sb_info *server, __u8 *p,
- struct cache_dirent *entry)
+static char *
+smb_decode_short_dirent(struct smb_sb_info *server, char *p,
+ struct qstr *qname, struct smb_fattr *fattr)
{
int len;
/*
* SMB doesn't have a concept of inode numbers ...
*/
- entry->ino = 0;
+ smb_init_dirent(server, fattr);
+ fattr->f_ino = 0; /* FIXME: do we need this? */
p += SMB_STATUS_SIZE; /* reserved (search_status) */
- entry->name = p + 9;
- len = strlen(entry->name);
- if (len > 12)
- len = 12;
+ fattr->attr = *p;
+ fattr->f_mtime = date_dos2unix(server, WVAL(p, 3), WVAL(p, 1));
+ fattr->f_size = DVAL(p, 5);
+ fattr->f_ctime = fattr->f_mtime;
+ fattr->f_atime = fattr->f_mtime;
+ qname->name = p + 9;
+ len = strnlen(qname->name, 12);
/*
* Trim trailing blanks for Pathworks servers
*/
- while (len > 2 && entry->name[len-1] == ' ')
+ while (len > 2 && qname->name[len-1] == ' ')
len--;
- entry->len = len;
+ qname->len = len;
+ smb_finish_dirent(server, fattr);
+
+#if 0
/* FIXME: These only work for ascii chars, and recent smbmount doesn't
- allow the flag to be set anyway. Remove? */
+ allow the flag to be set anyway. It kills const. Remove? */
switch (server->opt.case_handling) {
case SMB_CASE_UPPER:
str_upper(entry->name, len);
@@ -1461,24 +1548,31 @@ smb_decode_dirent(struct smb_sb_info *server, __u8 *p,
default:
break;
}
+#endif
- entry->len = server->convert(server->name_buf, SMB_MAXNAMELEN,
- entry->name, len,
+ qname->len = server->convert(server->name_buf, SMB_MAXNAMELEN,
+ qname->name, len,
server->remote_nls, server->local_nls);
- entry->name = server->name_buf;
+ qname->name = server->name_buf;
- DEBUG1("len=%d, name=%.*s\n", entry->len, entry->len, entry->name);
+ DEBUG1("len=%d, name=%.*s\n", qname->len, qname->len, qname->name);
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 <
- SMB_PROTOCOL_LANMAN2 */
-
+/*
+ * This routine is used to read in directory entries from the network.
+ * Note that it is for short directory name seeks, i.e.: protocol <
+ * SMB_PROTOCOL_LANMAN2
+ */
static int
-smb_proc_readdir_short(struct smb_sb_info *server, struct dentry *dir, int fpos,
- void *cachep)
+smb_proc_readdir_short(struct file *filp, void *dirent, filldir_t filldir,
+ struct smb_cache_control *ctl)
{
+ struct dentry *dir = filp->f_dentry;
+ struct smb_sb_info *server = server_from_dentry(dir);
+ struct qstr qname;
+ struct smb_fattr fattr;
+
unsigned char *p;
int result;
int i, first, entries_seen, entries;
@@ -1489,13 +1583,10 @@ smb_proc_readdir_short(struct smb_sb_info *server, struct dentry *dir, int fpos,
static struct qstr mask = { "*.*", 3, 0 };
unsigned char *last_status;
- VERBOSE("%s/%s, pos=%d\n", DENTRY_PATH(dir), fpos);
+ VERBOSE("%s/%s\n", DENTRY_PATH(dir));
smb_lock_server(server);
- /* N.B. We need to reinitialize the cache to restart */
-retry:
- smb_init_dircache(cachep);
first = 1;
entries = 0;
entries_seen = 2; /* implicit . and .. */
@@ -1530,8 +1621,10 @@ retry:
if ((server->rcls == ERRDOS) &&
(server->err == ERRnofiles))
break;
- if (smb_retry(server))
- goto retry;
+ if (smb_retry(server)) {
+ ctl->idx = -1; /* retry */
+ result = 0;
+ }
goto unlock_return;
}
p = SMB_VWV(server->packet);
@@ -1568,23 +1661,18 @@ retry:
/* Now we are ready to parse smb directory entries. */
for (i = 0; i < count; i++) {
- struct cache_dirent this_ent, *entry = &this_ent;
+ p = smb_decode_short_dirent(server, p,
+ &qname, &fattr);
- p = smb_decode_dirent(server, p, entry);
- if (entries_seen == 2 && entry->name[0] == '.') {
- if (entry->len == 1)
+ if (entries_seen == 2 && qname.name[0] == '.') {
+ if (qname.len == 1)
continue;
- if (entry->name[1] == '.' && entry->len == 2)
+ if (qname.name[1] == '.' && qname.len == 2)
continue;
}
- if (entries_seen >= fpos) {
- DEBUG1("fpos=%u\n", entries_seen);
- smb_add_to_cache(cachep, entry, entries_seen);
- entries++;
- } else {
- VERBOSE("skipped, seen=%d, i=%d, fpos=%d\n",
- entries_seen, i, fpos);
- }
+ if (!smb_fill_cache(filp, dirent, filldir, ctl,
+ &qname, &fattr))
+ ; /* stop reading? */
entries_seen++;
}
}
@@ -1600,44 +1688,69 @@ unlock_return:
* level 1 for anything below NT1 protocol
* level 260 for NT1 protocol
*
- * We return a reference to the name string to avoid copying, and perform
- * any needed upper/lower casing in place.
+ * qname is filled with the decoded, and possibly translated, name
+ * fattr receives decoded attributes.
*
* Bugs Noted:
* (1) Win NT 4.0 appends a null byte to names and counts it in the length!
*/
static char *
-smb_decode_long_dirent(struct smb_sb_info *server, char *p,
- struct cache_dirent *entry, int level)
+smb_decode_long_dirent(struct smb_sb_info *server, char *p, int level,
+ struct qstr *qname, struct smb_fattr *fattr)
{
char *result;
unsigned int len = 0;
+ __u16 date, time;
/*
* SMB doesn't have a concept of inode numbers ...
*/
- entry->ino = 0;
+ smb_init_dirent(server, fattr);
+ fattr->f_ino = 0; /* FIXME: do we need this? */
switch (level) {
case 1:
len = *((unsigned char *) p + 22);
- entry->name = p + 23;
+ qname->name = p + 23;
result = p + 24 + len;
+ date = WVAL(p, 0);
+ time = WVAL(p, 2);
+ fattr->f_ctime = date_dos2unix(server, date, time);
+
+ date = WVAL(p, 4);
+ time = WVAL(p, 6);
+ fattr->f_atime = date_dos2unix(server, date, time);
+
+ date = WVAL(p, 8);
+ time = WVAL(p, 10);
+ fattr->f_mtime = date_dos2unix(server, date, time);
+ fattr->f_size = DVAL(p, 12);
+ /* ULONG allocation size */
+ fattr->attr = WVAL(p, 20);
+
VERBOSE("info 1 at %p, len=%d, name=%.*s\n",
- p, len, len, entry->name);
+ p, len, len, qname->name);
break;
case 260:
result = p + WVAL(p, 0);
len = DVAL(p, 60);
if (len > 255) len = 255;
/* NT4 null terminates */
- entry->name = p + 94;
- if (len && entry->name[len-1] == '\0')
+ qname->name = p + 94;
+ if (len && qname->name[len-1] == '\0')
len--;
+ fattr->f_ctime = smb_ntutc2unixutc(server, LVAL(p, 8));
+ fattr->f_atime = smb_ntutc2unixutc(server, LVAL(p, 16));
+ fattr->f_mtime = smb_ntutc2unixutc(server, LVAL(p, 24));
+ /* change time (32) */
+ fattr->f_size = DVAL(p, 40);
+ /* alloc size (48) */
+ fattr->attr = DVAL(p, 56);
+
VERBOSE("info 260 at %p, len=%d, name=%.*s\n",
- p, len, len, entry->name);
+ p, len, len, qname->name);
break;
default:
PARANOIA("Unknown info level %d\n", level);
@@ -1645,21 +1758,28 @@ smb_decode_long_dirent(struct smb_sb_info *server, char *p,
goto out;
}
+ smb_finish_dirent(server, fattr);
+
+#if 0
+ /* FIXME: These only work for ascii chars, and recent smbmount doesn't
+ allow the flag to be set anyway. Remove? */
switch (server->opt.case_handling) {
case SMB_CASE_UPPER:
- str_upper(entry->name, len);
+ str_upper(qname->name, len);
break;
case SMB_CASE_LOWER:
- str_lower(entry->name, len);
+ str_lower(qname->name, len);
break;
default:
break;
}
+#endif
- entry->len = server->convert(server->name_buf, SMB_MAXNAMELEN,
- entry->name, len,
+ qname->len = server->convert(server->name_buf, SMB_MAXNAMELEN,
+ qname->name, len,
server->remote_nls, server->local_nls);
- entry->name = server->name_buf;
+ qname->name = server->name_buf;
+
out:
return result;
}
@@ -1682,13 +1802,18 @@ out:
* single file. (E.g. echo hi >foo breaks, rm -f foo works.)
*/
static int
-smb_proc_readdir_long(struct smb_sb_info *server, struct dentry *dir, int fpos,
- void *cachep)
+smb_proc_readdir_long(struct file *filp, void *dirent, filldir_t filldir,
+ struct smb_cache_control *ctl)
{
- unsigned char *p;
- char *mask, *lastname, *param = server->temp_buf;
+ struct dentry *dir = filp->f_dentry;
+ struct smb_sb_info *server = server_from_dentry(dir);
+ struct qstr qname;
+ struct smb_fattr fattr;
+
+ unsigned char *p, *lastname;
+ char *mask, *param = server->temp_buf;
__u16 command;
- int first, entries, entries_seen;
+ int first, entries_seen;
/* Both NT and OS/2 accept info level 1 (but see note below). */
int info_level = 260;
@@ -1714,7 +1839,6 @@ smb_proc_readdir_long(struct smb_sb_info *server, struct dentry *dir, int fpos,
smb_lock_server(server);
-retry:
/*
* Encode the initial path
*/
@@ -1722,17 +1846,13 @@ retry:
mask_len = smb_encode_path(server, mask, dir, &star);
if (mask_len < 0) {
- entries = mask_len;
+ result = mask_len;
goto unlock_return;
}
first = 1;
- VERBOSE("starting fpos=%d, mask=%s\n", fpos, mask);
+ VERBOSE("starting mask_len=%d, mask=%s\n", mask_len, mask);
- /*
- * We must reinitialize the dircache when retrying.
- */
- smb_init_dircache(cachep);
- entries = 0;
+ result = 0;
entries_seen = 2;
ff_eos = 0;
@@ -1741,7 +1861,7 @@ retry:
if (loop_count > 10) {
printk(KERN_WARNING "smb_proc_readdir_long: "
"Looping in FIND_NEXT??\n");
- entries = -EIO;
+ result = -EIO;
break;
}
@@ -1773,10 +1893,11 @@ retry:
if (result < 0) {
if (smb_retry(server)) {
PARANOIA("error=%d, retrying\n", result);
- goto retry;
+ ctl->idx = -1; /* retry */
+ result = 0;
+ goto unlock_return;
}
PARANOIA("error=%d, breaking\n", result);
- entries = result;
break;
}
@@ -1788,10 +1909,10 @@ retry:
continue;
}
- if (server->rcls != 0) {
- PARANOIA("name=%s, entries=%d, rcls=%d, err=%d\n",
- mask, entries, server->rcls, server->err);
- entries = -smb_errno(server);
+ if (server->rcls != 0) {
+ result = -smb_errno(server);
+ PARANOIA("name=%s, result=%d, rcls=%d, err=%d\n",
+ mask, result, server->rcls, server->err);
break;
}
@@ -1810,41 +1931,44 @@ retry:
if (ff_searchcount == 0)
break;
- /* we might need the lastname for continuations */
+ /*
+ * We might need the lastname for continuations.
+ *
+ * Note that some servers (win95?) point to the filename and
+ * others (NT4, Samba using NT1) to the dir entry. We assume
+ * here that those who do not point to a filename do not need
+ * this info to continue the listing. OS/2 needs this, but it
+ * talks "infolevel 1"
+ */
mask_len = 0;
- if (ff_lastname > 0) {
+ if (info_level == 1 && ff_lastname > 0 &&
+ ff_lastname < resp_data_len) {
lastname = resp_data + ff_lastname;
- switch (info_level) {
- case 260:
- if (ff_lastname < resp_data_len)
- mask_len = resp_data_len - ff_lastname;
- break;
- case 1:
- /* Win NT 4.0 doesn't set the length byte */
- lastname++;
- if (ff_lastname + 2 < resp_data_len)
- mask_len = strlen(lastname);
- break;
- }
+
+ /* lastname points to a length byte */
+ mask_len = *lastname++;
+ if (ff_lastname + 1 + mask_len > resp_data_len)
+ mask_len = resp_data_len-ff_lastname-1;
+
/*
* Update the mask string for the next message.
*/
+ if (mask_len < 0)
+ mask_len = 0;
if (mask_len > 255)
mask_len = 255;
if (mask_len)
strncpy(mask, lastname, mask_len);
}
- mask[mask_len] = 0;
- VERBOSE("new mask, len=%d@%d, mask=%s\n",
- mask_len, ff_lastname, mask);
+ mask_len = strnlen(mask, mask_len);
+ VERBOSE("new mask, len=%d@%d of %d, mask=%.*s\n",
+ mask_len, ff_lastname, resp_data_len, mask_len, mask);
/* Now we are ready to parse smb directory entries. */
/* point to the data bytes */
p = resp_data;
for (i = 0; i < ff_searchcount; i++) {
- struct cache_dirent this_ent, *entry = &this_ent;
-
/* make sure we stay within the buffer */
if (p >= resp_data + resp_data_len) {
printk(KERN_ERR "smb_proc_readdir_long: "
@@ -1856,21 +1980,21 @@ retry:
goto unlock_return;
}
- p = smb_decode_long_dirent(server, p, entry,
- info_level);
+ p = smb_decode_long_dirent(server, p, info_level,
+ &qname, &fattr);
/* ignore . and .. from the server */
- if (entries_seen == 2 && entry->name[0] == '.') {
- if (entry->len == 1)
+ if (entries_seen == 2 && qname.name[0] == '.') {
+ if (qname.len == 1)
continue;
- if (entry->name[1] == '.' && entry->len == 2)
+ if (qname.name[1] == '.' && qname.len == 2)
continue;
}
- if (entries_seen >= fpos) {
- smb_add_to_cache(cachep, entry, entries_seen);
- entries += 1;
- }
- entries_seen++;
+
+ if (!smb_fill_cache(filp, dirent, filldir, ctl,
+ &qname, &fattr))
+ ; /* stop reading? */
+ entries_seen++;
}
VERBOSE("received %d entries, eos=%d\n", ff_searchcount,ff_eos);
@@ -1881,19 +2005,19 @@ retry:
unlock_return:
smb_unlock_server(server);
- return entries;
+ return result;
}
int
-smb_proc_readdir(struct dentry *dir, int fpos, void *cachep)
+smb_proc_readdir(struct file *filp, void *dirent, filldir_t filldir,
+ struct smb_cache_control *ctl)
{
- struct smb_sb_info *server;
+ struct smb_sb_info *server = server_from_dentry(filp->f_dentry);
- server = server_from_dentry(dir);
if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2)
- return smb_proc_readdir_long(server, dir, fpos, cachep);
+ return smb_proc_readdir_long(filp, dirent, filldir, ctl);
else
- return smb_proc_readdir_short(server, dir, fpos, cachep);
+ return smb_proc_readdir_short(filp, dirent, filldir, ctl);
}
/*