1 /* 2 * File event notification support. 3 * 4 * Copyright (C) 2021, 2022 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 <map> 23 #include <mutex> 24 25 #include <ipc/cap_alloc.h> 26 #include <ipc/server.h> 27 28 #include <pthread.h> 29 #include <pthread-l4.h> 30 31 #include "notification_client.h" 32 #include "notifier.h" 33 34 35 36 /* Null notification state. */ 37 38 static FileNotificationState _null_state; 39 40 41 42 /* Lock protecting per-task notifier access. */ 43 44 static std::mutex _lock; 45 46 /* Per-task storage for specific waiting operations. */ 47 48 static SpecificFileNotifier *_notifier = NULL; 49 50 51 52 /* Return the per-task notifier for file-specific waiting operations. */ 53 54 SpecificFileNotifier *notifier_get_task_notifier() 55 { 56 std::lock_guard<std::mutex> guard(_lock); 57 58 /* Start any new notifier. */ 59 60 if (_notifier == NULL) 61 { 62 _notifier = new SpecificFileNotifier; 63 _notifier->start(); 64 } 65 66 return _notifier; 67 } 68 69 /* Return a local notifier for general file waiting operations. */ 70 71 GeneralFileNotifier *notifier_get_local_notifier() 72 { 73 GeneralFileNotifier *notifier = new GeneralFileNotifier; 74 75 notifier->start(); 76 return notifier; 77 } 78 79 80 81 /* Invoke the mainloop in a thread. */ 82 83 static void *notifier_mainloop(void *data) 84 { 85 FileNotifier *notifier = reinterpret_cast<FileNotifier *>(data); 86 87 notifier->mainloop(); 88 return 0; 89 } 90 91 92 93 /* Virtual destructor required for base class instance reference deletion. */ 94 95 FileNotifier::~FileNotifier() 96 { 97 } 98 99 /* Listen for notifications. */ 100 101 void FileNotifier::mainloop() 102 { 103 ipc_message_t msg; 104 l4_umword_t label; 105 106 while (1) 107 { 108 ipc_message_wait(&msg, &label); 109 110 /* Clear lower label bits. */ 111 112 label = label & ~3UL; 113 114 /* Ignore erroneous messages. */ 115 116 if (l4_ipc_error(msg.tag, l4_utcb())) 117 continue; 118 119 /* Interpret gate labels as file objects. */ 120 121 file_t *file = (file_t *) label; 122 123 /* Obtain message details. */ 124 125 ipc_message_open(&msg); 126 127 notify_flags_t flags = ipc_message_get_word(&msg, 0); 128 129 /* Reply to notifications. */ 130 131 ipc_message_reply(&msg); 132 ipc_message_discard(&msg); 133 134 /* Register the notification. */ 135 136 _notify(file, flags); 137 } 138 139 ipc_message_free(&msg); 140 } 141 142 /* Start listening for notifications. */ 143 144 long FileNotifier::start() 145 { 146 if (_started) 147 return L4_EOK; 148 149 pthread_t thread; 150 pthread_attr_t attr; 151 long err; 152 153 pthread_attr_init(&attr); 154 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 155 156 err = pthread_create(&thread, &attr, notifier_mainloop, this); 157 if (err) 158 return err; 159 160 _thread = pthread_l4_cap(thread); 161 _started = true; 162 163 return L4_EOK; 164 } 165 166 167 168 /* Return a notification state object for the given file or a null object if no 169 record existed for the file. */ 170 171 FileNotificationState &FileNotifier::file_state(file_t *file, bool create) 172 { 173 FileNotificationStates::iterator it = _state.find(file); 174 175 if (it == _state.end()) 176 { 177 if (create) 178 return _state[file]; 179 else 180 return _null_state; 181 } 182 183 return it->second; 184 } 185 186 /* Subscribe to notification events on a file. */ 187 188 long FileNotifier::subscribe(file_t *file, notify_flags_t flags) 189 { 190 /* Acquire the lock for state lookup. */ 191 192 std::unique_lock<std::mutex> state_guard(_state_lock); 193 194 FileNotificationState &state = file_state(file, true); 195 196 /* Create a notification endpoint, if necessary. */ 197 198 if (state.is_null()) 199 { 200 long err = ipc_server_new_for_thread(&state.endpoint, file, _thread); 201 202 if (err) 203 return err; 204 } 205 206 client_Notification notify(file->ref); 207 208 return notify.subscribe(state.endpoint, flags); 209 } 210 211 /* Unsubscribe from notification events on a file. */ 212 213 long FileNotifier::unsubscribe(file_t *file) 214 { 215 /* Acquire the lock for state lookup. */ 216 217 std::unique_lock<std::mutex> state_guard(_state_lock); 218 219 FileNotificationState &state = file_state(file, false); 220 221 if (state.is_null()) 222 return -L4_EINVAL; 223 224 _state.erase(file); 225 226 /* Remove the lock for updating file state. */ 227 228 _file_locks.erase(file); 229 230 return L4_EOK; 231 } 232 233 234 235 /* Handle a notification event for a file. Ideally, this would be invoked by the 236 generic server dispatch mechanism, with the gate label being interpreted and 237 provided as the first parameter. */ 238 239 void GeneralFileNotifier::_notify(file_t *file, notify_flags_t flags) 240 { 241 /* Enter critical section for the notifier (affecting all files). */ 242 243 std::unique_lock<std::mutex> general_guard(_general_lock); 244 245 /* Acquire the lock for state lookup. */ 246 247 std::unique_lock<std::mutex> state_guard(_state_lock); 248 249 FileNotificationState &state = file_state(file, false); 250 251 if (state.is_null()) 252 return; 253 254 /* Acquire the lock for the file state itself. */ 255 256 std::unique_lock<std::mutex> file_guard(state.lock); 257 258 /* Record flags and return previous flags. */ 259 260 notify_flags_t recorded = state.pending; 261 262 state.pending |= flags; 263 264 /* Add a file queue entry for any files without previous notifications. */ 265 266 if (!recorded) 267 _affected.push_back(file); 268 269 /* Notify any waiting caller. */ 270 271 _general_condition.notify_one(); 272 } 273 274 void SpecificFileNotifier::_notify(file_t *file, notify_flags_t flags) 275 { 276 /* Acquire the lock for state lookup. */ 277 278 std::unique_lock<std::mutex> state_guard(_state_lock); 279 280 FileNotificationState &state = file_state(file, false); 281 282 if (state.is_null()) 283 return; 284 285 /* Acquire the lock for the file state itself. */ 286 287 std::unique_lock<std::mutex> file_guard(state.lock); 288 289 state.pending |= flags; 290 291 /* Notify any waiting caller. */ 292 293 state.condition.notify_one(); 294 } 295 296 297 298 /* Transfer pending notifications to the given file. This must be called with a 299 lock acquired on the file notification state. */ 300 301 bool FileNotifier::_transfer(FileNotificationState &state, file_t *file) 302 { 303 notify_flags_t recorded = state.pending; 304 305 if (recorded) 306 { 307 file->notifications = recorded; 308 state.pending = 0; 309 return true; 310 } 311 312 return false; 313 } 314 315 316 317 /* Obtain file state and transfer notifications. */ 318 319 bool GeneralFileNotifier::_retrieve_for_file(file_t *file) 320 { 321 /* Acquire the lock for state lookup. */ 322 323 std::unique_lock<std::mutex> state_guard(_state_lock); 324 325 FileNotificationState &state = file_state(file, false); 326 327 if (state.is_null()) 328 return false; 329 330 /* Acquire the lock for the file state itself, then release the state lock. */ 331 332 std::unique_lock<std::mutex> file_guard(state.lock); 333 334 state_guard.unlock(); 335 336 /* Call generic method to transfer notifications, if possible. */ 337 338 return _transfer(state, file); 339 } 340 341 /* Obtain queued files until one is found that still has events recorded for it. 342 This must be called with the notifier's general lock acquired. */ 343 344 bool GeneralFileNotifier::_retrieve(file_t **file) 345 { 346 while (!_affected.empty()) 347 { 348 *file = _affected.front(); 349 _affected.pop_front(); 350 351 if (_retrieve_for_file(*file)) 352 return true; 353 } 354 355 return false; 356 } 357 358 359 360 /* Wait for notification events on files. */ 361 362 long GeneralFileNotifier::wait(file_t **file) 363 { 364 std::unique_lock<std::mutex> general_guard(_general_lock); 365 366 while (1) 367 { 368 /* With pending notifications, update the first file and exit. */ 369 370 if (_retrieve(file)) 371 break; 372 373 /* Otherwise, wait for notifications. */ 374 375 _general_condition.wait(general_guard); 376 } 377 378 return L4_EOK; 379 } 380 381 /* Wait for notifications from a single file. */ 382 383 long SpecificFileNotifier::wait_file(file_t *file) 384 { 385 /* Acquire the lock for reading file state. */ 386 387 std::unique_lock<std::mutex> state_guard(_state_lock); 388 389 FileNotificationState &state = file_state(file, false); 390 391 if (state.is_null()) 392 return -L4_EINVAL; 393 394 /* Acquire the lock for the file state itself, then release the state lock. */ 395 396 std::unique_lock<std::mutex> file_guard(state.lock); 397 398 state_guard.unlock(); 399 400 while (1) 401 { 402 /* With pending notifications, update the file and exit. */ 403 404 if (_transfer(state, file)) 405 break; 406 407 /* Otherwise, wait for notifications. */ 408 409 state.condition.wait(file_guard); 410 } 411 412 return L4_EOK; 413 } 414 415 // vim: tabstop=2 expandtab shiftwidth=2