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