diff options
Diffstat (limited to 'source3/modules/onefs_streams.c')
-rw-r--r-- | source3/modules/onefs_streams.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/source3/modules/onefs_streams.c b/source3/modules/onefs_streams.c new file mode 100644 index 0000000000..55ce11ecf5 --- /dev/null +++ b/source3/modules/onefs_streams.c @@ -0,0 +1,580 @@ +/* + * Unix SMB/CIFS implementation. + * + * Support for OneFS Alternate Data Streams + * + * Copyright (C) Tim Prouty, 2008 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "onefs.h" +#include <sys/isi_enc.h> + +/* + * OneFS stores streams without the explicit :$DATA at the end, so this strips + * it off. All onefs_stream functions must call through this instead of + * split_ntfs_stream_name directly. + */ +NTSTATUS onefs_split_ntfs_stream_name(TALLOC_CTX *mem_ctx, const char *fname, + char **pbase, char **pstream) +{ + NTSTATUS status; + char *stream; + + status = split_ntfs_stream_name(mem_ctx, fname, pbase, pstream); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Default $DATA stream. */ + if (pstream == NULL || *pstream == NULL) { + return NT_STATUS_OK; + } + + /* Strip off the $DATA. */ + stream = strrchr_m(*pstream, ':'); + SMB_ASSERT(stream); + stream[0] = '\0'; + + return NT_STATUS_OK; +} + +int onefs_close(vfs_handle_struct *handle, struct files_struct *fsp) +{ + int ret2, ret = 0; + + if (fsp->base_fsp) { + ret = SMB_VFS_NEXT_CLOSE(handle, fsp->base_fsp); + } + ret2 = SMB_VFS_NEXT_CLOSE(handle, fsp); + + return ret ? ret : ret2; +} + +/* + * Get the ADS directory fd for a file. + */ +static int get_stream_dir_fd(connection_struct *conn, const char *base, + int *base_fdp) +{ + int base_fd; + int dir_fd; + int saved_errno; + + /* If a valid base_fdp was given, use it. */ + if (base_fdp && *base_fdp >= 0) { + base_fd = *base_fdp; + } else { + base_fd = onefs_sys_create_file(conn, + -1, + base, + 0, + 0, + 0, + 0, + 0, + 0, + INTERNAL_OPEN_ONLY, + 0, + NULL, + 0, + NULL); + if (base_fd < 0) { + return -1; + } + } + + /* Open the ADS directory. */ + dir_fd = onefs_sys_create_file(conn, + base_fd, + ".", + 0, + FILE_READ_DATA, + 0, + 0, + 0, + 0, + INTERNAL_OPEN_ONLY, + 0, + NULL, + 0, + NULL); + + /* Close base_fd if it's not need or on error. */ + if (!base_fdp || dir_fd < 0) { + saved_errno = errno; + close(base_fd); + errno = saved_errno; + } + + /* Set the out base_fdp if successful and it was requested. */ + if (base_fdp && dir_fd >= 0) { + *base_fdp = base_fd; + } + + return dir_fd; +} + +int onefs_rename(vfs_handle_struct *handle, const char *oldname, + const char *newname) +{ + TALLOC_CTX *frame = NULL; + int ret = -1; + int dir_fd, saved_errno; + bool old_is_stream; + bool new_is_stream; + char *obase = NULL; + char *osname = NULL; + char *nbase = NULL; + char *nsname = NULL; + + old_is_stream = is_ntfs_stream_name(oldname); + new_is_stream = is_ntfs_stream_name(newname); + + if (!old_is_stream && !new_is_stream) { + return SMB_VFS_NEXT_RENAME(handle, oldname, newname); + } + + frame = talloc_stackframe(); + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), + oldname, &obase, + &osname))) { + errno = ENOMEM; + goto done; + } + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), + newname, &nbase, + &nsname))) { + errno = ENOMEM; + goto done; + } + + dir_fd = get_stream_dir_fd(handle->conn, obase, NULL); + if (dir_fd < -1) { + goto done; + } + + DEBUG(8,("onefs_rename called for %s : %s => %s : %s\n", + obase, osname, nbase, nsname)); + + /* Handle rename of stream to default stream specially. */ + if (nsname == NULL) { + ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, AT_FDCWD, + nbase, ENC_DEFAULT); + } else { + ret = enc_renameat(dir_fd, osname, ENC_DEFAULT, dir_fd, nsname, + ENC_DEFAULT); + } + + done: + saved_errno = errno; + close(dir_fd); + errno = saved_errno; + TALLOC_FREE(frame); + return ret; +} + +/* + * Merge a base file's sbuf into the a streams's sbuf. + */ +static void merge_stat(SMB_STRUCT_STAT *stream_sbuf, + const SMB_STRUCT_STAT *base_sbuf) +{ + int dos_flags = (UF_DOS_NOINDEX | UF_DOS_ARCHIVE | + UF_DOS_HIDDEN | UF_DOS_RO | UF_DOS_SYSTEM); + stream_sbuf->st_mtime = base_sbuf->st_mtime; + stream_sbuf->st_ctime = base_sbuf->st_ctime; + stream_sbuf->st_atime = base_sbuf->st_atime; + stream_sbuf->st_flags &= ~dos_flags; + stream_sbuf->st_flags |= base_sbuf->st_flags & dos_flags; +} + +static int stat_stream(vfs_handle_struct *handle, const char *base, + const char *stream, SMB_STRUCT_STAT *sbuf, int flags) +{ + SMB_STRUCT_STAT base_sbuf; + int base_fd = -1, dir_fd, ret, saved_errno; + + dir_fd = get_stream_dir_fd(handle->conn, base, &base_fd); + if (dir_fd < 0) { + return -1; + } + + /* Stat the stream. */ + ret = enc_fstatat(dir_fd, stream, ENC_DEFAULT, sbuf, flags); + if (ret != -1) { + /* Now stat the base file and merge the results. */ + ret = sys_fstat(base_fd, &base_sbuf); + if (ret != -1) { + merge_stat(sbuf, &base_sbuf); + } + } + + saved_errno = errno; + close(dir_fd); + close(base_fd); + errno = saved_errno; + return ret; +} + +int onefs_stat(vfs_handle_struct *handle, const char *path, + SMB_STRUCT_STAT *sbuf) +{ + char *base = NULL; + char *stream = NULL; + + if (!is_ntfs_stream_name(path)) { + return SMB_VFS_NEXT_STAT(handle, path, sbuf); + } + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path, + &base, &stream))) { + DEBUG(10, ("onefs_split_ntfs_stream_name failed\n")); + errno = ENOMEM; + return -1; + } + + /* If it's the ::$DATA stream just stat the base file name. */ + if (!stream) { + return SMB_VFS_NEXT_STAT(handle, base, sbuf); + } + + return stat_stream(handle, base, stream, sbuf, 0); +} + +int onefs_fstat(vfs_handle_struct *handle, struct files_struct *fsp, + SMB_STRUCT_STAT *sbuf) +{ + SMB_STRUCT_STAT base_sbuf; + int ret; + + /* Stat the stream, by calling next_fstat on the stream's fd. */ + ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf); + if (ret == -1) { + return ret; + } + + /* Stat the base file and merge the results. */ + if (fsp != NULL && fsp->base_fsp != NULL) { + ret = sys_fstat(fsp->base_fsp->fh->fd, &base_sbuf); + if (ret != -1) { + merge_stat(sbuf, &base_sbuf); + } + } + + return ret; +} + +int onefs_lstat(vfs_handle_struct *handle, const char *path, + SMB_STRUCT_STAT *sbuf) +{ + char *base = NULL; + char *stream = NULL; + + if (!is_ntfs_stream_name(path)) { + return SMB_VFS_NEXT_LSTAT(handle, path, sbuf); + } + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path, + &base, &stream))) { + DEBUG(10, ("onefs_split_ntfs_stream_name failed\n")); + errno = ENOMEM; + return -1; + } + + /* If it's the ::$DATA stream just stat the base file name. */ + if (!stream) { + return SMB_VFS_NEXT_LSTAT(handle, base, sbuf); + } + + return stat_stream(handle, base, stream, sbuf, AT_SYMLINK_NOFOLLOW); +} + +int onefs_unlink(vfs_handle_struct *handle, const char *path) +{ + char *base = NULL; + char *stream = NULL; + int dir_fd, ret, saved_errno; + + if (!is_ntfs_stream_name(path)) { + return SMB_VFS_NEXT_UNLINK(handle, path); + } + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path, + &base, &stream))) { + DEBUG(10, ("onefs_split_ntfs_stream_name failed\n")); + errno = ENOMEM; + return -1; + } + + /* If it's the ::$DATA stream just unlink the base file name. */ + if (!stream) { + return SMB_VFS_NEXT_UNLINK(handle, base); + } + + dir_fd = get_stream_dir_fd(handle->conn, base, NULL); + if (dir_fd < 0) { + return -1; + } + + ret = enc_unlinkat(dir_fd, stream, ENC_DEFAULT, 0); + + saved_errno = errno; + close(dir_fd); + errno = saved_errno; + return ret; +} + +int onefs_chflags(vfs_handle_struct *handle, const char *path, + unsigned int flags) +{ + char *base = NULL; + char *stream = NULL; + + if (!NT_STATUS_IS_OK(onefs_split_ntfs_stream_name(talloc_tos(), path, + &base, &stream))) { + DEBUG(10, ("onefs_split_ntfs_stream_name failed\n")); + errno = ENOMEM; + return -1; + } + + /* + * Only set the attributes on the base file. ifs_createfile handles + * file creation attribute semantics. + */ + return SMB_VFS_NEXT_CHFLAGS(handle, base, flags); +} + +/* + * Streaminfo enumeration functionality + */ +struct streaminfo_state { + TALLOC_CTX *mem_ctx; + vfs_handle_struct *handle; + unsigned int num_streams; + struct stream_struct *streams; + NTSTATUS status; +}; + +static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams, + struct stream_struct **streams, + const char *name, SMB_OFF_T size, + SMB_OFF_T alloc_size) +{ + struct stream_struct *tmp; + + tmp = TALLOC_REALLOC_ARRAY(mem_ctx, *streams, struct stream_struct, + (*num_streams)+1); + if (tmp == NULL) { + return false; + } + + tmp[*num_streams].name = talloc_asprintf(mem_ctx, ":%s:%s", name, + "$DATA"); + if (tmp[*num_streams].name == NULL) { + return false; + } + + tmp[*num_streams].size = size; + tmp[*num_streams].alloc_size = alloc_size; + + *streams = tmp; + *num_streams += 1; + return true; +} + +static NTSTATUS walk_onefs_streams(connection_struct *conn, files_struct *fsp, + const char *fname, + struct streaminfo_state *state, + SMB_STRUCT_STAT *base_sbuf) +{ + NTSTATUS status = NT_STATUS_OK; + bool opened_base_fd = false; + int base_fd = -1; + int dir_fd = -1; + int stream_fd = -1; + int ret; + SMB_STRUCT_DIR *dirp = NULL; + SMB_STRUCT_DIRENT *dp = NULL; + files_struct fake_fs; + struct fd_handle fake_fh; + SMB_STRUCT_STAT stream_sbuf; + + ZERO_STRUCT(fake_fh); + ZERO_STRUCT(fake_fs); + + /* If the base file is already open, use its fd. */ + if ((fsp != NULL) && (fsp->fh->fd != -1)) { + base_fd = fsp->fh->fd; + } else { + opened_base_fd = true; + } + + dir_fd = get_stream_dir_fd(conn, fname, &base_fd); + if (dir_fd < 0) { + return map_nt_error_from_unix(errno); + } + + /* Open the ADS directory. */ + if ((dirp = fdopendir(dir_fd)) == NULL) { + DEBUG(0, ("Error on opendir %s. errno=%d (%s)\n", + fname, errno, strerror(errno))); + status = map_nt_error_from_unix(errno); + goto out; + } + + fake_fs.conn = conn; + fake_fs.fh = &fake_fh; + fake_fs.fsp_name = SMB_STRDUP(fname); + + /* Iterate over the streams in the ADS directory. */ + while ((dp = SMB_VFS_READDIR(conn, dirp)) != NULL) { + /* Skip the "." and ".." entries */ + if ((strcmp(dp->d_name, ".") == 0) || + (strcmp(dp->d_name, "..") == 0)) + continue; + + /* Open actual stream */ + if ((stream_fd = onefs_sys_create_file(conn, + base_fd, + dp->d_name, + 0, + 0, + 0, + 0, + 0, + 0, + INTERNAL_OPEN_ONLY, + 0, + NULL, + 0, + NULL)) == -1) { + DEBUG(0, ("Error opening stream %s:%s. " + "errno=%d (%s)\n", fname, dp->d_name, errno, + strerror(errno))); + continue; + } + + /* Figure out the stat info. */ + fake_fh.fd = stream_fd; + ret = SMB_VFS_FSTAT(&fake_fs, &stream_sbuf); + close(stream_fd); + + if (ret) { + DEBUG(0, ("Error fstating stream %s:%s. " + "errno=%d (%s)\n", fname, dp->d_name, errno, + strerror(errno))); + continue; + } + + merge_stat(&stream_sbuf, base_sbuf); + + if (!add_one_stream(state->mem_ctx, + &state->num_streams, &state->streams, + dp->d_name, stream_sbuf.st_size, + get_allocation_size(conn, NULL, + &stream_sbuf))) { + state->status = NT_STATUS_NO_MEMORY; + break; + } + } + +out: + /* Cleanup everything that was opened. */ + if (dirp != NULL) { + SMB_VFS_CLOSEDIR(conn, dirp); + } + if (dir_fd >= 0) { + close(dir_fd); + } + if (opened_base_fd) { + SMB_ASSERT(base_fd >= 0); + close(base_fd); + } + + SAFE_FREE(fake_fs.fsp_name); + return status; +} + +NTSTATUS onefs_streaminfo(vfs_handle_struct *handle, + struct files_struct *fsp, + const char *fname, + TALLOC_CTX *mem_ctx, + unsigned int *num_streams, + struct stream_struct **streams) +{ + SMB_STRUCT_STAT sbuf; + int ret; + NTSTATUS status; + struct streaminfo_state state; + + /* Get a valid stat. */ + if ((fsp != NULL) && (fsp->fh->fd != -1)) { + if (is_ntfs_stream_name(fsp->fsp_name)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_FSTAT(fsp, &sbuf); + } else { + if (is_ntfs_stream_name(fname)) { + return NT_STATUS_INVALID_PARAMETER; + } + ret = SMB_VFS_STAT(handle->conn, fname, &sbuf); + } + + if (ret == -1) { + return map_nt_error_from_unix(errno); + } + + state.streams = NULL; + state.num_streams = 0; + + /* Add the default stream. */ + if (S_ISREG(sbuf.st_mode)) { + if (!add_one_stream(mem_ctx, + &state.num_streams, &state.streams, + "", sbuf.st_size, + get_allocation_size(handle->conn, fsp, + &sbuf))) { + return NT_STATUS_NO_MEMORY; + } + } + + state.mem_ctx = mem_ctx; + state.handle = handle; + state.status = NT_STATUS_OK; + + /* If there are more streams, add them too. */ + if (sbuf.st_flags & UF_HASADS) { + + status = walk_onefs_streams(handle->conn, fsp, fname, + &state, &sbuf); + + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(state.streams); + return status; + } + + if (!NT_STATUS_IS_OK(state.status)) { + TALLOC_FREE(state.streams); + return state.status; + } + } + + *num_streams = state.num_streams; + *streams = state.streams; + return NT_STATUS_OK; +} |