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 /* Thread-local storage workaround. */ 37 38 static std::mutex lock; 39 static std::map<l4_cap_idx_t, FileNotifier *> notifiers; 40 41 FileNotifier *get_notifier() 42 { 43 std::lock_guard<std::mutex> guard(lock); 44 45 l4_cap_idx_t thread = pthread_l4_cap(pthread_self()); 46 FileNotifier *notifier = notifiers[thread]; 47 48 /* Start any new notifier. */ 49 50 if (notifier == NULL) 51 { 52 notifier = new FileNotifier; 53 notifiers[thread] = notifier; 54 notifier->start(); 55 } 56 57 return notifier; 58 } 59 60 61 62 /* Subscribe to notification events on a file. */ 63 64 long FileNotifier::subscribe(file_t *file, notify_flags_t flags) 65 { 66 /* Create a notification endpoint, if necessary. */ 67 68 if (l4_is_invalid_cap(file->notifier)) 69 { 70 long err = ipc_server_new_for_thread(&file->notifier, file, _thread); 71 72 if (err) 73 return err; 74 } 75 76 client_Notification notify(file->ref); 77 78 return notify.subscribe(file->notifier, flags); 79 } 80 81 /* Unsubscribe from notification events on a file. */ 82 83 long FileNotifier::unsubscribe(file_t *file) 84 { 85 if (l4_is_invalid_cap(file->notifier)) 86 return -L4_EINVAL; 87 88 ipc_cap_free_um(file->notifier); 89 90 client_Notification notify(file->ref); 91 92 return notify.unsubscribe(); 93 } 94 95 /* Handle a notification event for a file. Ideally, this would be invoked by the 96 generic server dispatch mechanism, with the gate label being interpreted and 97 provided as the first parameter. */ 98 99 void FileNotifier::_notify(file_t *file, notify_flags_t flags) 100 { 101 std::unique_lock<std::mutex> guard(_lock); 102 103 /* Record the flags for the file object. Where no flags are already recorded, 104 the new flags will be recorded and the file object queued. Otherwise, the 105 new flags will be combined with the recorded flags. */ 106 107 notify_flags_t recorded = _affected_flags[file]; 108 109 _affected_flags[file] = recorded | flags; 110 111 /* Add a file queue entry for any files without recorded notifications. */ 112 113 if (!recorded) 114 _affected.push_back(file); 115 116 /* Notify any waiting caller. */ 117 118 _notified.notify_one(); 119 } 120 121 /* Listen for notifications. */ 122 123 void FileNotifier::mainloop() 124 { 125 ipc_message_t msg; 126 l4_umword_t label; 127 128 while (1) 129 { 130 ipc_message_wait(&msg, &label); 131 132 /* Clear lower label bits. */ 133 134 label = label & ~3UL; 135 136 /* Ignore erroneous messages. */ 137 138 if (l4_ipc_error(msg.tag, l4_utcb())) 139 continue; 140 141 /* Interpret gate labels as file objects. */ 142 143 file_t *file = (file_t *) label; 144 145 /* Obtain message details. */ 146 147 ipc_message_open(&msg); 148 149 notify_flags_t flags = ipc_message_get_word(&msg, 0); 150 151 /* Reply to notifications. */ 152 153 ipc_message_reply(&msg); 154 ipc_message_discard(&msg); 155 156 /* Register the notification. */ 157 158 _notify(file, flags); 159 } 160 161 ipc_message_free(&msg); 162 } 163 164 /* Invoke the mainloop in a thread. */ 165 166 static void *notifier_mainloop(void *data) 167 { 168 FileNotifier *notifier = reinterpret_cast<FileNotifier *>(data); 169 170 notifier->mainloop(); 171 return 0; 172 } 173 174 /* Start listening for notifications. */ 175 176 long FileNotifier::start() 177 { 178 if (_started) 179 return L4_EOK; 180 181 pthread_t thread; 182 pthread_attr_t attr; 183 long err; 184 185 pthread_attr_init(&attr); 186 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 187 188 err = pthread_create(&thread, &attr, notifier_mainloop, this); 189 if (err) 190 return err; 191 192 _thread = pthread_l4_cap(thread); 193 _started = true; 194 195 return L4_EOK; 196 } 197 198 /* Wait for notification events on files. */ 199 200 long FileNotifier::wait(file_t **file) 201 { 202 std::unique_lock<std::mutex> guard(_lock); 203 204 while (1) 205 { 206 /* Obtain queued files until one is found that still has events recorded 207 for it. (Waiting for events specific to one file will remove recorded 208 events but not any file queue entries.) */ 209 210 while (!_affected.empty()) 211 { 212 *file = _affected.front(); 213 _affected.pop_front(); 214 215 notify_flags_t recorded = _affected_flags[*file]; 216 217 if (recorded) 218 { 219 (*file)->notifications = recorded; 220 _affected_flags.erase(*file); 221 return L4_EOK; 222 } 223 } 224 225 /* No queued events. */ 226 227 _notified.wait(guard); 228 } 229 230 return L4_EOK; 231 } 232 233 /* Wait for notifications from a single file. */ 234 235 long FileNotifier::wait_file(file_t *file) 236 { 237 std::unique_lock<std::mutex> guard(_lock); 238 239 while (1) 240 { 241 notify_flags_t recorded = _affected_flags[file]; 242 243 if (recorded) 244 { 245 file->notifications = recorded; 246 _affected_flags.erase(file); 247 return L4_EOK; 248 } 249 250 /* No recorded events for the file. */ 251 252 _notified.wait(guard); 253 } 254 255 return L4_EOK; 256 } 257 258 // vim: tabstop=2 expandtab shiftwidth=2