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