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 long err; 196 197 /* Create a notification endpoint, if necessary. */ 198 199 if (state.is_null()) 200 { 201 err = ipc_server_new_for_thread(&state.endpoint, file, _thread); 202 203 if (err) 204 return err; 205 } 206 207 /* Subscribe, sending the notification endpoint. */ 208 209 client_Notification notify(file->ref); 210 211 err = notify.subscribe(state.endpoint, flags); 212 213 if (err) 214 { 215 ipc_cap_free_um(state.endpoint); 216 _state.erase(file); 217 return err; 218 } 219 220 return L4_EOK; 221 } 222 223 /* Unsubscribe from notification events on a file. */ 224 225 long FileNotifier::unsubscribe(file_t *file) 226 { 227 /* Acquire the lock for state lookup. */ 228 229 std::unique_lock<std::mutex> state_guard(_state_lock); 230 231 FileNotificationState &state = file_state(file, false); 232 233 if (state.is_null()) 234 return -L4_EINVAL; 235 236 /* Unsubscribe via the notification interface. */ 237 238 client_Notification notify(file->ref); 239 240 notify.unsubscribe(); 241 242 ipc_cap_free_um(state.endpoint); 243 244 _state.erase(file); 245 246 /* Remove the lock for updating file state. */ 247 248 _file_locks.erase(file); 249 250 return L4_EOK; 251 } 252 253 254 255 /* Handle a notification event for a file. Ideally, this would be invoked by the 256 generic server dispatch mechanism, with the gate label being interpreted and 257 provided as the first parameter. */ 258 259 void GeneralFileNotifier::_notify(file_t *file, notify_flags_t flags) 260 { 261 /* Enter critical section for the notifier (affecting all files). */ 262 263 std::unique_lock<std::mutex> general_guard(_general_lock); 264 265 /* Acquire the lock for state lookup. */ 266 267 std::unique_lock<std::mutex> state_guard(_state_lock); 268 269 FileNotificationState &state = file_state(file, false); 270 271 if (state.is_null()) 272 return; 273 274 /* Acquire the lock for the file state itself. */ 275 276 std::unique_lock<std::mutex> file_guard(state.lock); 277 278 /* Record flags and return previous flags. */ 279 280 notify_flags_t recorded = state.pending; 281 282 state.pending |= flags; 283 284 /* Add a file queue entry for any files without previous notifications. */ 285 286 if (!recorded) 287 _affected.push_back(file); 288 289 /* Notify any waiting caller. */ 290 291 _general_condition.notify_one(); 292 } 293 294 void SpecificFileNotifier::_notify(file_t *file, notify_flags_t flags) 295 { 296 /* Acquire the lock for state lookup. */ 297 298 std::unique_lock<std::mutex> state_guard(_state_lock); 299 300 FileNotificationState &state = file_state(file, false); 301 302 if (state.is_null()) 303 return; 304 305 /* Acquire the lock for the file state itself. */ 306 307 std::unique_lock<std::mutex> file_guard(state.lock); 308 309 state.pending |= flags; 310 311 /* Notify any waiting caller. */ 312 313 state.condition.notify_one(); 314 } 315 316 317 318 /* Transfer pending notifications to the given file. This must be called with a 319 lock acquired on the file notification state. */ 320 321 bool FileNotifier::_transfer(FileNotificationState &state, file_t *file) 322 { 323 notify_flags_t recorded = state.pending; 324 325 if (recorded) 326 { 327 file->notifications = recorded; 328 state.pending = 0; 329 return true; 330 } 331 332 return false; 333 } 334 335 336 337 /* Obtain file state and transfer notifications. */ 338 339 bool GeneralFileNotifier::_retrieve_for_file(file_t *file) 340 { 341 /* Acquire the lock for state lookup. */ 342 343 std::unique_lock<std::mutex> state_guard(_state_lock); 344 345 FileNotificationState &state = file_state(file, false); 346 347 if (state.is_null()) 348 return false; 349 350 /* Acquire the lock for the file state itself, then release the state lock. */ 351 352 std::unique_lock<std::mutex> file_guard(state.lock); 353 354 state_guard.unlock(); 355 356 /* Call generic method to transfer notifications, if possible. */ 357 358 return _transfer(state, file); 359 } 360 361 /* Obtain queued files until one is found that still has events recorded for it. 362 This must be called with the notifier's general lock acquired. */ 363 364 bool GeneralFileNotifier::_retrieve(file_t **file) 365 { 366 while (!_affected.empty()) 367 { 368 *file = _affected.front(); 369 _affected.pop_front(); 370 371 if (_retrieve_for_file(*file)) 372 return true; 373 } 374 375 return false; 376 } 377 378 379 380 /* Wait for notification events on files. */ 381 382 long GeneralFileNotifier::wait(file_t **file) 383 { 384 std::unique_lock<std::mutex> general_guard(_general_lock); 385 386 while (1) 387 { 388 /* With pending notifications, update the first file and exit. */ 389 390 if (_retrieve(file)) 391 break; 392 393 /* Otherwise, wait for notifications. */ 394 395 _general_condition.wait(general_guard); 396 } 397 398 return L4_EOK; 399 } 400 401 /* Wait for notifications from a single file. */ 402 403 long SpecificFileNotifier::wait_file(file_t *file) 404 { 405 /* Acquire the lock for reading file state. */ 406 407 std::unique_lock<std::mutex> state_guard(_state_lock); 408 409 FileNotificationState &state = file_state(file, false); 410 411 if (state.is_null()) 412 return -L4_EINVAL; 413 414 /* Acquire the lock for the file state itself, then release the state lock. */ 415 416 std::unique_lock<std::mutex> file_guard(state.lock); 417 418 state_guard.unlock(); 419 420 while (1) 421 { 422 /* With pending notifications, update the file and exit. */ 423 424 if (_transfer(state, file)) 425 break; 426 427 /* Otherwise, wait for notifications. */ 428 429 state.condition.wait(file_guard); 430 } 431 432 return L4_EOK; 433 } 434 435 // vim: tabstop=2 expandtab shiftwidth=2