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, 122 fileid_t fileid, offset_t *size, 123 l4_cap_idx_t *cap, object_flags_t *object_flags) 124 { 125 /* The file identifier is used to obtain the directory. */ 126 127 (void) path; (void) flags; 128 129 file_t *reader, *writer; 130 131 // NOTE: Might be more appropriate to use lower-level file operations to 132 // NOTE: avoid unnecessary mapping of the reader's memory region. 133 134 long err = client_pipe(&reader, &writer); 135 136 if (err) 137 return err; 138 139 *size = reader->size; 140 *cap = reader->ref; 141 *object_flags = 0; /* does not support mmap, has no fixed size */ 142 143 /* Spawn a independent thread for reading the directory details and writing 144 them to the pipe. */ 145 146 std::thread(_read_directory, this, fileid, writer).detach(); 147 148 /* Discard the reader structure but preserve the capability. */ 149 150 reader->ref = L4_INVALID_CAP; 151 file_close(reader); 152 153 /* Return an indication that the capability will be propagated and not 154 retained. This is explicitly supported by the opener context. */ 155 156 return IPC_MESSAGE_SENT; 157 } 158 159 /* Thread payload helper method. */ 160 161 void Ext2FileOpener::read_directory(fileid_t fileid, file_t *writer) 162 { 163 /* Initialise directory reading state: opener, writer, entry, offset. */ 164 165 struct Ext2FileOpenerDir dir = {this, writer, NULL, 0}; 166 167 /* Call the handler function for each directory entry. */ 168 169 _ops->directory_iterate((ext2_ino_t) fileid, read_directory_entry, &dir); 170 } 171 172 /* Callback method for directory entry output. */ 173 174 int Ext2FileOpener::write_directory_entry(struct Ext2FileOpenerDir *dir) 175 { 176 struct ext2_inode inode; 177 178 /* Obtain the inode details for metadata. */ 179 180 if (_ops->read_inode(dir->entry->inode, &inode)) 181 return DIRENT_ABORT; 182 183 /* Align the size of the entry to avoid problems on architectures which 184 require aligned accesses and where the compiler needs to assume an 185 aligned structure. */ 186 187 offset_t namelen = ext2fs_dirent_name_len(dir->entry); 188 offset_t reclen = pad_align(DIRENT_CORE_SIZE + namelen); 189 190 /* Construct a directory entry structure of the calculated size. */ 191 192 char buffer[reclen]; 193 struct dirent *dirent = (struct dirent *) buffer; 194 195 dirent->d_ino = dir->entry->inode; 196 dirent->d_off = dir->offset; 197 dirent->d_reclen = reclen; 198 dirent->d_type = convert_file_type(ext2fs_dirent_file_type(dir->entry)); 199 200 /* Copy the name, padding the memory after it to the alignment boundary. */ 201 202 memcpy(dirent->d_name, dir->entry->name, namelen); 203 memset(dirent->d_name + namelen, 0, reclen - namelen); 204 205 /* Write the structure to the pipe. */ 206 207 offset_t nwritten = client_write(dir->writer, (const void *) dirent, reclen); 208 209 /* Stop writing if the pipe is closed. */ 210 211 if (nwritten < reclen) 212 return DIRENT_ABORT; 213 214 return 0; 215 } 216 217 /* Return a file identifier for the given 'path'. */ 218 219 long Ext2FileOpener::get_fileid(const char *path, flags_t flags, fileid_t *fileid) 220 { 221 ext2_ino_t ino; 222 const char *remaining; 223 long err = _ops->find_file(path, &ino, &remaining); 224 225 /* Return an existing file. */ 226 227 if (!err) 228 { 229 *fileid = (fileid_t) ino; 230 return L4_EOK; 231 } 232 233 *fileid = FILEID_INVALID; 234 235 /* Create a missing file if possible. */ 236 237 if ((flags & O_CREAT) && (flags & ~O_DIRECTORY)) 238 { 239 /* Determine whether only the leafname is left of the path, with 240 the inode number referring to the parent directory. */ 241 242 if (path_is_leafname(remaining)) 243 { 244 err = _ops->create_file(ino, remaining, _user, &ino); 245 if (err) 246 return err; 247 248 *fileid = (fileid_t) ino; 249 return L4_EOK; 250 } 251 } 252 253 return -L4_ENOENT; 254 } 255 256 /* Return a new accessor for 'fileid'. */ 257 258 long Ext2FileOpener::make_accessor(const char *path, flags_t flags, fileid_t fileid, Accessor **accessor) 259 { 260 (void) path; (void) flags; 261 262 ext2_file_t file; 263 long err = _ops->open_file((ext2_ino_t) fileid, &file); 264 265 if (err) 266 return err; 267 268 *accessor = new Ext2FileAccessor(_ops, file, fileid); 269 return L4_EOK; 270 } 271 272 // vim: tabstop=4 expandtab shiftwidth=4