/** * xrdp: A Remote Desktop Protocol server. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This file implements the interface in chansrv_fuse_fs.h */ #include #include #include #include #include #include #if defined(HAVE_CONFIG_H) #include #endif #include "os_calls.h" #include "chansrv_xfs.h" /* * Skip this module if FUSE is not supported. A standards-compliant C * translation unit must contain at least one declaration (C99:6.9), and we've * fulfilled that requirement by this stage. */ #ifdef XRDP_FUSE #define INODE_TABLE_ALLOCATION_INITIAL 4096 #define INODE_TABLE_ALLOCATION_GRANULARITY 100 /* inum of the delete pending directory */ #define DELETE_PENDING_ID 2 /* * A double-linked list of inodes, sorted by inum * * The elements in the list are sorted in increasing inum order, as this * allows a directory enumeration to be easily resumed if elements * are removed or added. See xfs_readdir() for details on this. */ typedef struct xfs_inode_all XFS_INODE_ALL; typedef struct xfs_list { XFS_INODE_ALL *begin; XFS_INODE_ALL *end; } XFS_LIST; /* * A complete inode, including the private elements used by the * implementation */ typedef struct xfs_inode_all { XFS_INODE pub; /* Public elements */ /* * Directory linkage elements * * Because we don't support hard-linking, elements can be stored in * one and only one directory:- */ struct xfs_inode_all *parent; /* Parent inode */ struct xfs_inode_all *next; /* Next entry in parent */ struct xfs_inode_all *previous; /* Previous entry in parent */ XFS_LIST dir; /* Directory only - children */ /* * Other private elements */ unsigned int open_count; /* Regular files only */ } XFS_INODE_ALL; /* the xrdp file system in memory * * inode_table allows for O(1) access to any file based on the inum. * Index 0 is unused, so we can use an inode of zero for * an invalid inode, and avoid off-by-one errors index * 1 is our '.' directory. * 2 is the delete pending directory, where we can place * inodes with a positive open count which are * deleted. * free_list List of free inode numbers. Allows for O(1) access to * a free node, provided the free list is not empty. */ struct xfs_fs { XFS_INODE_ALL **inode_table; /* a table of entries; can grow. */ fuse_ino_t *free_list; /* Free inodes */ unsigned int inode_count; /* Current number of inodes */ unsigned int free_count; /* Size of free_list */ unsigned int generation; /* Changes when an inode is deleted */ }; /* A directory handle * * inum inum of the directory being scanned * generation Generation of the inum we opened */ struct xfs_dir_handle { fuse_ino_t inum; tui32 generation; }; /* module based logging */ #define LOG_ERROR 0 #define LOG_INFO 1 #define LOG_DEBUG 2 #ifndef LOG_LEVEL #define LOG_LEVEL LOG_ERROR #endif #define log_error(_params...) \ { \ g_write("[%10.10u]: XFS %s: %d : ERROR: ", \ g_time3(), __func__, __LINE__); \ g_writeln (_params); \ } #define log_always(_params...) \ { \ g_write("[%10.10u]: XFS %s: %d : ALWAYS: ", \ g_time3(), __func__, __LINE__); \ g_writeln (_params); \ } #define log_info(_params...) \ { \ if (LOG_INFO <= LOG_LEVEL) \ { \ g_write("[%10.10u]: XFS %s: %d : ", \ g_time3(), __func__, __LINE__); \ g_writeln (_params); \ } \ } #define log_debug(_params...) \ { \ if (LOG_DEBUG <= LOG_LEVEL) \ { \ g_write("[%10.10u]: XFS %s: %d : ", \ g_time3(), __func__, __LINE__); \ g_writeln (_params); \ } \ } /* ------------------------------------------------------------------------ */ static int grow_xfs(struct xfs_fs *xfs, unsigned int extra_inodes) { int result = 0; unsigned int new_count = xfs->inode_count + extra_inodes; XFS_INODE_ALL **new_table; fuse_ino_t *new_free_list; new_table = (XFS_INODE_ALL **) realloc(xfs->inode_table, new_count * sizeof(new_table[0])); if (new_table != NULL) { unsigned int i; for (i = xfs->inode_count ; i < new_count ; ++i) { new_table[i] = NULL; } xfs->inode_table = new_table; new_free_list = (fuse_ino_t *) realloc(xfs->free_list, new_count * sizeof(new_free_list[0])); if (new_free_list) { /* Add the new inodes in to the new_free_list, so the lowest * number is allocated first */ i = new_count; while (i > xfs->inode_count) { new_free_list[xfs->free_count++] = --i; } xfs->free_list = new_free_list; xfs->inode_count = new_count; result = 1; } } return result; } /* ------------------------------------------------------------------------ */ static void add_inode_to_list(XFS_LIST *list, XFS_INODE_ALL *xino) { fuse_ino_t inum = xino->pub.inum; /* Find the element we need to insert after */ XFS_INODE_ALL *predecessor = list->end; while (predecessor != NULL && predecessor->pub.inum > inum) { predecessor = predecessor->previous; } if (predecessor == NULL) { /* Inserting at the beginning */ /* Set up links in node */ xino->next = list->begin; xino->previous = NULL; /* Set up back-link to node */ if (list->begin == NULL) { /* We are the last node */ list->end = xino; } else { list->begin->previous = xino; } /* Set up forward-link to node */ list->begin = xino; } else { /* Set up links in node */ xino->next = predecessor->next; xino->previous = predecessor; /* Set up back-link to node */ if (predecessor->next == NULL) { list->end = xino; } else { predecessor->next->previous = xino; } /* Set up forward-link to node */ predecessor->next = xino; } } /* ------------------------------------------------------------------------ */ static void remove_inode_from_list(XFS_LIST *list, XFS_INODE_ALL *xino) { if (xino->previous == NULL) { /* First element */ list->begin = xino->next; } else { xino->previous->next = xino->next; } if (xino->next == NULL) { /* Last element */ list->end = xino->previous; } else { xino->next->previous = xino->previous; } } /* ------------------------------------------------------------------------ */ static void link_inode_into_directory_node(XFS_INODE_ALL *dinode, XFS_INODE_ALL *xino) { xino->parent = dinode; add_inode_to_list(&dinode->dir, xino); } /* ------------------------------------------------------------------------ */ static void unlink_inode_from_parent(XFS_INODE_ALL *xino) { remove_inode_from_list(&xino->parent->dir, xino); xino->next = NULL; xino->previous = NULL; xino->parent = NULL; } /* ------------------------------------------------------------------------ */ struct xfs_fs * xfs_create_xfs_fs(mode_t umask, uid_t uid, gid_t gid) { struct xfs_fs *xfs = g_new0(struct xfs_fs, 1); XFS_INODE_ALL *xino1 = NULL; XFS_INODE_ALL *xino2 = NULL; if (xfs != NULL) { xfs->inode_count = 0; xfs->free_count = 0; xfs->inode_table = NULL; xfs->free_list = NULL; xfs->generation = 1; if (!grow_xfs(xfs, INODE_TABLE_ALLOCATION_INITIAL) || (xino1 = g_new0(XFS_INODE_ALL, 1)) == NULL || (xino2 = g_new0(XFS_INODE_ALL, 1)) == NULL) { free(xino1); free(xino2); xfs_delete_xfs_fs(xfs); xfs = NULL; } else { /* * The use of grow_xfs to allocate the inode table will make * inodes 0, 1 (FUSE_ROOT_ID) and 2 (DELETE_PENDING_ID) the first * available free inodes. We can ignore these */ xfs->free_count -= 3; xfs->inode_table[0] = NULL; xfs->inode_table[FUSE_ROOT_ID] = xino1; xfs->inode_table[DELETE_PENDING_ID] = xino2; xino1->pub.inum = FUSE_ROOT_ID; xino1->pub.mode = (S_IFDIR | 0777) & ~umask; xino1->pub.uid = uid; xino1->pub.gid = gid; xino1->pub.size = 0; xino1->pub.atime = time(0); xino1->pub.mtime = xino1->pub.atime; xino1->pub.ctime = xino1->pub.atime; strcpy(xino1->pub.name, "."); xino1->pub.generation = xfs->generation; xino1->pub.is_redirected = 0; xino1->pub.device_id = 0; /* * FUSE_ROOT_ID has no parent rather than being a parent * of itself. This is intentional */ xino1->parent = NULL; xino1->next = NULL; xino1->previous = NULL; xino1->dir.begin = NULL; xino1->dir.end = NULL; xino2->pub.inum = DELETE_PENDING_ID; xino2->pub.mode = (S_IFDIR | 0777) & ~umask; xino2->pub.uid = uid; xino2->pub.gid = gid; xino2->pub.size = 0; xino2->pub.atime = time(0); xino2->pub.mtime = xino2->pub.atime; xino2->pub.ctime = xino2->pub.atime; strcpy(xino2->pub.name, ".delete-pending"); xino2->pub.generation = xfs->generation; xino2->pub.is_redirected = 0; xino2->pub.device_id = 0; xino2->parent = NULL; xino2->next = NULL; xino2->previous = NULL; xino2->dir.begin = NULL; xino2->dir.end = NULL; /* * Uncomment this line to make the .delete-pending * directory visible to the user in the root */ /* link_inode_into_directory_node(xino1, xino2); */ } } return xfs; } /* ------------------------------------------------------------------------ */ void xfs_delete_xfs_fs(struct xfs_fs *xfs) { if (xfs == NULL) { return; } if (xfs->inode_table != NULL) { size_t i; for (i = 0 ; i < xfs->inode_count; ++i) { free(xfs->inode_table[i]); } } free(xfs->inode_table); free(xfs->free_list); free(xfs); } /* ------------------------------------------------------------------------ */ XFS_INODE * xfs_add_entry(struct xfs_fs *xfs, fuse_ino_t parent_inum, const char *name, mode_t mode) { XFS_INODE *result = NULL; XFS_INODE_ALL *parent = NULL; /* Checks:- * 1) the parent exists (and is a directory) * 2) the caller is not inserting into the .delete-pending directory, * 3) Name's not too long * 4) Entry does not already exist */ if (parent_inum < xfs->inode_count && ((parent = xfs->inode_table[parent_inum]) != NULL) && (parent->pub.mode & S_IFDIR) != 0 && parent_inum != DELETE_PENDING_ID && strlen(name) <= XFS_MAXFILENAMELEN && !xfs_lookup_in_dir(xfs, parent_inum, name)) { /* Sanitise the mode so one-and-only-one of S_IFDIR and * S_IFREG is set */ if ((mode & S_IFDIR) != 0) { mode = (mode & 0777) | S_IFDIR; } else { mode = (mode & 0777) | S_IFREG; } /* Space for a new entry? */ if (xfs->free_count > 0 || grow_xfs(xfs, INODE_TABLE_ALLOCATION_GRANULARITY)) { XFS_INODE_ALL *xino = NULL; if ((xino = g_new0(XFS_INODE_ALL, 1)) != NULL) { fuse_ino_t inum = xfs->free_list[--xfs->free_count]; if (xfs->inode_table[inum] != NULL) { log_error("Unexpected non-NULL value in inode table " "entry %ld", inum); } xfs->inode_table[inum] = xino; xino->pub.inum = inum; xino->pub.mode = mode; xino->pub.uid = parent->pub.uid; xino->pub.gid = parent->pub.gid; if (mode & S_IFDIR) { xino->pub.size = 4096; } else { xino->pub.size = 0; } xino->pub.atime = time(0); xino->pub.mtime = xino->pub.atime; xino->pub.ctime = xino->pub.atime; strcpy(xino->pub.name, name); xino->pub.generation = xfs->generation; xino->pub.is_redirected = parent->pub.is_redirected; xino->pub.device_id = parent->pub.device_id; xino->pub.lindex = 0; xino->parent = NULL; xino->next = NULL; xino->previous = NULL; link_inode_into_directory_node(parent, xino); result = &xino->pub; } } } return result; } /* ------------------------------------------------------------------------ */ void xfs_remove_directory_contents(struct xfs_fs *xfs, fuse_ino_t inum) { XFS_INODE_ALL *xino = NULL; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && ((xino->pub.mode & S_IFDIR) != 0)) { XFS_INODE_ALL *e; while ((e = xino->dir.end) != NULL) { xfs_remove_entry(xfs, e->pub.inum); } } } /* ------------------------------------------------------------------------ */ void xfs_remove_entry(struct xfs_fs *xfs, fuse_ino_t inum) { XFS_INODE_ALL *xino = NULL; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL)) { if ((xino->pub.mode & S_IFDIR) != 0) { xfs_remove_directory_contents(xfs, inum); } unlink_inode_from_parent(xino); if ((xino->pub.mode & S_IFREG) != 0 && xino->open_count > 0) { link_inode_into_directory_node( xfs->inode_table[DELETE_PENDING_ID], xino); } else { xfs->free_list[xfs->free_count++] = inum; xfs->inode_table[inum] = NULL; /* * Bump the generation when we return an inum to the free list, * so that the caller can distinguish re-uses of the same inum. */ ++xfs->generation; free(xino); } } } /* ------------------------------------------------------------------------ */ XFS_INODE * xfs_get(struct xfs_fs *xfs, fuse_ino_t inum) { return (inum < xfs->inode_count) ? &xfs->inode_table[inum]->pub : NULL; } /* ------------------------------------------------------------------------ */ char * xfs_get_full_path(struct xfs_fs *xfs, fuse_ino_t inum) { char *result = NULL; XFS_INODE_ALL *xino = NULL; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL)) { if (xino->pub.inum == FUSE_ROOT_ID) { return strdup("/"); } else { /* * Add up the lengths of all the names up to the root, * allowing one extra char for a '/' prefix for each element */ size_t len = 0; XFS_INODE_ALL *p; for (p = xino ; p && p->pub.inum != FUSE_ROOT_ID ; p = p->parent) { len += strlen(p->pub.name); ++len; /* Allow for '/' prefix */ } result = (char *) malloc(len + 1); if (result != NULL) { /* Construct the path from the end */ char *end = result + len; *end = '\0'; for (p = xino ; p && p->pub.inum != FUSE_ROOT_ID ; p = p->parent) { len = strlen(p->pub.name); end -= (len + 1); *end = '/'; memcpy(end + 1, p->pub.name, len); } } } } return result; } /* ------------------------------------------------------------------------ */ XFS_INODE * xfs_lookup_in_dir(struct xfs_fs *xfs, fuse_ino_t inum, const char *name) { XFS_INODE_ALL *xino; XFS_INODE *result = NULL; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && (xino->pub.mode & S_IFDIR) != 0) { XFS_INODE_ALL *p; for (p = xino->dir.begin ; p != NULL; p = p->next) { if (strcmp(p->pub.name, name) == 0) { result = &p->pub; break; } } } return result; } /* ------------------------------------------------------------------------ */ int xfs_is_dir_empty(struct xfs_fs *xfs, fuse_ino_t inum) { XFS_INODE_ALL *xino = NULL; int result = 0; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && (xino->pub.mode & S_IFDIR) != 0) { result = (xino->dir.begin == NULL); } return result; } /* ------------------------------------------------------------------------ */ unsigned int xfs_is_under(struct xfs_fs *xfs, fuse_ino_t dir, fuse_ino_t entry) { unsigned int result = 0; XFS_INODE_ALL *dxino = NULL; XFS_INODE_ALL *exino = NULL; if (dir < xfs->inode_count && ((dxino = xfs->inode_table[dir]) != NULL) && (dxino->pub.mode & S_IFDIR) != 0 && entry < xfs->inode_count && ((exino = xfs->inode_table[entry]) != NULL)) { unsigned int count = 0; while (exino != NULL && exino != dxino) { ++count; exino = exino->parent; } if (exino != NULL) { result = count; } } return result; } /* ------------------------------------------------------------------------ */ struct xfs_dir_handle * xfs_opendir(struct xfs_fs *xfs, fuse_ino_t dir) { XFS_INODE_ALL *xino = NULL; struct xfs_dir_handle *result = NULL; if (dir < xfs->inode_count && ((xino = xfs->inode_table[dir]) != NULL) && (xino->pub.mode & S_IFDIR) != 0) { result = g_new0(struct xfs_dir_handle, 1); if (result) { result->inum = xino->pub.inum; result->generation = xino->pub.generation; } } return result; } /* ------------------------------------------------------------------------ */ XFS_INODE * xfs_readdir(struct xfs_fs *xfs, struct xfs_dir_handle *handle, off_t *off) { XFS_INODE_ALL *result = NULL; XFS_INODE_ALL *dxino = NULL; XFS_INODE_ALL *xino = NULL; /* Check the direcory is still valid */ if (handle->inum < xfs->inode_count && ((dxino = xfs->inode_table[handle->inum]) != NULL) && (dxino->pub.mode & S_IFDIR) != 0 && handle->generation == dxino->pub.generation) { fuse_ino_t inum; if (*off == (off_t) -1) { /* We're at the end already */ } else if ((inum = *off) == 0) { /* First call */ result = dxino->dir.begin; } else if (inum < xfs->inode_count && (xino = xfs->inode_table[inum]) != 0 && xino->parent == dxino) { /* The node we're pointing to is still valid */ result = xino; } else { /* * The file we wanted has been pulled out from under us. * We will look forward in the inode table to try to * discover the next inode in the directory. Because * files are stored in inode order, this guarantees * we'll meet POSIX requirements. */ for (inum = inum + 1 ; inum < xfs->inode_count ; ++inum) { if ((xino = xfs->inode_table[inum]) != 0 && xino->parent == dxino) { result = xino; break; } } } } /* Update the offset */ if (result == NULL || result->next == NULL) { /* We're done */ *off = (off_t) -1; } else { *off = (off_t)result->next->pub.inum; } /* Caller only sees public interface to the result */ return (result) ? &result->pub : NULL; } /* ------------------------------------------------------------------------ */ void xfs_closedir(struct xfs_fs *xfs, struct xfs_dir_handle *handle) { free(handle); } /* ------------------------------------------------------------------------ */ void xfs_increment_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum) { XFS_INODE_ALL *xino; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && (xino->pub.mode & S_IFREG) != 0) { ++xino->open_count; } } /* ------------------------------------------------------------------------ */ void xfs_decrement_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum) { XFS_INODE_ALL *xino; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && (xino->pub.mode & S_IFREG) != 0) { if (xino->open_count > 0) { --xino->open_count; } if (xino->open_count == 0 && xino->parent == xfs->inode_table[DELETE_PENDING_ID]) { /* We can get rid of this one now */ xfs_remove_entry(xfs, inum); } } } /* ------------------------------------------------------------------------ */ unsigned int xfs_get_file_open_count(struct xfs_fs *xfs, fuse_ino_t inum) { unsigned int result = 0; XFS_INODE_ALL *xino; if (inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && (xino->pub.mode & S_IFREG) != 0) { result = xino->open_count; } return result; } /* ------------------------------------------------------------------------ */ void xfs_delete_redirected_entries_with_device_id(struct xfs_fs *xfs, tui32 device_id) { fuse_ino_t inum; XFS_INODE_ALL *xino; /* Using xfs_remove_entry() is convenient, but it recurses * in to directories. To make sure all entries are removed, set the * open_count of all affected files to 0 first */ for (inum = FUSE_ROOT_ID; inum < xfs->inode_count; ++inum) { if ((xino = xfs->inode_table[inum]) != NULL && xino->pub.is_redirected != 0 && xino->pub.device_id == device_id && (xino->pub.mode & S_IFREG) != 0) { xino->open_count = 0; } } /* Now we can be sure everything will be deleted correctly */ for (inum = FUSE_ROOT_ID; inum < xfs->inode_count; ++inum) { if ((xino = xfs->inode_table[inum]) != NULL && xino->pub.is_redirected != 0 && xino->pub.device_id == device_id) { xfs_remove_entry(xfs, xino->pub.inum); } } } /* ------------------------------------------------------------------------ */ int xfs_check_move_entry(struct xfs_fs *xfs, fuse_ino_t inum, fuse_ino_t new_parent_inum, const char *name) { XFS_INODE_ALL *xino; XFS_INODE_ALL *parent; return (strlen(name) <= XFS_MAXFILENAMELEN && inum < xfs->inode_count && ((xino = xfs->inode_table[inum]) != NULL) && new_parent_inum != DELETE_PENDING_ID && new_parent_inum < xfs->inode_count && ((parent = xfs->inode_table[new_parent_inum]) != NULL) && (parent->pub.mode & S_IFDIR) != 0 && xfs_is_under(xfs, inum, new_parent_inum) == 0); } /* ------------------------------------------------------------------------ */ int xfs_move_entry(struct xfs_fs *xfs, fuse_ino_t inum, fuse_ino_t new_parent_inum, const char *name) { int result = EINVAL; XFS_INODE_ALL *xino; XFS_INODE_ALL *parent; XFS_INODE *dest; if (xfs_check_move_entry(xfs, inum, new_parent_inum, name)) { xino = xfs->inode_table[inum]; parent = xfs->inode_table[new_parent_inum]; if (xino->parent != parent) { /* We're moving between directories */ /* Does the target name already exist in the destination? */ if ((dest = xfs_lookup_in_dir(xfs, new_parent_inum, name)) != NULL) { xfs_remove_entry(xfs, dest->inum); } unlink_inode_from_parent(xino); link_inode_into_directory_node(parent, xino); strcpy(xino->pub.name, name); } else if (strcmp(xino->pub.name, name) != 0) { /* Same directory, but name has changed */ if ((dest = xfs_lookup_in_dir(xfs, new_parent_inum, name)) != NULL) { xfs_remove_entry(xfs, dest->inum); } strcpy(xino->pub.name, name); } result = 0; } return result; } #endif /* XRDP_FUSE */