s3:smbd: let unix_convert() skip stat() calls for wildcard names
[metze/samba/wip.git] / source3 / smbd / filename.c
index 27f17f96283bbc9318cf75e75c104c93a09da04c..7178f82fcef86e1b5e236fc9e4a76d7b44f29486 100644 (file)
 
 #include "includes.h"
 
-static BOOL scan_directory(connection_struct *conn, const char *path,
-                          char *name, char **found_name);
+static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
+                                 connection_struct *conn,
+                                 const char *orig_path,
+                                 const char *basepath,
+                                 const char *streamname,
+                                 SMB_STRUCT_STAT *pst,
+                                 char **path);
 
 /****************************************************************************
  Mangle the 2nd name and check if it is then equal to the first name.
 ****************************************************************************/
 
-static BOOL mangled_equal(const char *name1,
+static bool mangled_equal(const char *name1,
                        const char *name2,
                        const struct share_params *p)
 {
@@ -50,7 +55,7 @@ static BOOL mangled_equal(const char *name1,
 ****************************************************************************/
 
 static NTSTATUS determine_path_error(const char *name,
-                       BOOL allow_wcard_last_component)
+                       bool allow_wcard_last_component)
 {
        const char *p;
 
@@ -94,8 +99,7 @@ get any fatal errors that should immediately terminate the calling
 SMB processing whilst resolving.
 
 If the saved_last_component != 0, then the unmodified last component
-of the pathname is returned there. This is used in an exceptional
-case in reply_mv (so far). If saved_last_component == 0 then nothing
+of the pathname is returned there. If saved_last_component == 0 then nothing
 is returned there.
 
 If last_component_wcard is true then a MS wildcard was detected and
@@ -107,9 +111,10 @@ stat struct will be filled with zeros (and this can be detected by checking
 for nlinks = 0, which can never be true for any file).
 ****************************************************************************/
 
-NTSTATUS unix_convert(connection_struct *conn,
+NTSTATUS unix_convert(TALLOC_CTX *ctx,
+                       connection_struct *conn,
                        const char *orig_path,
-                       BOOL allow_wcard_last_component,
+                       bool allow_wcard_last_component,
                        char **pp_conv_path,
                        char **pp_saved_last_component,
                        SMB_STRUCT_STAT *pst)
@@ -118,10 +123,12 @@ NTSTATUS unix_convert(connection_struct *conn,
        char *start, *end;
        char *dirpath = NULL;
        char *name = NULL;
-       BOOL component_was_mangled = False;
-       BOOL name_has_wildcard = False;
+       char *stream = NULL;
+       bool component_was_mangled = False;
+       bool name_has_wildcard = False;
+       bool posix_pathnames = false;
        NTSTATUS result;
-       TALLOC_CTX *ctx = talloc_tos();
+       int ret = -1;
 
        SET_STAT_INVALID(*pst);
        *pp_conv_path = NULL;
@@ -187,20 +194,6 @@ NTSTATUS unix_convert(connection_struct *conn,
                return result;
        }
 
-       /*
-        * Ensure saved_last_component is valid even if file exists.
-        */
-
-       if(pp_saved_last_component) {
-               end = strrchr_m(orig_path, '/');
-               if (end) {
-                       *pp_saved_last_component = talloc_strdup(ctx, end + 1);
-               } else {
-                       *pp_saved_last_component = talloc_strdup(ctx,
-                                                       orig_path);
-               }
-       }
-
        if (!(name = talloc_strdup(ctx, orig_path))) {
                DEBUG(0, ("talloc_strdup failed\n"));
                return NT_STATUS_NO_MEMORY;
@@ -220,10 +213,46 @@ NTSTATUS unix_convert(connection_struct *conn,
                strnorm(name, lp_defaultcase(SNUM(conn)));
        }
 
+       /*
+        * Ensure saved_last_component is valid even if file exists.
+        */
+
+       if(pp_saved_last_component) {
+               end = strrchr_m(name, '/');
+               if (end) {
+                       *pp_saved_last_component = talloc_strdup(ctx, end + 1);
+               } else {
+                       *pp_saved_last_component = talloc_strdup(ctx,
+                                                       name);
+               }
+       }
+
+       posix_pathnames = lp_posix_pathnames();
+
+       if (!posix_pathnames) {
+               stream = strchr_m(name, ':');
+
+               if (stream != NULL) {
+                       char *tmp = talloc_strdup(ctx, stream);
+                       if (tmp == NULL) {
+                               TALLOC_FREE(name);
+                               return NT_STATUS_NO_MEMORY;
+                       }
+                       *stream = '\0';
+                       stream = tmp;
+               }
+       }
+
        start = name;
 
-       if(!conn->case_sensitive
-          && stat_cache_lookup(conn, &name, &dirpath, &start, &st)) {
+       /* If we're providing case insentive semantics or
+        * the underlying filesystem is case insensitive,
+        * then a case-normalized hit in the stat-cache is
+        * authoratitive. JRA.
+        */
+
+       if((!conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
+                       stat_cache_lookup(conn, &name, &dirpath, &start, &st)) {
                *pst = st;
                goto done;
        }
@@ -239,29 +268,45 @@ NTSTATUS unix_convert(connection_struct *conn,
                return NT_STATUS_NO_MEMORY;
        }
 
-       /*
-        * stat the name - if it exists then we are all done!
-        */
+       name_has_wildcard = ms_has_wild(name);
 
-       if (SMB_VFS_STAT(conn,name,&st) == 0) {
-               /* Ensure we catch all names with in "/."
-                  this is disallowed under Windows. */
-               const char *p = strstr(name, "/."); /* mb safe. */
-               if (p) {
-                       if (p[2] == '/') {
-                               /* Error code within a pathname. */
-                               result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
-                               goto fail;
-                       } else if (p[2] == '\0') {
-                               /* Error code at the end of a pathname. */
-                               result = NT_STATUS_OBJECT_NAME_INVALID;
-                               goto fail;
+       /* Wildcard not valid anywhere. */
+       if (name_has_wildcard && !allow_wcard_last_component) {
+               result = NT_STATUS_OBJECT_NAME_INVALID;
+               goto fail;
+       }
+
+       if (!name_has_wildcard) {
+               /*
+                * stat the name - if it exists then we are all done!
+                */
+
+               if (posix_pathnames) {
+                       ret = SMB_VFS_LSTAT(conn,name,&st);
+               } else {
+                       ret = SMB_VFS_STAT(conn,name,&st);
+               }
+
+               if (ret == 0) {
+                       /* Ensure we catch all names with in "/."
+                          this is disallowed under Windows. */
+                       const char *p = strstr(name, "/."); /* mb safe. */
+                       if (p) {
+                               if (p[2] == '/') {
+                                       /* Error code within a pathname. */
+                                       result = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+                                       goto fail;
+                               } else if (p[2] == '\0') {
+                                       /* Error code at the end of a pathname. */
+                                       result = NT_STATUS_OBJECT_NAME_INVALID;
+                                       goto fail;
+                               }
                        }
+                       stat_cache_add(orig_path, name, conn->case_sensitive);
+                       DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
+                       *pst = st;
+                       goto done;
                }
-               stat_cache_add(orig_path, name, conn->case_sensitive);
-               DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
-               *pst = st;
-               goto done;
        }
 
        DEBUG(5,("unix_convert begin: name = %s, dirpath = %s, start = %s\n",
@@ -269,10 +314,11 @@ NTSTATUS unix_convert(connection_struct *conn,
 
        /*
         * A special case - if we don't have any mangling chars and are case
-        * sensitive then searching won't help.
+        * sensitive or the underlying filesystem is case insentive then searching
+        * won't help.
         */
 
-       if (conn->case_sensitive &&
+       if ((conn->case_sensitive || !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) &&
                        !mangle_is_mangled(name, conn->params)) {
                goto done;
        }
@@ -338,28 +384,33 @@ NTSTATUS unix_convert(connection_struct *conn,
 
                name_has_wildcard = ms_has_wild(start);
 
-               /* Wildcard not valid anywhere. */
-               if (name_has_wildcard && !allow_wcard_last_component) {
-                       result = NT_STATUS_OBJECT_NAME_INVALID;
-                       goto fail;
-               }
-
                /* Wildcards never valid within a pathname. */
                if (name_has_wildcard && end) {
                        result = NT_STATUS_OBJECT_NAME_INVALID;
                        goto fail;
                }
 
+               if (name_has_wildcard) {
+                       DEBUG(5,("Wildcard %s\n",start));
+                       goto done;
+               }
+
                /*
                 * Check if the name exists up to this point.
                 */
 
-               if (SMB_VFS_STAT(conn,name, &st) == 0) {
+               if (posix_pathnames) {
+                       ret = SMB_VFS_LSTAT(conn,name, &st);
+               } else {
+                       ret = SMB_VFS_STAT(conn,name, &st);
+               }
+
+               if (ret == 0) {
                        /*
                         * It exists. it must either be a directory or this must
                         * be the last part of the path for it to be OK.
                         */
-                       if (end && !(st.st_mode & S_IFDIR)) {
+                       if (end && !S_ISDIR(st.st_ex_mode)) {
                                /*
                                 * An intermediate part of the name isn't
                                 * a directory.
@@ -405,8 +456,9 @@ NTSTATUS unix_convert(connection_struct *conn,
                         */
 
                        if (name_has_wildcard ||
-                           !scan_directory(conn, dirpath,
-                                   start, &found_name)) {
+                           (get_real_filename(conn, dirpath, start,
+                                              talloc_tos(),
+                                              &found_name) == -1)) {
                                char *unmangled;
 
                                if (end) {
@@ -447,8 +499,14 @@ NTSTATUS unix_convert(connection_struct *conn,
                                        goto fail;
                                }
 
-                               /* ENOENT is the only valid error here. */
-                               if (errno != ENOENT) {
+                               /*
+                                * ENOENT/EACCESS are the only valid errors
+                                * here. EACCESS needs handling here for
+                                * "dropboxes", i.e. directories where users
+                                * can only put stuff with permission -wx.
+                                */
+                               if ((errno != 0) && (errno != ENOENT)
+                                   && (errno != EACCES)) {
                                        /*
                                         * ENOTDIR and ELOOP both map to
                                         * NT_STATUS_OBJECT_PATH_NOT_FOUND
@@ -458,8 +516,7 @@ NTSTATUS unix_convert(connection_struct *conn,
                                                        errno == ELOOP) {
                                                result =
                                                NT_STATUS_OBJECT_PATH_NOT_FOUND;
-                                       }
-                                       else {
+                                       } else {
                                                result =
                                                map_nt_error_from_unix(errno);
                                        }
@@ -571,7 +628,13 @@ NTSTATUS unix_convert(connection_struct *conn,
                                 * if it exists. JRA.
                                 */
 
-                               if (SMB_VFS_STAT(conn,name, &st) == 0) {
+                               if (posix_pathnames) {
+                                       ret = SMB_VFS_LSTAT(conn,name, &st);
+                               } else {
+                                       ret = SMB_VFS_STAT(conn,name, &st);
+                               }
+
+                               if (ret == 0) {
                                        *pst = st;
                                } else {
                                        SET_STAT_INVALID(st);
@@ -582,11 +645,19 @@ NTSTATUS unix_convert(connection_struct *conn,
                } /* end else */
 
 #ifdef DEVELOPER
-               if (VALID_STAT(st) &&
-                   get_delete_on_close_flag(vfs_file_id_from_sbuf(conn,
-                                   &st))) {
-                       result = NT_STATUS_DELETE_PENDING;
-                       goto fail;
+               /*
+                * This sucks!
+                * We should never provide different behaviors
+                * depending on DEVELOPER!!!
+                */
+               if (VALID_STAT(st)) {
+                       bool delete_pending;
+                       get_file_infos(vfs_file_id_from_sbuf(conn, &st),
+                                      &delete_pending, NULL);
+                       if (delete_pending) {
+                               result = NT_STATUS_DELETE_PENDING;
+                               goto fail;
+                       }
                }
 #endif
 
@@ -646,6 +717,20 @@ NTSTATUS unix_convert(connection_struct *conn,
        DEBUG(5,("conversion finished %s -> %s\n",orig_path, name));
 
  done:
+       if (stream != NULL) {
+               char *tmp = NULL;
+
+               result = build_stream_path(ctx, conn, orig_path, name, stream,
+                                          pst, &tmp);
+               if (!NT_STATUS_IS_OK(result)) {
+                       goto fail;
+               }
+
+               DEBUG(10, ("build_stream_path returned %s\n", tmp));
+
+               TALLOC_FREE(name);
+               name = tmp;
+       }
        *pp_conv_path = name;
        TALLOC_FREE(dirpath);
        return NT_STATUS_OK;
@@ -702,8 +787,8 @@ NTSTATUS check_name(connection_struct *conn, const char *name)
  This needs to be careful about whether we are case sensitive.
 ****************************************************************************/
 
-static BOOL fname_equal(const char *name1, const char *name2,
-               BOOL case_sensitive)
+static bool fname_equal(const char *name1, const char *name2,
+               bool case_sensitive)
 {
        /* Normal filename handling */
        if (case_sensitive) {
@@ -718,23 +803,30 @@ static BOOL fname_equal(const char *name1, const char *name2,
  If the name looks like a mangled name then try via the mangling functions
 ****************************************************************************/
 
-static BOOL scan_directory(connection_struct *conn, const char *path,
-                          char *name, char **found_name)
+static int get_real_filename_full_scan(connection_struct *conn,
+                                      const char *path, const char *name,
+                                      bool mangled,
+                                      TALLOC_CTX *mem_ctx, char **found_name)
 {
        struct smb_Dir *cur_dir;
        const char *dname;
-       BOOL mangled;
        char *unmangled_name = NULL;
        long curpos;
-       TALLOC_CTX *ctx = talloc_tos();
-
-       mangled = mangle_is_mangled(name, conn->params);
 
        /* handle null paths */
        if ((path == NULL) || (*path == 0)) {
                path = ".";
        }
 
+       /* If we have a case-sensitive filesystem, it doesn't do us any
+        * good to search for a name. If a case variation of the name was
+        * there, then the original stat(2) would have found it.
+        */
+       if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
+               errno = ENOENT;
+               return -1;
+       }
+
        /*
         * The incoming name can be mangled, and if we de-mangle it
         * here it will not compare correctly against the filename (name2)
@@ -751,25 +843,25 @@ static BOOL scan_directory(connection_struct *conn, const char *path,
         */
 
        if (mangled && !conn->case_sensitive) {
-               mangled = !mangle_lookup_name_from_8_3(ctx,
-                                               name,
-                                               &unmangled_name,
-                                               conn->params);
-               if (mangled) {
+               mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name,
+                                                      &unmangled_name,
+                                                      conn->params);
+               if (!mangled) {
+                       /* Name is now unmangled. */
                        name = unmangled_name;
                }
        }
 
        /* open the directory */
-       if (!(cur_dir = OpenDir(conn, path, NULL, 0))) {
+       if (!(cur_dir = OpenDir(talloc_tos(), conn, path, NULL, 0))) {
                DEBUG(3,("scan dir didn't open dir [%s]\n",path));
                TALLOC_FREE(unmangled_name);
-               return(False);
+               return -1;
        }
 
        /* now scan for matching names */
        curpos = 0;
-       while ((dname = ReadDirName(cur_dir, &curpos))) {
+       while ((dname = ReadDirName(cur_dir, &curpos, NULL))) {
 
                /* Is it dot or dot dot. */
                if (ISDOT(dname) || ISDOTDOT(dname)) {
@@ -790,19 +882,141 @@ static BOOL scan_directory(connection_struct *conn, const char *path,
                if ((mangled && mangled_equal(name,dname,conn->params)) ||
                        fname_equal(name, dname, conn->case_sensitive)) {
                        /* we've found the file, change it's name and return */
-                       *found_name = talloc_strdup(ctx,dname);
+                       *found_name = talloc_strdup(mem_ctx, dname);
                        TALLOC_FREE(unmangled_name);
-                       CloseDir(cur_dir);
+                       TALLOC_FREE(cur_dir);
                        if (!*found_name) {
                                errno = ENOMEM;
-                               return False;
+                               return -1;
                        }
-                       return(True);
+                       return 0;
                }
        }
 
        TALLOC_FREE(unmangled_name);
-       CloseDir(cur_dir);
+       TALLOC_FREE(cur_dir);
        errno = ENOENT;
-       return False;
+       return -1;
+}
+
+/****************************************************************************
+ Wrapper around the vfs get_real_filename and the full directory scan
+ fallback.
+****************************************************************************/
+
+int get_real_filename(connection_struct *conn, const char *path,
+                     const char *name, TALLOC_CTX *mem_ctx,
+                     char **found_name)
+{
+       int ret;
+       bool mangled;
+
+       mangled = mangle_is_mangled(name, conn->params);
+
+       if (mangled) {
+               return get_real_filename_full_scan(conn, path, name, mangled,
+                                                  mem_ctx, found_name);
+       }
+
+       /* Try the vfs first to take advantage of case-insensitive stat. */
+       ret = SMB_VFS_GET_REAL_FILENAME(conn, path, name, mem_ctx, found_name);
+
+       /*
+        * If the case-insensitive stat was successful, or returned an error
+        * other than EOPNOTSUPP then there is no need to fall back on the
+        * full directory scan.
+        */
+       if (ret == 0 || (ret == -1 && errno != EOPNOTSUPP)) {
+               return ret;
+       }
+
+       return get_real_filename_full_scan(conn, path, name, mangled, mem_ctx,
+                                          found_name);
+}
+
+static NTSTATUS build_stream_path(TALLOC_CTX *mem_ctx,
+                                 connection_struct *conn,
+                                 const char *orig_path,
+                                 const char *basepath,
+                                 const char *streamname,
+                                 SMB_STRUCT_STAT *pst,
+                                 char **path)
+{
+       SMB_STRUCT_STAT st;
+       char *result = NULL;
+       NTSTATUS status;
+       unsigned int i, num_streams;
+       struct stream_struct *streams = NULL;
+
+       result = talloc_asprintf(mem_ctx, "%s%s", basepath, streamname);
+       if (result == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+
+       if (SMB_VFS_STAT(conn, result, &st) == 0) {
+               *pst = st;
+               *path = result;
+               return NT_STATUS_OK;
+       }
+
+       if (errno != ENOENT) {
+               status = map_nt_error_from_unix(errno);
+               DEBUG(10, ("vfs_stat failed: %s\n", nt_errstr(status)));
+               goto fail;
+       }
+
+       status = SMB_VFS_STREAMINFO(conn, NULL, basepath, mem_ctx,
+                                   &num_streams, &streams);
+
+       if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+               SET_STAT_INVALID(*pst);
+               *path = result;
+               return NT_STATUS_OK;
+       }
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DEBUG(10, ("vfs_streaminfo failed: %s\n", nt_errstr(status)));
+               goto fail;
+       }
+
+       for (i=0; i<num_streams; i++) {
+               DEBUG(10, ("comparing [%s] and [%s]: ",
+                          streamname, streams[i].name));
+               if (fname_equal(streamname, streams[i].name,
+                               conn->case_sensitive)) {
+                       DEBUGADD(10, ("equal\n"));
+                       break;
+               }
+               DEBUGADD(10, ("not equal\n"));
+       }
+
+       if (i == num_streams) {
+               SET_STAT_INVALID(*pst);
+               *path = result;
+               TALLOC_FREE(streams);
+               return NT_STATUS_OK;
+       }
+
+       TALLOC_FREE(result);
+
+       result = talloc_asprintf(mem_ctx, "%s%s", basepath, streams[i].name);
+       if (result == NULL) {
+               status = NT_STATUS_NO_MEMORY;
+               goto fail;
+       }
+
+       SET_STAT_INVALID(*pst);
+
+       if (SMB_VFS_STAT(conn, result, pst) == 0) {
+               stat_cache_add(orig_path, result, conn->case_sensitive);
+       }
+
+       *path = result;
+       TALLOC_FREE(streams);
+       return NT_STATUS_OK;
+
+ fail:
+       TALLOC_FREE(result);
+       TALLOC_FREE(streams);
+       return status;
 }