Skip to content

Commit 20d49b7

Browse files
palijfvogel
authored andcommitted
cifs: Fix changing times and read-only attr over SMB1 smb_set_file_info() function
[ Upstream commit f122121796f91168d0894c2710b8dd71330a34f8 ] Function CIFSSMBSetPathInfo() is not supported by non-NT servers and returns error. Fallback code via open filehandle and CIFSSMBSetFileInfo() does not work neither because CIFS_open() works also only on NT server. Therefore currently the whole smb_set_file_info() function as a SMB1 callback for the ->set_file_info() does not work with older non-NT SMB servers, like Win9x and others. This change implements fallback code in smb_set_file_info() which will works with any server and allows to change time values and also to set or clear read-only attributes. To make existing fallback code via CIFSSMBSetFileInfo() working with also non-NT servers, it is needed to change open function from CIFS_open() (which is NT specific) to cifs_open_file() which works with any server (this is just a open wrapper function which choose the correct open function supported by the server). CIFSSMBSetFileInfo() is working also on non-NT servers, but zero time values are not treated specially. So first it is needed to fill all time values if some of them are missing, via cifs_query_path_info() call. There is another issue, opening file in write-mode (needed for changing attributes) is not possible when the file has read-only attribute set. The only option how to clear read-only attribute is via SMB_COM_SETATTR command. And opening directory is not possible neither and here the SMB_COM_SETATTR command is the only option how to change attributes. And CIFSSMBSetFileInfo() does not honor setting read-only attribute, so for setting is also needed to use SMB_COM_SETATTR command. Existing code in cifs_query_path_info() is already using SMB_COM_GETATTR as a fallback code path (function SMBQueryInformation()), so introduce a new function SMBSetInformation which will implement SMB_COM_SETATTR command. My testing showed that Windows XP SMB1 client is also using SMB_COM_SETATTR command for setting or clearing read-only attribute against non-NT server. So this can prove that this is the correct way how to do it. With this change it is possible set all 4 time values and all attributes, including clearing and setting read-only bit on non-NT SMB servers. Tested against Win98 SMB1 server. This change fixes "touch" command which was failing when called on existing file. And fixes also "chmod +w" and "chmod -w" commands which were also failing (as they are changing read-only attribute). Note that this change depends on following change "cifs: Improve cifs_query_path_info() and cifs_query_file_info()" as it require to query all 4 time attribute values. Signed-off-by: Pali Rohár <[email protected]> Signed-off-by: Steve French <[email protected]> Signed-off-by: Sasha Levin <[email protected]> (cherry picked from commit 6b1a9a76470971a3a3ac222a1aea75c25309af39) Signed-off-by: Jack Vogel <[email protected]>
1 parent 106a6a0 commit 20d49b7

File tree

4 files changed

+166
-12
lines changed

4 files changed

+166
-12
lines changed

fs/smb/client/cifspdu.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,10 +1226,9 @@ typedef struct smb_com_query_information_rsp {
12261226
typedef struct smb_com_setattr_req {
12271227
struct smb_hdr hdr; /* wct = 8 */
12281228
__le16 attr;
1229-
__le16 time_low;
1230-
__le16 time_high;
1229+
__le32 last_write_time;
12311230
__le16 reserved[5]; /* must be zero */
1232-
__u16 ByteCount;
1231+
__le16 ByteCount;
12331232
__u8 BufferFormat; /* 4 = ASCII */
12341233
unsigned char fileName[];
12351234
} __attribute__((packed)) SETATTR_REQ;

fs/smb/client/cifsproto.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,10 @@ extern int CIFSSMBQFSUnixInfo(const unsigned int xid, struct cifs_tcon *tcon);
396396
extern int CIFSSMBQFSPosixInfo(const unsigned int xid, struct cifs_tcon *tcon,
397397
struct kstatfs *FSData);
398398

399+
extern int SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon,
400+
const char *fileName, __le32 attributes, __le64 write_time,
401+
const struct nls_table *nls_codepage,
402+
struct cifs_sb_info *cifs_sb);
399403
extern int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
400404
const char *fileName, const FILE_BASIC_INFO *data,
401405
const struct nls_table *nls_codepage,

fs/smb/client/cifssmb.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5199,6 +5199,63 @@ CIFSSMBSetFileSize(const unsigned int xid, struct cifs_tcon *tcon,
51995199
return rc;
52005200
}
52015201

