📄 proc.c
字号:
/* * Trim trailing blanks for Pathworks servers */ while (len > 2 && qname->name[len-1] == ' ') 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. It kills const. Remove? */ switch (server->opt.case_handling) { case SMB_CASE_UPPER: str_upper(entry->name, len); break; case SMB_CASE_LOWER: str_lower(entry->name, len); break; default: break; }#endif qname->len = server->convert(server->name_buf, SMB_MAXNAMELEN, qname->name, len, server->remote_nls, server->local_nls); qname->name = server->name_buf; 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 */static intsmb_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; char *p; int result; int i, first, entries_seen, entries; int entries_asked = (server->opt.max_xmit - 100) / SMB_DIRINFO_SIZE; __u16 bcc; __u16 count; char status[SMB_STATUS_SIZE]; static struct qstr mask = { "*.*", 3, 0 }; unsigned char *last_status; VERBOSE("%s/%s\n", DENTRY_PATH(dir)); smb_lock_server(server); first = 1; entries = 0; entries_seen = 2; /* implicit . and .. */ while (1) { p = smb_setup_header(server, SMBsearch, 2, 0); WSET(server->packet, smb_vwv0, entries_asked); WSET(server->packet, smb_vwv1, aDIR); if (first == 1) { result = smb_simple_encode_path(server, &p, dir, &mask); if (result < 0) goto unlock_return; if (p + 3 > (char*)server->packet+server->packet_size) { result = -ENAMETOOLONG; goto unlock_return; } *p++ = 5; WSET(p, 0, 0); p += 2; first = 0; } else { if (p + 5 + SMB_STATUS_SIZE > (char*)server->packet + server->packet_size) { result = -ENAMETOOLONG; goto unlock_return; } *p++ = 4; *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); result = smb_request_ok(server, SMBsearch, 1, -1); if (result < 0) { if ((server->rcls == ERRDOS) && (server->err == ERRnofiles)) break; if (smb_retry(server)) { ctl->idx = -1; /* retry */ result = 0; } goto unlock_return; } p = SMB_VWV(server->packet); count = WVAL(p, 0); if (count <= 0) break; result = -EIO; bcc = WVAL(p, 2); if (bcc != count * SMB_DIRINFO_SIZE + 3) goto unlock_return; p += 7; /* Make sure the response fits in the buffer. Fixed sized entries means we don't have to check in the decode loop. */ last_status = SMB_BUF(server->packet) + 3 + (count - 1) * SMB_DIRINFO_SIZE; if (last_status + SMB_DIRINFO_SIZE >= server->packet + server->packet_size) { printk(KERN_ERR "smb_proc_readdir_short: " "last dir entry outside buffer! " "%d@%p %d@%p\n", SMB_DIRINFO_SIZE, last_status, server->packet_size, server->packet); goto unlock_return; } /* Read the last entry into the status field. */ memcpy(status, last_status, SMB_STATUS_SIZE); /* Now we are ready to parse smb directory entries. */ for (i = 0; i < count; i++) { p = smb_decode_short_dirent(server, p, &qname, &fattr); if (entries_seen == 2 && qname.name[0] == '.') { if (qname.len == 1) continue; if (qname.name[1] == '.' && qname.len == 2) continue; } if (!smb_fill_cache(filp, dirent, filldir, ctl, &qname, &fattr)) ; /* stop reading? */ entries_seen++; } } result = entries;unlock_return: smb_unlock_server(server); return result;}/* * Interpret a long filename structure using the specified info level: * level 1 for anything below NT1 protocol * level 260 for NT1 protocol * * 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, 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 ... */ smb_init_dirent(server, fattr); fattr->f_ino = 0; /* FIXME: do we need this? */ switch (level) { case 1: len = *((unsigned char *) p + 22); 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, qname->name); break; case 260: result = p + WVAL(p, 0); len = DVAL(p, 60); if (len > 255) len = 255; /* NT4 null terminates */ qname->name = p + 94; if (len && qname->name[len-1] == '\0') len--; fattr->f_ctime = smb_ntutc2unixutc(LVAL(p, 8)); fattr->f_atime = smb_ntutc2unixutc(LVAL(p, 16)); fattr->f_mtime = smb_ntutc2unixutc(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, qname->name); break; default: PARANOIA("Unknown info level %d\n", level); result = p + WVAL(p, 0); 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(qname->name, len); break; case SMB_CASE_LOWER: str_lower(qname->name, len); break; default: break; }#endif qname->len = server->convert(server->name_buf, SMB_MAXNAMELEN, qname->name, len, server->remote_nls, server->local_nls); qname->name = server->name_buf;out: return result;}/* findfirst/findnext flags */#define SMB_CLOSE_AFTER_FIRST (1<<0)#define SMB_CLOSE_IF_END (1<<1)#define SMB_REQUIRE_RESUME_KEY (1<<2)#define SMB_CONTINUE_BIT (1<<3)/* * Note: samba-2.0.7 (at least) has a very similar routine, cli_list, in * source/libsmb/clilist.c. When looking for smb bugs in the readdir code, * go there for advise. * * Bugs Noted: * (1) When using Info Level 1 Win NT 4.0 truncates directory listings * for certain patterns of names and/or lengths. The breakage pattern * is completely reproducible and can be toggled by the creation of a * single file. (E.g. echo hi >foo breaks, rm -f foo works.) */static intsmb_proc_readdir_long(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, *lastname; char *mask, *param = server->temp_buf; __u16 command; int first, entries_seen; /* Both NT and OS/2 accept info level 1 (but see note below). */ int info_level = 260; const int max_matches = 512; unsigned char *resp_data = NULL; unsigned char *resp_param = NULL; int resp_data_len = 0; int resp_param_len = 0; int ff_searchcount = 0; int ff_eos = 0; int ff_lastname = 0; int ff_dir_handle = 0; int loop_count = 0; int mask_len, i, result; static struct qstr star = { "*", 1, 0 }; /* * use info level 1 for older servers that don't do 260 */ if (server->opt.protocol < SMB_PROTOCOL_NT1) info_level = 1; smb_lock_server(server); /* * Encode the initial path */ mask = param + 12; mask_len = smb_encode_path(server, mask, SMB_MAXNAMELEN+1, dir, &star); if (mask_len < 0) { result = mask_len; goto unlock_return; } mask_len--; /* mask_len is strlen, not #bytes */ first = 1; VERBOSE("starting mask_len=%d, mask=%s\n", mask_len, mask); result = 0; entries_seen = 2; ff_eos = 0; while (ff_eos == 0) { loop_count += 1; if (loop_count > 10) { printk(KERN_WARNING "smb_proc_readdir_long: " "Looping in FIND_NEXT??\n"); result = -EIO; break; } if (first != 0) { command = TRANSACT2_FINDFIRST; WSET(param, 0, aSYSTEM | aHIDDEN | aDIR); WSET(param, 2, max_matches); /* max count */ WSET(param, 4, SMB_CLOSE_IF_END); WSET(param, 6, info_level); DSET(param, 8, 0); } else { command = TRANSACT2_FINDNEXT; VERBOSE("handle=0x%X, lastname=%d, mask=%s\n", ff_dir_handle, ff_lastname, mask); WSET(param, 0, ff_dir_handle); /* search handle */ WSET(param, 2, max_matches); /* max count */ WSET(param, 4, info_level); DSET(param, 6, 0); WSET(param, 10, SMB_CONTINUE_BIT|SMB_CLOSE_IF_END); } result = smb_trans2_request(server, command, 0, NULL, 12 + mask_len + 1, param, &resp_data_len, &resp_data, &resp_param_len, &resp_param); if (result < 0) { if (smb_retry(server)) { PARANOIA("error=%d, retrying\n", result); ctl->idx = -1; /* retry */ result = 0; goto unlock_return; } PARANOIA("error=%d, breaking\n", result); break; } if (server->rcls == ERRSRV && server->err == ERRerror) { /* a damn Win95 bug - sometimes it clags if you ask it too fast */ current->state = TASK_INTERRUPTIBLE; schedule_timeout(HZ/5); continue; } 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; } /* 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; /* * 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 and talks infolevel 1 * NetApps want lastname with infolevel 260 * * Both are happy if we return the data they point to. So we do. */ mask_len = 0; if (ff_lastname > 0 && ff_lastname < resp_data_len) { lastname = resp_data + ff_lastname; switch (info_level) { case 260: mask_len = resp_data_len - ff_lastname; break; case 1: /* 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; break; } /* * 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_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++) { /* make sure we stay within the buffer */ if (p >= resp_data + resp_data_len) { printk(KERN_ERR "smb_proc_readdir_long: " "dirent pointer outside buffer! " "%p %d@%p %d@%p\n", p, resp_data_len, resp_data, server->packet_size, server->packet); result = -EIO; /* always a comm. error? */ goto unlock_return; } p = smb_decode_long_dirent(server, p, info_level, &qname, &fattr); /* ignore . and .. from the server */ if (entries_seen == 2 && qname.name[0] == '.') { if (qname.len == 1) continue; if (qname.name[1] == '.' && qname.len == 2) continue; } 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); first = 0; loop_count = 0; }unlock_return: smb_unlock_server(server); return result;}intsmb_proc_readdir(struct file *filp, void *dirent, filldir_t filldir, struct smb_cache_control *ctl){ struct smb_sb_info *server = server_from_dentry(filp->f_dentry); if (server->opt.protocol >= SMB_PROTOCOL_LANMAN2) return smb_proc_readdir_long(filp, dirent, filldir, ctl); else return smb_proc_readdir_short(filp, dirent, filldir, ctl);}/* * This version uses the trans2 TRANSACT2_FINDFIRST message * to get the attribute data. * Note: called with the server locked. * * Bugs Noted:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -