unison

Fork of Unison, a bi-directional file synchronization tool
git clone git://git.laack.co/unison.git
Log | Files | Refs | README | LICENSE

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