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