5202+
int
5203+
SMBSetInformation(const unsigned int xid, struct cifs_tcon *tcon,
5204+
const char *fileName, __le32 attributes, __le64 write_time,
5205+
const struct nls_table *nls_codepage,
5206+
struct cifs_sb_info *cifs_sb)
5207+
{
5208+
SETATTR_REQ *pSMB;
5209+
SETATTR_RSP *pSMBr;
5210+
struct timespec64 ts;
5211+
int bytes_returned;
5212+
int name_len;
5213+
int rc;
5214+
5215+
cifs_dbg(FYI, "In %s path %s\n", __func__, fileName);
5216+
5217+
retry:
5218+
rc = smb_init(SMB_COM_SETATTR, 8, tcon, (void **) &pSMB,
5219+
(void **) &pSMBr);
5220+
if (rc)
5221+
return rc;
5222+
5223+
if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) {
5224+
name_len =
5225+
cifsConvertToUTF16((__le16 *) pSMB->fileName,
5226+
fileName, PATH_MAX, nls_codepage,
5227+
cifs_remap(cifs_sb));
5228+
name_len++; /* trailing null */
5229+
name_len *= 2;
5230+
} else {
5231+
name_len = copy_path_name(pSMB->fileName, fileName);
5232+
}
5233+
/* Only few attributes can be set by this command, others are not accepted by Win9x. */
5234+
pSMB->attr = cpu_to_le16(le32_to_cpu(attributes) &
5235+
(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_ARCHIVE));
5236+
/* Zero write time value (in both NT and SETATTR formats) means to not change it. */
5237+
if (le64_to_cpu(write_time) != 0) {
5238+
ts = cifs_NTtimeToUnix(write_time);
5239+
pSMB->last_write_time = cpu_to_le32(ts.tv_sec);
5240+
}
5241+
pSMB->BufferFormat = 0x04;
5242+
name_len++; /* account for buffer type byte */
5243+
inc_rfc1001_len(pSMB, (__u16)name_len);
5244+
pSMB->ByteCount = cpu_to_le16(name_len);
5245+
5246+
rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB,
5247+
(struct smb_hdr *) pSMBr, &bytes_returned, 0);
5248+
if (rc)
5249+
cifs_dbg(FYI, "Send error in %s = %d\n", __func__, rc);
5250+
5251+
cifs_buf_release(pSMB);
5252+
5253+
if (rc == -EAGAIN)
5254+
goto retry;
5255+
5256+
return rc;
5257+
}
5258+
52025259
/* Some legacy servers such as NT4 require that the file times be set on
52035260
an open handle, rather than by pathname - this is awkward due to
52045261
potential access conflicts on the open, but it is unavoidable for these

fs/smb/client/smb1ops.c

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -912,27 +912,68 @@ smb_set_file_info(struct inode *inode, const char *full_path,
912912
struct cifs_fid fid;
913913
struct cifs_open_parms oparms;
914914
struct cifsFileInfo *open_file;
915+
FILE_BASIC_INFO new_buf;
916+
struct cifs_open_info_data query_data;
917+
__le64 write_time = buf->LastWriteTime;
915918
struct cifsInodeInfo *cinode = CIFS_I(inode);
916919
struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
917920
struct tcon_link *tlink = NULL;
918921
struct cifs_tcon *tcon;
919922

920923
/* if the file is already open for write, just use that fileid */
921924
open_file = find_writable_file(cinode, FIND_WR_FSUID_ONLY);
925+
922926
if (open_file) {
923927
fid.netfid = open_file->fid.netfid;
924928
netpid = open_file->pid;
925929
tcon = tlink_tcon(open_file->tlink);
926-
goto set_via_filehandle;
930+
} else {
931+
tlink = cifs_sb_tlink(cifs_sb);
932+
if (IS_ERR(tlink)) {
933+
rc = PTR_ERR(tlink);
934+
tlink = NULL;
935+
goto out;
936+
}
937+
tcon = tlink_tcon(tlink);
927938
}
928939

929-
tlink = cifs_sb_tlink(cifs_sb);
930-
if (IS_ERR(tlink)) {
931-
rc = PTR_ERR(tlink);
932-
tlink = NULL;
933-
goto out;
940+
/*
941+
* Non-NT servers interprets zero time value in SMB_SET_FILE_BASIC_INFO
942+
* over TRANS2_SET_FILE_INFORMATION as a valid time value. NT servers
943+
* interprets zero time value as do not change existing value on server.
944+
* API of ->set_file_info() callback expects that zero time value has
945+
* the NT meaning - do not change. Therefore if server is non-NT and
946+
* some time values in "buf" are zero, then fetch missing time values.
947+
*/
948+
if (!(tcon->ses->capabilities & CAP_NT_SMBS) &&
949+
(!buf->CreationTime || !buf->LastAccessTime ||
950+
!buf->LastWriteTime || !buf->ChangeTime)) {
951+
rc = cifs_query_path_info(xid, tcon, cifs_sb, full_path, &query_data);
952+
if (rc) {
953+
if (open_file) {
954+
cifsFileInfo_put(open_file);
955+
open_file = NULL;
956+
}
957+
goto out;
958+
}
959+
/*
960+
* Original write_time from buf->LastWriteTime is preserved
961+
* as SMBSetInformation() interprets zero as do not change.
962+
*/
963+
new_buf = *buf;
964+
buf = &new_buf;
965+
if (!buf->CreationTime)
966+
buf->CreationTime = query_data.fi.CreationTime;
967+
if (!buf->LastAccessTime)
968+
buf->LastAccessTime = query_data.fi.LastAccessTime;
969+
if (!buf->LastWriteTime)
970+
buf->LastWriteTime = query_data.fi.LastWriteTime;
971+
if (!buf->ChangeTime)
972+
buf->ChangeTime = query_data.fi.ChangeTime;
934973
}
935-
tcon = tlink_tcon(tlink);
974+
975+
if (open_file)
976+
goto set_via_filehandle;
936977

937978
rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb->local_nls,
938979
cifs_sb);
@@ -953,15 +994,53 @@ smb_set_file_info(struct inode *inode, const char *full_path,
953994
.fid = &fid,
954995
};
955996

