Make us pass RAW-CHKPATH with a case sensitive share.
authorJeremy Allison <jra@samba.org>
Wed, 25 Nov 2009 21:17:56 +0000 (13:17 -0800)
committerJeremy Allison <jra@samba.org>
Wed, 25 Nov 2009 21:17:56 +0000 (13:17 -0800)
I know Volker will look at this closely so here's the explaination :-).
Originally on a case-sensitive share we simply did a stat (or lstat)
call and returned success of fail based on the result. However this
failed to take account of incoming paths with a wildcard (which must
always fail, and with different error messages depending on whether
the wildcard is the last component or in the path). Also it failed
to take account of a stat fail with ENOENT due to a missing component
of the path as the last component (which is ok as it could be a new
file)  or if the ENOENT was due to the missing component within
the path (not the last component) - which must return the correct
error. What this means is that with "case sensitive = yes" we do
one more talloc call (to get the parent directory) and one more
stat call (on the parent directory) in the case where the stat
call fails. I think this is an acceptable overhead to enable
case sensitive shares to return the correct error messages for
applications. Volker please examine carefully :-).
Jeremy.

source3/smbd/filename.c

index 16e36312bbf78fa472f36ab6e9f6a05f55310086..ab79dfd9269c48eac05b545fdc3e4bb103cf4e35 100644 (file)
@@ -80,6 +80,24 @@ static NTSTATUS determine_path_error(const char *name,
        }
 }
 
+static NTSTATUS check_for_dot_component(const struct smb_filename *smb_fname)
+{
+       /* Ensure we catch all names with in "/."
+          this is disallowed under Windows and
+          in POSIX they've already been removed. */
+       const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
+       if (p) {
+               if (p[2] == '/') {
+                       /* Error code within a pathname. */
+                       return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+               } else if (p[2] == '\0') {
+                       /* Error code at the end of a pathname. */
+                       return NT_STATUS_OBJECT_NAME_INVALID;
+               }
+       }
+       return NT_STATUS_OK;
+}
+
 /****************************************************************************
 This routine is called to convert names from the dos namespace to unix
 namespace. It needs to handle any case conversions, mangling, format changes,
@@ -294,52 +312,103 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
        }
 
        /*
-        * stat the name - if it exists then we can add the stream back (if
-        * there was one) and be done!
+        * If we have a wildcard we must walk the path to
+        * find where the error is, even if case sensitive
+        * is true.
         */
 
-       if (posix_pathnames) {
-               ret = SMB_VFS_LSTAT(conn, smb_fname);
-       } else {
-               ret = SMB_VFS_STAT(conn, smb_fname);
+       name_has_wildcard = ms_has_wild(smb_fname->base_name);
+       if (name_has_wildcard && !allow_wcard_last_component) {
+               /* Wildcard not valid anywhere. */
+               status = NT_STATUS_OBJECT_NAME_INVALID;
+               goto fail;
        }
 
-       if (ret == 0) {
-               /* Ensure we catch all names with in "/."
-                  this is disallowed under Windows. */
-               const char *p = strstr(smb_fname->base_name, "/."); /*mb safe*/
-               if (p) {
-                       if (p[2] == '/') {
-                               /* Error code within a pathname. */
-                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
-                               goto fail;
-                       } else if (p[2] == '\0') {
-                               /* Error code at the end of a pathname. */
-                               status = NT_STATUS_OBJECT_NAME_INVALID;
+       DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
+                smb_fname->base_name, dirpath, start));
+
+       if (!name_has_wildcard) {
+               /*
+                * stat the name - if it exists then we can add the stream back (if
+                * there was one) and be done!
+                */
+
+               if (posix_pathnames) {
+                       ret = SMB_VFS_LSTAT(conn, smb_fname);
+               } else {
+                       ret = SMB_VFS_STAT(conn, smb_fname);
+               }
+
+               if (ret == 0) {
+                       status = check_for_dot_component(smb_fname);
+                       if (!NT_STATUS_IS_OK(status)) {
                                goto fail;
                        }
+                       /* Add the path (not including the stream) to the cache. */
+                       stat_cache_add(orig_path, smb_fname->base_name,
+                                      conn->case_sensitive);
+                       DEBUG(5,("conversion of base_name finished %s -> %s\n",
+                                orig_path, smb_fname->base_name));
+                       goto done;
                }
-               /* Add the path (not including the stream) to the cache. */
-               stat_cache_add(orig_path, smb_fname->base_name,
-                              conn->case_sensitive);
-               DEBUG(5,("conversion of base_name finished %s -> %s\n",
-                        orig_path, smb_fname->base_name));
-               goto done;
-       }
 
-       DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
-                smb_fname->base_name, dirpath, start));
+               /*
+                * A special case - if we don't have any wildcards or mangling chars and are case
+                * sensitive or the underlying filesystem is case insentive then searching
+                * won't help.
+                */
 
-       /*
-        * A special case - if we don't have any mangling chars and are case
-        * sensitive or the underlying filesystem is case insentive then searching
-        * won't help.
-        */
+               if ((conn->case_sensitive || !(conn->fs_capabilities &
+                                       FILE_CASE_SENSITIVE_SEARCH)) &&
+                               !mangle_is_mangled(smb_fname->base_name, conn->params)) {
 
-       if ((conn->case_sensitive || !(conn->fs_capabilities &
-                                      FILE_CASE_SENSITIVE_SEARCH)) &&
-           !mangle_is_mangled(smb_fname->base_name, conn->params)) {
-               goto done;
+                       status = check_for_dot_component(smb_fname);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               goto fail;
+                       }
+
+                       /*
+                        * The stat failed. Could be ok as it could be
+                        * a new file.
+                        */
+
+                       if (errno == ENOTDIR || errno == ELOOP) {
+                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                               goto fail;
+                       } else if (errno == ENOENT) {
+                               /*
+                                * Was it a missing last component ?
+                                * or a missing intermediate component ?
+                                */
+                               struct smb_filename parent_fname;
+                               ZERO_STRUCT(parent_fname);
+                               if (!parent_dirname(ctx, smb_fname->base_name,
+                                                       &parent_fname.base_name,
+                                                       NULL)) {
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto fail;
+                               }
+                               if (posix_pathnames) {
+                                       ret = SMB_VFS_LSTAT(conn, &parent_fname);
+                               } else {
+                                       ret = SMB_VFS_STAT(conn, &parent_fname);
+                               }
+                               if (ret == -1) {
+                                       if (errno == ENOTDIR ||
+                                                       errno == ENOENT ||
+                                                       errno == ELOOP) {
+                                               status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                                               goto fail;
+                                       }
+                               }
+                               /*
+                                * Missing last component is ok - new file.
+                                * Also deal with permission denied elsewhere.
+                                * Just drop out to done.
+                                */
+                               goto done;
+                       }
+               }
        }
 
        /*
@@ -404,12 +473,6 @@ NTSTATUS unix_convert(TALLOC_CTX *ctx,
 
                name_has_wildcard = ms_has_wild(start);
 
-               /* Wildcard not valid anywhere. */
-               if (name_has_wildcard && !allow_wcard_last_component) {
-                       status = NT_STATUS_OBJECT_NAME_INVALID;
-                       goto fail;
-               }
-
                /* Wildcards never valid within a pathname. */
                if (name_has_wildcard && end) {
                        status = NT_STATUS_OBJECT_NAME_INVALID;