1 /* 2 * An opener for a file provided by an Ext2-compatible filesystem. 3 * 4 * Copyright (C) 2021 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <dirent.h> 23 #include <string.h> 24 25 #include <thread> 26 27 #include <e2access/path.h> 28 #include <fsclient/client.h> 29 #include <systypes/fcntl.h> 30 31 #include "ext2_file_accessor.h" 32 #include "ext2_file_opener.h" 33 34 35 36 /* Common definitions. */ 37 38 #define DIRENT_CORE_SIZE (sizeof(struct dirent) - sizeof(((struct dirent *) 0)->d_name)) 39 40 /* File type conversion. */ 41 42 static int convert_file_type(int type) 43 { 44 switch (type) 45 { 46 case EXT2_FT_REG_FILE: return DT_REG; 47 case EXT2_FT_DIR: return DT_DIR; 48 case EXT2_FT_CHRDEV: return DT_CHR; 49 case EXT2_FT_BLKDEV: return DT_BLK; 50 case EXT2_FT_FIFO: return DT_FIFO; 51 case EXT2_FT_SOCK: return DT_SOCK; 52 case EXT2_FT_SYMLINK: return DT_LNK; 53 default: return DT_UNKNOWN; 54 } 55 } 56 57 /* Helper function to ensure alignment in generated entries. */ 58 59 static int pad_align(int value) 60 { 61 return value + (sizeof(unsigned int) - (value % sizeof(unsigned int))); 62 } 63 64 /* Callback function. */ 65 66 static int read_directory_entry(struct ext2_dir_entry *dir_entry, int offset, 67 int blocksize, char *buf, void *priv_data) 68 { 69 (void) offset; (void) blocksize; (void) buf; 70 71 struct Ext2FileOpenerDir *dir = reinterpret_cast<struct Ext2FileOpenerDir *>(priv_data); 72 73 dir->entry = dir_entry; 74 dir->offset = offset; 75 return dir->opener->write_directory_entry(dir); 76 } 77 78 /* Thread payload. */ 79 80 static void _read_directory(Ext2FileOpener *opener, fileid_t fileid, file_t *writer) 81 { 82 /* Subscribe to space and closure notifications on the pipe. */ 83 84 long err = client_set_blocking(writer, NOTIFY_SPACE_AVAILABLE | NOTIFY_PEER_CLOSED); 85 86 if (err) 87 { 88 client_close(writer); 89 return; 90 } 91 92 opener->read_directory(fileid, writer); 93 94 client_close(writer); 95 } 96 97 98 99 Ext2FileOpener::~Ext2FileOpener() 100 { 101 } 102 103 /* Test if a directory is being accessed. */ 104 105 bool Ext2FileOpener::accessing_directory(const char *path, flags_t flags, fileid_t fileid) 106 { 107 (void) path; (void) flags; 108 return _ops->is_directory((ext2_ino_t) fileid); 109 } 110 111 /* Test if a file is being accessed. */ 112 113 bool Ext2FileOpener::accessing_file(const char *path, flags_t flags, fileid_t fileid) 114 { 115 (void) path; (void) flags; 116 return _ops->is_file((ext2_ino_t) fileid); 117 } 118 119 // NOTE: This is mostly the same as the HostFileOpener implementation. 120 121 long Ext2FileOpener::get_directory(const char *path, flags_t flags, fileid_t fileid, offset_t *size, l4_cap_idx_t *cap) 122 { 123 (void) path; (void) flags; 124 125 file_t *reader, *writer; 126 127 // NOTE: Might be more appropriate to use lower-level file operations to 128 // NOTE: avoid unnecessary mapping of the reader's memory region. 129 130 long err = client_pipe(&reader, &writer); 131 132 if (err) 133 return err; 134 135 *size = reader->size; 136 *cap = reader->ref; 137 138 /* Spawn a independent thread for reading the directory details and writing 139 them to the pipe. */ 140 141 std::thread(_read_directory, this, fileid, writer).detach(); 142 143 /* Discard the reader structure but preserve the capability. */ 144 145 reader->ref = L4_INVALID_CAP; 146 file_close(reader); 147 148 /* Return an indication that the capability will be propagated and not 149 retained. This is explicitly supported by the opener context. */ 150 151 return IPC_MESSAGE_SENT; 152 } 153 154 /* Thread payload helper method. */ 155 156 void Ext2FileOpener::read_directory(fileid_t fileid, file_t *writer) 157 { 158 /* Initialise directory reading state: opener, writer, entry, offset. */ 159 160 struct Ext2FileOpenerDir dir = {this, writer, NULL, 0}; 161 162 /* Call the handler function for each directory entry. */ 163 164 _ops->directory_iterate((ext2_ino_t) fileid, read_directory_entry, &dir); 165 } 166 167 /* Callback method for directory entry output. */ 168 169 int Ext2FileOpener::write_directory_entry(struct Ext2FileOpenerDir *dir) 170 { 171 struct ext2_inode inode; 172 173 /* Obtain the inode details for metadata. */ 174 175 if (_ops->read_inode(dir->entry->inode, &inode)) 176 return DIRENT_ABORT; 177 178 /* Align the size of the entry to avoid problems on architectures which 179 require aligned accesses and where the compiler needs to assume an 180 aligned structure. */ 181 182 offset_t namelen = ext2fs_dirent_name_len(dir->entry); 183 offset_t reclen = pad_align(DIRENT_CORE_SIZE + namelen); 184 185 /* Construct a directory entry structure of the calculated size. */ 186 187 char buffer[reclen]; 188 struct dirent *dirent = (struct dirent *) buffer; 189 190 dirent->d_ino = dir->entry->inode; 191 dirent->d_off = dir->offset; 192 dirent->d_reclen = reclen; 193 dirent->d_type = convert_file_type(ext2fs_dirent_file_type(dir->entry)); 194 195 /* Copy the name, padding the memory after it to the alignment boundary. */ 196 197 memcpy(dirent->d_name, dir->entry->name, namelen); 198 memset(dirent->d_name + namelen, 0, reclen - namelen); 199 200 /* Write the structure to the pipe. */ 201 202 offset_t nwritten = client_write(dir->writer, (const void *) dirent, reclen); 203 204 /* Stop writing if the pipe is closed. */ 205 206 if (nwritten < reclen) 207 return DIRENT_ABORT; 208 209 return 0; 210 } 211 212 /* Return a file identifier for the given 'path'. */ 213 214 long Ext2FileOpener::get_fileid(const char *path, flags_t flags, fileid_t *fileid) 215 { 216 ext2_ino_t ino; 217 const char *remaining; 218 long err = _ops->find_file(path, &ino, &remaining); 219 220 /* Return an existing file. */ 221 222 if (!err) 223 { 224 *fileid = (fileid_t) ino; 225 return L4_EOK; 226 } 227 228 *fileid = FILEID_INVALID; 229 230 /* Create a missing file if possible. */ 231 232 if ((flags & O_CREAT) && (flags & ~O_DIRECTORY)) 233 { 234 /* Determine whether only the leafname is left of the path, with 235 the inode number referring to the parent directory. */ 236 237 if (path_is_leafname(remaining)) 238 { 239 err = _ops->create_file(ino, remaining, _user, &ino); 240 if (err) 241 return err; 242 243 *fileid = (fileid_t) ino; 244 return L4_EOK; 245 } 246 } 247 248 return -L4_ENOENT; 249 } 250 251 /* Return a new accessor for 'fileid'. */ 252 253 long Ext2FileOpener::make_accessor(const char *path, flags_t flags, fileid_t fileid, Accessor **accessor) 254 { 255 (void) path; (void) flags; 256 257 ext2_file_t file; 258 long err = _ops->open_file((ext2_ino_t) fileid, &file); 259 260 if (err) 261 return err; 262 263 *accessor = new Ext2FileAccessor(_ops, file, fileid); 264 return L4_EOK; 265 } 266 267 // vim: tabstop=4 expandtab shiftwidth=4