fen_stubs.c (8292B)
1 /* Unison file synchronizer: src/fsmonitor/solaris/fen_stubs.c */ 2 /* Copyright 2021, Tõivo Leedjärv 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include <errno.h> 19 #include <port.h> 20 #include <stdio.h> 21 #include <string.h> 22 #include <assert.h> 23 24 #include <caml/mlvalues.h> 25 #include <caml/memory.h> 26 #include <caml/alloc.h> 27 #include <caml/unixsupport.h> 28 #include <caml/version.h> 29 30 #if OCAML_VERSION_MAJOR < 5 31 #define caml_unix_error unix_error 32 #define caml_uerror uerror 33 #endif 34 35 36 /* FILE_TRUNC was added in illumos and may not be present in Solaris */ 37 #ifndef FILE_TRUNC 38 #define FILE_TRUNC 0 39 #endif 40 41 /* We define the flags here rather than pass in from OCaml code 42 * because they're constant and it reduces extra processing. */ 43 #define EV_FLAGS_FOLLOW FILE_MODIFIED | FILE_ATTRIB 44 #define EV_FLAGS_NOFOLLOW EV_FLAGS_FOLLOW | FILE_NOFOLLOW 45 46 47 CAMLprim value unsn_port_create(value unit) 48 { 49 CAMLparam0(); 50 51 int port = port_create(); 52 if (port == -1) { 53 caml_uerror("port_create", Nothing); 54 } 55 56 CAMLreturn(Val_int(port)); 57 } 58 59 CAMLprim value unsn_port_close(value v) 60 { 61 CAMLparam1(v); 62 63 int status = close(Int_val(v)); 64 if (status == -1) { 65 caml_uerror("port_close", Nothing); 66 } 67 68 CAMLreturn(Val_unit); 69 } 70 71 72 static int ev_flag_table[] = { 73 FILE_ACCESS, FILE_MODIFIED, FILE_ATTRIB, 74 FILE_DELETE, FILE_RENAME_TO, FILE_RENAME_FROM, 75 FILE_TRUNC, 76 FILE_NOFOLLOW, UNMOUNTED, MOUNTEDOVER, 0 77 }; 78 79 struct event_obj { 80 int cookie; 81 int prev_events; 82 struct file_obj fo; 83 }; 84 85 static struct event_obj * EvObj_val(value v) 86 { 87 return (struct event_obj *) (v & ~1); 88 } 89 90 static value Val_EvObj(struct event_obj *x) 91 { 92 assert(((uintptr_t) x & 1) == 0); 93 return (value) x | 1; 94 } 95 96 static void free_eo(struct event_obj *eo) 97 { 98 if (eo == NULL) return; 99 100 free(eo->fo.fo_name); 101 free(eo); 102 } 103 104 CAMLprim value unsn_free_event_object(value eo) 105 { 106 CAMLparam1(eo); 107 free_eo(EvObj_val(eo)); 108 CAMLreturn(Val_unit); 109 } 110 111 static int port_associate_aux(int port, struct event_obj *eo, int follow) 112 { 113 eo->prev_events = 0; 114 115 /* Due to the synchronous nature of the protocol between Unison and 116 * fsmonitor, there is no need to pass in stat times to port_associate(). 117 * Passing in stat times allows detecting changes that have occurred 118 * between the stat() call and port_associate(), by way of simple time 119 * comparison. 120 * 121 * If stat times would be passed in to port_associate() then the call to 122 * stat() must remain in the C stub. It has been detected that passing in 123 * [struct stat] value from OCaml will report incorrect change events 124 * because the times lose precision in the nanoseconds. (Not sure why it 125 * happens, it really shouldn't.) */ 126 127 eo->fo.fo_atime.tv_sec = eo->fo.fo_atime.tv_nsec = 128 eo->fo.fo_mtime.tv_sec = eo->fo.fo_mtime.tv_nsec = 129 eo->fo.fo_ctime.tv_sec = eo->fo.fo_ctime.tv_nsec = 0; 130 131 /* The event object must remain valid and allocated for the entire duration 132 * the port association is valid. It must be freed only after an event is 133 * received or after port_dissociate(). */ 134 135 return port_associate(port, PORT_SOURCE_FILE, (uintptr_t) &(eo->fo), 136 !follow ? EV_FLAGS_NOFOLLOW : EV_FLAGS_FOLLOW, eo); 137 } 138 139 CAMLprim value unsn_port_associate(value port, value path, value follow, value cookie) 140 { 141 CAMLparam4(port, path, follow, cookie); 142 CAMLlocal1(result); 143 144 struct event_obj *eo = malloc(sizeof(struct event_obj)); 145 if (eo == NULL) { 146 caml_unix_error(ENOMEM, "port_associate", path); 147 } 148 149 eo->cookie = Int_val(cookie); 150 eo->fo.fo_name = strdup(String_val(path)); 151 152 int status = port_associate_aux(Int_val(port), eo, Bool_val(follow)); 153 if (status == -1) { 154 free_eo(eo); 155 caml_uerror("port_associate", path); 156 } 157 158 /* Returning a malloc'ed pointer as a value is not fully safe as it's not 159 * possible to restrict the value from being used in OCaml code after having 160 * been freed (likewise, not possible to prevent double freeing). 161 * 162 * free_event_object() must be called after receiving an event (unless 163 * re-associating) and after calling port_dissociate(). 164 * 165 * A safer solution is to create a custom data block with a finalizer to 166 * free the memory. The current implementation does not use this solution 167 * to avoid the overhead. */ 168 CAMLreturn(Val_EvObj(eo)); 169 } 170 171 CAMLprim value unsn_port_reassociate(value port, value eo_val, value follow) 172 { 173 CAMLparam3(port, eo_val, follow); 174 175 struct event_obj *eo = EvObj_val(eo_val); 176 if (eo == NULL) { 177 caml_unix_error(EINVAL, "port_reassociate", 178 caml_copy_string("NULL eo; this indicates a BUG!")); 179 } 180 181 if (eo->prev_events & FILE_EXCEPTION) { 182 CAMLreturn(Val_false); 183 } 184 185 /* The object to associate is expected to exist, except when re-associating. 186 * It may happen that the file or directory has already been deleted without 187 * the delete event having been delivered. This may happen when recursively 188 * deleting files and directories. The deletion of a file within a directory 189 * will first produce a directory modification event, which also dissociates 190 * the directory. Now, when the directory is subsequently deleted, the delete 191 * event is lost before re-association takes place. For this reason, when 192 * re-associating, ENOENT errors must not be raised as exceptions. */ 193 int status = port_associate_aux(Int_val(port), eo, Bool_val(follow)); 194 if (status == -1) { 195 CAMLreturn(Val_false); 196 } 197 198 CAMLreturn(Val_true); 199 } 200 201 CAMLprim value unsn_port_dissociate(value port, value eo_val) 202 { 203 CAMLparam2(port, eo_val); 204 205 struct event_obj *eo = EvObj_val(eo_val); 206 if (eo == NULL) { 207 caml_unix_error(EINVAL, "port_dissociate", 208 caml_copy_string("NULL eo; this indicates a BUG!")); 209 } 210 211 int status = port_dissociate(Int_val(port), PORT_SOURCE_FILE, (uintptr_t) &(eo->fo)); 212 if (status == -1 && errno != ENOENT) { 213 caml_uerror("port_dissociate", caml_copy_string(eo->fo.fo_name)); 214 } 215 216 CAMLreturn(Val_unit); 217 } 218 219 CAMLprim value unsn_port_get(value port) 220 { 221 CAMLparam1(port); 222 CAMLlocal3(result, l, tmpl); 223 int status; 224 uint_t cnt = 0; 225 port_event_t *pel; 226 struct timespec timeout; 227 228 timeout.tv_sec = timeout.tv_nsec = 0; 229 230 result = Val_emptylist; 231 232 status = port_getn(Int_val(port), NULL, 0, &cnt, &timeout); 233 if (status == -1 && errno != ETIME && errno != EINTR) { 234 fprintf(stderr, " unison-fsmonitor: [port_getn] Warning: error getting event count: %i\n", errno); 235 } 236 237 if (!cnt) { 238 CAMLreturn(result); 239 } 240 241 pel = malloc(cnt * sizeof(port_event_t)); 242 if (pel == NULL) { 243 caml_unix_error(ENOMEM, "port_getn", Nothing); 244 } 245 246 status = port_getn(Int_val(port), pel, cnt, &cnt, &timeout); 247 if (status == -1 && errno != ETIME && errno != EINTR) { 248 free(pel); 249 caml_uerror("port_getn", Nothing); 250 } 251 252 for (int j = 0; j < cnt; j++) { 253 if (pel[j].portev_source != PORT_SOURCE_FILE) { 254 continue; 255 } 256 257 struct event_obj *eo = pel[j].portev_user; 258 259 if (eo == NULL) { 260 free(pel); 261 caml_unix_error(EINVAL, "portev_user", 262 caml_copy_string("NULL eo; this indicates a BUG!")); 263 } 264 265 eo->prev_events = pel[j].portev_events; 266 267 l = Val_emptylist; 268 269 for (int i = 0; ev_flag_table[i]; i++) { 270 if (!(pel[j].portev_events & ev_flag_table[i])) { 271 continue; 272 } 273 274 tmpl = caml_alloc_small(2, Tag_cons); 275 Field(tmpl, 0) = Val_int(i); 276 Field(tmpl, 1) = l; 277 l = tmpl; 278 } 279 280 tmpl = caml_alloc_small(4, 0); 281 Field(tmpl, 0) = port; 282 Field(tmpl, 1) = Val_EvObj(eo); 283 Field(tmpl, 2) = Val_int(eo->cookie); 284 Field(tmpl, 3) = l; 285 l = tmpl; 286 287 tmpl = caml_alloc_small(2, Tag_cons); 288 Field(tmpl, 0) = l; 289 Field(tmpl, 1) = result; 290 result = tmpl; 291 } 292 293 free(pel); 294 295 CAMLreturn(result); 296 } 297