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