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. */ 104 105 _affected_flags[file] |= flags; 106 _affected.push_back(file); 107 108 /* Notify any waiting caller. */ 109 110 _notified.notify_one(); 111 } 112 113 /* Listen for notifications. */ 114 115 void FileNotifier::mainloop() 116 { 117 ipc_message_t msg; 118 l4_umword_t label; 119 120 while (1) 121 { 122 ipc_message_wait(&msg, &label); 123 124 /* Clear lower label bits. */ 125 126 label = label & ~3UL; 127 128 /* Ignore erroneous messages. */ 129 130 if (l4_ipc_error(msg.tag, l4_utcb())) 131 continue; 132 133 /* Reply to notifications. */ 134 135 ipc_message_reply(&msg); 136 137 /* Interpret gate labels as file objects. */ 138 139 file_t *file = (file_t *) label; 140 notify_flags_t flags = ipc_message_get_word(&msg, 0); 141 142 /* Register the notification. */ 143 144 _notify(file, flags); 145 } 146 147 ipc_message_free(&msg); 148 } 149 150 /* Invoke the mainloop in a thread. */ 151 152 static void *notifier_mainloop(void *data) 153 { 154 FileNotifier *notifier = reinterpret_cast<FileNotifier *>(data); 155 156 notifier->mainloop(); 157 return 0; 158 } 159 160 /* Start listening for notifications. */ 161 162 long FileNotifier::start() 163 { 164 if (_started) 165 return L4_EOK; 166 167 pthread_t thread; 168 pthread_attr_t attr; 169 long err; 170 171 pthread_attr_init(&attr); 172 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 173 174 err = pthread_create(&thread, &attr, notifier_mainloop, this); 175 if (err) 176 return err; 177 178 _thread = pthread_l4_cap(thread); 179 _started = true; 180 181 return L4_EOK; 182 } 183 184 /* Wait for notification events on files. */ 185 186 long FileNotifier::wait(file_t **file) 187 { 188 std::unique_lock<std::mutex> guard(_lock); 189 190 while (1) 191 { 192 if (!_affected.empty()) 193 { 194 *file = _affected.front(); 195 _affected.pop_front(); 196 197 (*file)->notifications = _affected_flags[*file]; 198 _affected_flags.erase(*file); 199 return L4_EOK; 200 } 201 else 202 _notified.wait(guard); 203 } 204 205 return L4_EOK; 206 } 207 208 // vim: tabstop=2 expandtab shiftwidth=2