956-
cifs_dbg(FYI, "calling SetFileInfo since SetPathInfo for times not supported by this server\n");
957-
rc = CIFS_open(xid, &oparms, &oplock, NULL);
997+
if (S_ISDIR(inode->i_mode) && !(tcon->ses->capabilities & CAP_NT_SMBS)) {
998+
/* Opening directory path is not possible on non-NT servers. */
999+
rc = -EOPNOTSUPP;
1000+
} else {
1001+
/*
1002+
* Use cifs_open_file() instead of CIFS_open() as the
1003+
* cifs_open_file() selects the correct function which
1004+
* works also on non-NT servers.
1005+
*/
1006+
rc = cifs_open_file(xid, &oparms, &oplock, NULL);
1007+
/*
1008+
* Opening path for writing on non-NT servers is not
1009+
* possible when the read-only attribute is already set.
1010+
* Non-NT server in this case returns -EACCES. For those
1011+
* servers the only possible way how to clear the read-only
1012+
* bit is via SMB_COM_SETATTR command.
1013+
*/
1014+
if (rc == -EACCES &&
1015+
(cinode->cifsAttrs & ATTR_READONLY) &&
1016+
le32_to_cpu(buf->Attributes) != 0 && /* 0 = do not change attrs */
1017+
!(le32_to_cpu(buf->Attributes) & ATTR_READONLY) &&
1018+
!(tcon->ses->capabilities & CAP_NT_SMBS))
1019+
rc = -EOPNOTSUPP;
1020+
}
1021+
1022+
/* Fallback to SMB_COM_SETATTR command when absolutelty needed. */
1023+
if (rc == -EOPNOTSUPP) {
1024+
cifs_dbg(FYI, "calling SetInformation since SetPathInfo for attrs/times not supported by this server\n");
1025+
rc = SMBSetInformation(xid, tcon, full_path,
1026+
buf->Attributes != 0 ? buf->Attributes : cpu_to_le32(cinode->cifsAttrs),
1027+
write_time,
1028+
cifs_sb->local_nls, cifs_sb);
1029+
if (rc == 0)
1030+
cinode->cifsAttrs = le32_to_cpu(buf->Attributes);
1031+
else
1032+
rc = -EACCES;
1033+
goto out;
1034+
}
1035+
9581036
if (rc != 0) {
9591037
if (rc == -EIO)
9601038
rc = -EINVAL;
9611039
goto out;
9621040
}
9631041

9641042
netpid = current->tgid;
1043+
cifs_dbg(FYI, "calling SetFileInfo since SetPathInfo for attrs/times not supported by this server\n");
9651044

9661045
set_via_filehandle:
9671046
rc = CIFSSMBSetFileInfo(xid, tcon, buf, fid.netfid, netpid);
@@ -972,6 +1051,21 @@ smb_set_file_info(struct inode *inode, const char *full_path,
9721051
CIFSSMBClose(xid, tcon, fid.netfid);
9731052
else
9741053
cifsFileInfo_put(open_file);
1054+
1055+
/*
1056+
* Setting the read-only bit is not honered on non-NT servers when done
1057+
* via open-semantics. So for setting it, use SMB_COM_SETATTR command.
1058+
* This command works only after the file is closed, so use it only when
1059+
* operation was called without the filehandle.
1060+
*/
1061+
if (open_file == NULL &&
1062+
!(tcon->ses->capabilities & CAP_NT_SMBS) &&
1063+
le32_to_cpu(buf->Attributes) & ATTR_READONLY) {
1064+
SMBSetInformation(xid, tcon, full_path,
1065+
buf->Attributes,
1066+
0 /* do not change write time */,
1067+
cifs_sb->local_nls, cifs_sb);
1068+
}
9751069
out:
9761070
if (tlink != NULL)
9771071
cifs_put_tlink(tlink);

0 commit comments

Comments
 (0)