Fortran · 33857 bytes Raw Blame History
1 module fgof_watch
2 use, intrinsic :: iso_c_binding, only : c_associated, c_char, c_f_pointer, c_int, c_null_char, c_null_ptr, c_ptr, c_size_t
3 use fgof_watch_types, only : &
4 FGOF_WATCH_ERR_NONE, &
5 FGOF_WATCH_ERR_SNAPSHOT_FAILED, &
6 FGOF_WATCH_EVT_CREATED, &
7 FGOF_WATCH_EVT_MODIFIED, &
8 FGOF_WATCH_EVT_MOVED, &
9 FGOF_WATCH_EVT_NONE, &
10 FGOF_WATCH_EVT_REMOVED, &
11 watch_entry, &
12 watch_event, &
13 watch_options, &
14 watch_session
15 implicit none
16 private
17
18 public :: init_watch
19 public :: poll_watch
20 public :: reset_watch
21 public :: clear_ignore_prefixes
22 public :: set_ignore_prefixes
23
24 interface
25 integer(c_int) function fgof_watch_collect_snapshot_c(root, recursive, ignore_hidden, prefix_count, prefix_stride, prefixes, buffer, buffer_len) bind(C, name="fgof_watch_collect_snapshot")
26 import :: c_char, c_int, c_ptr, c_size_t
27 character(kind=c_char), intent(in) :: root(*)
28 integer(c_int), value :: recursive
29 integer(c_int), value :: ignore_hidden
30 integer(c_int), value :: prefix_count
31 integer(c_int), value :: prefix_stride
32 character(kind=c_char), intent(in) :: prefixes(*)
33 type(c_ptr), intent(out) :: buffer
34 integer(c_size_t), intent(out) :: buffer_len
35 end function fgof_watch_collect_snapshot_c
36
37 subroutine fgof_watch_free_buffer_c(buffer) bind(C, name="fgof_watch_free_buffer")
38 import :: c_ptr
39 type(c_ptr), value :: buffer
40 end subroutine fgof_watch_free_buffer_c
41 end interface
42
43 contains
44
45 subroutine init_watch(session, root, options)
46 type(watch_session), intent(out) :: session
47 character(len=*), intent(in) :: root
48 type(watch_options), intent(in), optional :: options
49 integer :: snapshot_status
50 character(len=:), allocatable :: snapshot_message
51
52 if (present(options)) then
53 session%options = options
54 end if
55
56 session%root = root
57 session%active = (len(root) > 0)
58
59 if (.not. session%active) then
60 allocate(session%entries(0))
61 allocate(session%pending_events(0))
62 allocate(session%pending_remaining(0))
63 call clear_watch_error(session)
64 return
65 end if
66
67 call collect_snapshot(root, session%options, session%entries, snapshot_status, snapshot_message)
68 allocate(session%pending_events(0))
69 allocate(session%pending_remaining(0))
70 if (snapshot_status /= 0) then
71 call set_watch_error(session, FGOF_WATCH_ERR_SNAPSHOT_FAILED, snapshot_message)
72 deallocate(session%entries)
73 allocate(session%entries(0))
74 else
75 call clear_watch_error(session)
76 end if
77 end subroutine init_watch
78
79 function poll_watch(session) result(events)
80 type(watch_session), intent(inout) :: session
81 type(watch_event), allocatable :: events(:)
82 type(watch_entry), allocatable :: current_entries(:)
83 integer :: snapshot_status
84 character(len=:), allocatable :: snapshot_message
85
86 if (.not. session%active) then
87 allocate(events(0))
88 return
89 end if
90
91 call collect_snapshot(session%root, session%options, current_entries, snapshot_status, snapshot_message)
92 if (snapshot_status /= 0) then
93 call set_watch_error(session, FGOF_WATCH_ERR_SNAPSHOT_FAILED, snapshot_message)
94 allocate(events(0))
95 return
96 end if
97
98 call clear_watch_error(session)
99 events = diff_snapshots(session%entries, current_entries, session%options)
100 if (session%options%debounce_polls > 0) then
101 events = debounce_event_batch(session, events)
102 end if
103 call move_alloc(current_entries, session%entries)
104 end function poll_watch
105
106 subroutine reset_watch(session)
107 type(watch_session), intent(inout) :: session
108
109 if (allocated(session%root)) then
110 deallocate(session%root)
111 end if
112
113 if (allocated(session%entries)) then
114 deallocate(session%entries)
115 end if
116
117 if (allocated(session%pending_events)) then
118 deallocate(session%pending_events)
119 end if
120
121 if (allocated(session%pending_remaining)) then
122 deallocate(session%pending_remaining)
123 end if
124
125 session%options = watch_options()
126 session%active = .false.
127 call clear_watch_error(session)
128 end subroutine reset_watch
129
130 subroutine clear_watch_error(session)
131 type(watch_session), intent(inout) :: session
132
133 session%last_error_code = FGOF_WATCH_ERR_NONE
134 if (allocated(session%last_error_message)) then
135 deallocate(session%last_error_message)
136 end if
137 session%last_error_message = ""
138 end subroutine clear_watch_error
139
140 subroutine set_watch_error(session, code, message)
141 type(watch_session), intent(inout) :: session
142 integer, intent(in) :: code
143 character(len=*), intent(in) :: message
144
145 session%last_error_code = code
146 if (allocated(session%last_error_message)) then
147 deallocate(session%last_error_message)
148 end if
149 session%last_error_message = trim(message)
150 end subroutine set_watch_error
151
152 subroutine set_ignore_prefixes(options, prefixes)
153 type(watch_options), intent(inout) :: options
154 character(len=*), intent(in) :: prefixes(:)
155 integer :: i
156 integer :: width
157
158 call clear_ignore_prefixes(options)
159 if (size(prefixes) == 0) return
160
161 width = max(1, max_string_length(prefixes))
162 allocate(character(len=width) :: options%ignore_prefixes(size(prefixes)))
163 allocate(options%ignore_prefix_lengths(size(prefixes)))
164 do i = 1, size(prefixes)
165 options%ignore_prefixes(i) = prefixes(i)
166 options%ignore_prefix_lengths(i) = len(prefixes(i))
167 end do
168 end subroutine set_ignore_prefixes
169
170 subroutine clear_ignore_prefixes(options)
171 type(watch_options), intent(inout) :: options
172
173 if (allocated(options%ignore_prefixes)) then
174 deallocate(options%ignore_prefixes)
175 end if
176 if (allocated(options%ignore_prefix_lengths)) then
177 deallocate(options%ignore_prefix_lengths)
178 end if
179 end subroutine clear_ignore_prefixes
180
181 subroutine collect_snapshot(root, options, entries, status_code, status_message)
182 character(len=*), intent(in) :: root
183 type(watch_options), intent(in) :: options
184 type(watch_entry), allocatable, intent(out) :: entries(:)
185 integer, intent(out) :: status_code
186 character(len=:), allocatable, intent(out) :: status_message
187 type(c_ptr) :: raw_ptr
188 integer(c_int) :: status
189 integer(c_size_t) :: raw_len
190 character(kind=c_char), allocatable :: c_root(:)
191 character(kind=c_char), allocatable :: c_prefixes(:)
192 integer(c_int) :: prefix_count
193 integer(c_int) :: prefix_stride
194 character(kind=c_char), pointer :: raw_chars(:)
195
196 c_root = to_c_string(root)
197 call pack_ignore_prefixes(options, prefix_count, prefix_stride, c_prefixes)
198 raw_ptr = c_null_ptr
199 raw_len = 0_c_size_t
200 status_code = 0
201 status_message = ""
202
203 status = fgof_watch_collect_snapshot_c( &
204 c_root, &
205 merge(1_c_int, 0_c_int, options%recursive), &
206 merge(1_c_int, 0_c_int, options%ignore_hidden), &
207 prefix_count, &
208 prefix_stride, &
209 c_prefixes, &
210 raw_ptr, &
211 raw_len &
212 )
213 if (status /= 0_c_int) then
214 allocate(entries(0))
215 if (c_associated(raw_ptr)) call fgof_watch_free_buffer_c(raw_ptr)
216 status_code = int(status)
217 status_message = errno_message("snapshot collection failed", int(status))
218 return
219 end if
220
221 if (.not. c_associated(raw_ptr) .or. raw_len == 0_c_size_t) then
222 allocate(entries(0))
223 if (c_associated(raw_ptr)) call fgof_watch_free_buffer_c(raw_ptr)
224 return
225 end if
226
227 call c_f_pointer(raw_ptr, raw_chars, [int(raw_len)])
228 call parse_snapshot_buffer(raw_chars, int(raw_len), entries)
229 call fgof_watch_free_buffer_c(raw_ptr)
230
231 call filter_entries(root, options, entries)
232 call sort_entries(entries)
233 end subroutine collect_snapshot
234
235 subroutine pack_ignore_prefixes(options, count, stride, buffer)
236 type(watch_options), intent(in) :: options
237 integer(c_int), intent(out) :: count
238 integer(c_int), intent(out) :: stride
239 character(kind=c_char), allocatable, intent(out) :: buffer(:)
240 integer :: i
241 integer :: j
242 integer :: width
243 integer :: offset
244
245 if (.not. allocated(options%ignore_prefixes)) then
246 count = 0_c_int
247 stride = 0_c_int
248 buffer = empty_c_string()
249 return
250 end if
251
252 if (size(options%ignore_prefixes) == 0) then
253 count = 0_c_int
254 stride = 0_c_int
255 buffer = empty_c_string()
256 return
257 end if
258
259 width = max_string_length(options%ignore_prefixes) + 1
260 count = int(size(options%ignore_prefixes), c_int)
261 stride = int(width, c_int)
262 allocate(buffer(size(options%ignore_prefixes) * width))
263 buffer = c_null_char
264
265 do i = 1, size(options%ignore_prefixes)
266 offset = (i - 1) * width
267 do j = 1, ignore_prefix_length(options, i)
268 buffer(offset + j) = options%ignore_prefixes(i)(j:j)
269 end do
270 buffer(offset + ignore_prefix_length(options, i) + 1) = c_null_char
271 end do
272 end subroutine pack_ignore_prefixes
273
274 subroutine filter_entries(root, options, entries)
275 character(len=*), intent(in) :: root
276 type(watch_options), intent(in) :: options
277 type(watch_entry), allocatable, intent(inout) :: entries(:)
278 type(watch_entry), allocatable :: filtered(:)
279 integer :: i
280
281 allocate(filtered(0))
282 do i = 1, size(entries)
283 if (entry_is_ignored(root, options, entries(i))) cycle
284 call append_entry(filtered, entries(i))
285 end do
286 call move_alloc(filtered, entries)
287 end subroutine filter_entries
288
289 logical function entry_is_ignored(root, options, entry) result(ignored)
290 character(len=*), intent(in) :: root
291 type(watch_options), intent(in) :: options
292 type(watch_entry), intent(in) :: entry
293
294 ignored = .false.
295
296 if (options%ignore_hidden) then
297 if (contains_hidden_segment(path_after_root(root, entry%path))) then
298 ignored = .true.
299 return
300 end if
301 end if
302
303 if (path_matches_ignore_prefix(options, entry%path)) then
304 ignored = .true.
305 end if
306 end function entry_is_ignored
307
308 logical function path_matches_ignore_prefix(options, path) result(matches)
309 type(watch_options), intent(in) :: options
310 character(len=*), intent(in) :: path
311 integer :: i
312 integer :: prefix_len
313
314 matches = .false.
315 if (.not. allocated(options%ignore_prefixes)) return
316
317 do i = 1, size(options%ignore_prefixes)
318 prefix_len = ignore_prefix_length(options, i)
319 if (prefix_len == 0) cycle
320 if (len(path) == prefix_len) then
321 if (path == options%ignore_prefixes(i)(1:prefix_len)) then
322 matches = .true.
323 return
324 end if
325 end if
326 if (len(path) > prefix_len) then
327 if (path(1:prefix_len) == options%ignore_prefixes(i)(1:prefix_len) .and. path(prefix_len + 1:prefix_len + 1) == "/") then
328 matches = .true.
329 return
330 end if
331 end if
332 end do
333 end function path_matches_ignore_prefix
334
335 integer function ignore_prefix_length(options, index_value) result(length_value)
336 type(watch_options), intent(in) :: options
337 integer, intent(in) :: index_value
338
339 if (allocated(options%ignore_prefix_lengths)) then
340 length_value = options%ignore_prefix_lengths(index_value)
341 else
342 length_value = len_trim(options%ignore_prefixes(index_value))
343 end if
344 end function ignore_prefix_length
345
346 function path_after_root(root, path) result(relative)
347 character(len=*), intent(in) :: root
348 character(len=*), intent(in) :: path
349 character(len=:), allocatable :: relative
350
351 if (path == root) then
352 relative = basename_text(root)
353 return
354 end if
355
356 if (len(path) > len(root)) then
357 if (path(1:len(root)) == root .and. path(len(root) + 1:len(root) + 1) == "/") then
358 relative = path(len(root) + 2:)
359 return
360 end if
361 end if
362
363 relative = path
364 end function path_after_root
365
366 logical function contains_hidden_segment(path) result(has_hidden)
367 character(len=*), intent(in) :: path
368 integer :: i
369 integer :: start
370 integer :: n
371
372 has_hidden = .false.
373 n = len(path)
374 if (n == 0) return
375
376 start = 1
377 do i = 1, n + 1
378 if (i <= n .and. path(i:i) /= "/") cycle
379 if (i > start) then
380 if (path(start:start) == ".") then
381 has_hidden = .true.
382 return
383 end if
384 end if
385 start = i + 1
386 end do
387 end function contains_hidden_segment
388
389 function basename_text(path) result(name)
390 character(len=*), intent(in) :: path
391 character(len=:), allocatable :: name
392 integer :: i
393
394 do i = len(path), 1, -1
395 if (path(i:i) == "/") then
396 name = path(i + 1:)
397 return
398 end if
399 end do
400
401 name = path
402 end function basename_text
403
404 function diff_snapshots(previous_entries, current_entries, options) result(events)
405 type(watch_entry), intent(in) :: previous_entries(:)
406 type(watch_entry), intent(in) :: current_entries(:)
407 type(watch_options), intent(in) :: options
408 type(watch_event), allocatable :: events(:)
409 type(watch_entry), allocatable :: created(:)
410 type(watch_entry), allocatable :: modified(:)
411 type(watch_entry), allocatable :: removed(:)
412 integer :: i
413 integer :: j
414
415 allocate(created(0))
416 allocate(modified(0))
417 allocate(removed(0))
418
419 i = 1
420 j = 1
421 do while (i <= size(previous_entries) .or. j <= size(current_entries))
422 if (i > size(previous_entries)) then
423 call append_entry(created, current_entries(j))
424 j = j + 1
425 else if (j > size(current_entries)) then
426 call append_entry(removed, previous_entries(i))
427 i = i + 1
428 else if (previous_entries(i)%path == current_entries(j)%path) then
429 if (entry_changed(previous_entries(i), current_entries(j))) then
430 if (.not. current_entries(j)%is_directory) then
431 call append_entry(modified, current_entries(j))
432 end if
433 end if
434 i = i + 1
435 j = j + 1
436 else if (entry_less(previous_entries(i)%path, current_entries(j)%path)) then
437 call append_entry(removed, previous_entries(i))
438 i = i + 1
439 else
440 call append_entry(created, current_entries(j))
441 j = j + 1
442 end if
443 end do
444
445 events = build_event_batch(created, modified, removed, options)
446 call sort_events(events)
447 end function diff_snapshots
448
449 function build_event_batch(created, modified, removed, options) result(events)
450 type(watch_entry), intent(in) :: created(:)
451 type(watch_entry), intent(in) :: modified(:)
452 type(watch_entry), intent(in) :: removed(:)
453 type(watch_options), intent(in) :: options
454 type(watch_event), allocatable :: events(:)
455 logical, allocatable :: created_used(:)
456 logical, allocatable :: removed_used(:)
457 integer :: i
458 integer :: j
459
460 allocate(events(0))
461 allocate(created_used(size(created)))
462 allocate(removed_used(size(removed)))
463 created_used = .false.
464 removed_used = .false.
465
466 do i = 1, size(removed)
467 do j = 1, size(created)
468 if (created_used(j)) cycle
469 if (removed(i)%inode <= 0) cycle
470 if (removed(i)%inode /= created(j)%inode) cycle
471 if (removed(i)%is_directory .neqv. created(j)%is_directory) cycle
472 if (created(j)%is_directory .and. .not. options%emit_directory_events) then
473 created_used(j) = .true.
474 removed_used(i) = .true.
475 exit
476 end if
477 call append_event(events, FGOF_WATCH_EVT_MOVED, created(j)%path, removed(i)%path, created(j)%is_directory)
478 created_used(j) = .true.
479 removed_used(i) = .true.
480 exit
481 end do
482 end do
483
484 do i = 1, size(created)
485 if (created_used(i)) cycle
486 if (created(i)%is_directory .and. .not. options%emit_directory_events) cycle
487 call append_event(events, FGOF_WATCH_EVT_CREATED, created(i)%path, "", created(i)%is_directory)
488 end do
489
490 do i = 1, size(modified)
491 if (modified(i)%is_directory .and. .not. options%emit_directory_events) cycle
492 call append_event(events, FGOF_WATCH_EVT_MODIFIED, modified(i)%path, "", modified(i)%is_directory)
493 end do
494
495 do i = 1, size(removed)
496 if (removed_used(i)) cycle
497 if (removed(i)%is_directory .and. .not. options%emit_directory_events) cycle
498 call append_event(events, FGOF_WATCH_EVT_REMOVED, removed(i)%path, "", removed(i)%is_directory)
499 end do
500 end function build_event_batch
501
502 function debounce_event_batch(session, raw_events) result(events)
503 type(watch_session), intent(inout) :: session
504 type(watch_event), intent(in) :: raw_events(:)
505 type(watch_event), allocatable :: events(:)
506 logical, allocatable :: touched(:)
507 integer :: i
508 integer :: index
509
510 if (.not. allocated(session%pending_events)) allocate(session%pending_events(0))
511 if (.not. allocated(session%pending_remaining)) allocate(session%pending_remaining(0))
512
513 allocate(touched(size(session%pending_events)))
514 touched = .false.
515
516 do i = 1, size(raw_events)
517 index = find_related_pending_event(session%pending_events, raw_events(i))
518 if (index > 0) then
519 call merge_pending_event(session, index, raw_events(i))
520 if (index <= size(session%pending_events)) then
521 touched = resize_logical_flags(touched, size(session%pending_events))
522 touched(index) = .true.
523 end if
524 else
525 call append_pending_event(session, raw_events(i), session%options%debounce_polls)
526 touched = resize_logical_flags(touched, size(session%pending_events))
527 touched(size(touched)) = .true.
528 end if
529 end do
530
531 do i = 1, size(session%pending_remaining)
532 if (touched(i)) cycle
533 session%pending_remaining(i) = session%pending_remaining(i) - 1
534 end do
535
536 call emit_ready_events(session, events)
537 end function debounce_event_batch
538
539 subroutine merge_pending_event(session, index, incoming)
540 type(watch_session), intent(inout) :: session
541 integer, intent(in) :: index
542 type(watch_event), intent(in) :: incoming
543 type(watch_event) :: merged
544 logical :: drop_pending
545
546 call merge_event_pair(session%pending_events(index), incoming, merged, drop_pending)
547 if (drop_pending) then
548 call remove_pending_event(session, index)
549 return
550 end if
551
552 session%pending_events(index) = merged
553 session%pending_remaining(index) = session%options%debounce_polls
554 end subroutine merge_pending_event
555
556 subroutine merge_event_pair(existing, incoming, merged, drop_pending)
557 type(watch_event), intent(in) :: existing
558 type(watch_event), intent(in) :: incoming
559 type(watch_event), intent(out) :: merged
560 logical, intent(out) :: drop_pending
561
562 drop_pending = .false.
563 merged = incoming
564
565 select case (existing%kind)
566 case (FGOF_WATCH_EVT_CREATED)
567 select case (incoming%kind)
568 case (FGOF_WATCH_EVT_CREATED)
569 merged = incoming
570 case (FGOF_WATCH_EVT_MODIFIED)
571 merged = existing
572 case (FGOF_WATCH_EVT_REMOVED)
573 if (incoming%path == existing%path) then
574 drop_pending = .true.
575 else
576 merged = incoming
577 end if
578 case (FGOF_WATCH_EVT_MOVED)
579 if (incoming%previous_path == existing%path) then
580 merged = existing
581 merged%path = incoming%path
582 else
583 merged = incoming
584 end if
585 end select
586
587 case (FGOF_WATCH_EVT_MODIFIED)
588 select case (incoming%kind)
589 case (FGOF_WATCH_EVT_CREATED)
590 merged = incoming
591 case (FGOF_WATCH_EVT_MODIFIED)
592 merged = incoming
593 case (FGOF_WATCH_EVT_REMOVED)
594 merged = incoming
595 case (FGOF_WATCH_EVT_MOVED)
596 merged = incoming
597 end select
598
599 case (FGOF_WATCH_EVT_REMOVED)
600 select case (incoming%kind)
601 case (FGOF_WATCH_EVT_CREATED)
602 if (incoming%path == existing%path) then
603 merged%kind = FGOF_WATCH_EVT_MODIFIED
604 merged%path = incoming%path
605 merged%previous_path = ""
606 merged%is_directory = incoming%is_directory
607 else
608 merged = incoming
609 end if
610 case default
611 merged = incoming
612 end select
613
614 case (FGOF_WATCH_EVT_MOVED)
615 select case (incoming%kind)
616 case (FGOF_WATCH_EVT_MODIFIED)
617 if (incoming%path == existing%path) then
618 merged = existing
619 else
620 merged = incoming
621 end if
622 case (FGOF_WATCH_EVT_REMOVED)
623 if (incoming%path == existing%path) then
624 merged = incoming
625 else
626 merged = incoming
627 end if
628 case (FGOF_WATCH_EVT_MOVED)
629 if (incoming%previous_path == existing%path) then
630 merged = existing
631 merged%path = incoming%path
632 else
633 merged = incoming
634 end if
635 case (FGOF_WATCH_EVT_CREATED)
636 merged = incoming
637 end select
638 end select
639 end subroutine merge_event_pair
640
641 subroutine emit_ready_events(session, events)
642 type(watch_session), intent(inout) :: session
643 type(watch_event), allocatable, intent(out) :: events(:)
644 type(watch_event), allocatable :: ready(:)
645 integer :: i
646
647 allocate(ready(0))
648 i = 1
649 do while (i <= size(session%pending_events))
650 if (session%pending_remaining(i) > 0) then
651 i = i + 1
652 cycle
653 end if
654
655 call append_event_object(ready, session%pending_events(i))
656 call remove_pending_event(session, i)
657 end do
658
659 call move_alloc(ready, events)
660 end subroutine emit_ready_events
661
662 subroutine append_pending_event(session, event, remaining)
663 type(watch_session), intent(inout) :: session
664 type(watch_event), intent(in) :: event
665 integer, intent(in) :: remaining
666 type(watch_event), allocatable :: grown_events(:)
667 integer, allocatable :: grown_remaining(:)
668 integer :: n
669
670 n = size(session%pending_events)
671 allocate(grown_events(n + 1))
672 allocate(grown_remaining(n + 1))
673
674 if (n > 0) then
675 grown_events(1:n) = session%pending_events
676 grown_remaining(1:n) = session%pending_remaining
677 end if
678
679 grown_events(n + 1) = event
680 grown_remaining(n + 1) = remaining
681
682 call move_alloc(grown_events, session%pending_events)
683 call move_alloc(grown_remaining, session%pending_remaining)
684 end subroutine append_pending_event
685
686 subroutine remove_pending_event(session, index)
687 type(watch_session), intent(inout) :: session
688 integer, intent(in) :: index
689 type(watch_event), allocatable :: kept_events(:)
690 integer, allocatable :: kept_remaining(:)
691 integer :: n
692
693 n = size(session%pending_events)
694 if (index < 1 .or. index > n) return
695
696 allocate(kept_events(n - 1))
697 allocate(kept_remaining(n - 1))
698
699 if (index > 1) then
700 kept_events(1:index - 1) = session%pending_events(1:index - 1)
701 kept_remaining(1:index - 1) = session%pending_remaining(1:index - 1)
702 end if
703
704 if (index < n) then
705 kept_events(index:n - 1) = session%pending_events(index + 1:n)
706 kept_remaining(index:n - 1) = session%pending_remaining(index + 1:n)
707 end if
708
709 call move_alloc(kept_events, session%pending_events)
710 call move_alloc(kept_remaining, session%pending_remaining)
711 end subroutine remove_pending_event
712
713 integer function find_related_pending_event(pending_events, incoming) result(index_found)
714 type(watch_event), intent(in) :: pending_events(:)
715 type(watch_event), intent(in) :: incoming
716 integer :: i
717
718 index_found = 0
719 do i = 1, size(pending_events)
720 if (events_related(pending_events(i), incoming)) then
721 index_found = i
722 return
723 end if
724 end do
725 end function find_related_pending_event
726
727 logical function events_related(left, right) result(related)
728 type(watch_event), intent(in) :: left
729 type(watch_event), intent(in) :: right
730
731 related = .false.
732 if (same_nonempty_text(left%path, right%path)) related = .true.
733 if (same_nonempty_text(left%path, right%previous_path)) related = .true.
734 if (same_nonempty_text(left%previous_path, right%path)) related = .true.
735 if (same_nonempty_text(left%previous_path, right%previous_path)) related = .true.
736 end function events_related
737
738 logical function same_nonempty_text(left, right) result(matches)
739 character(len=*), intent(in) :: left
740 character(len=*), intent(in) :: right
741
742 matches = .false.
743 if (len(left) == 0 .or. len(right) == 0) return
744 matches = (left == right)
745 end function same_nonempty_text
746
747 function resize_logical_flags(flags, new_size) result(resized)
748 logical, intent(in) :: flags(:)
749 integer, intent(in) :: new_size
750 logical, allocatable :: resized(:)
751 integer :: copy_count
752
753 allocate(resized(new_size))
754 resized = .false.
755 copy_count = min(size(flags), new_size)
756 if (copy_count > 0) resized(1:copy_count) = flags(1:copy_count)
757 end function resize_logical_flags
758
759 logical function entry_changed(previous_entry, current_entry) result(changed)
760 type(watch_entry), intent(in) :: previous_entry
761 type(watch_entry), intent(in) :: current_entry
762
763 changed = .false.
764 if (previous_entry%inode /= current_entry%inode) changed = .true.
765 if (previous_entry%size /= current_entry%size) changed = .true.
766 if (previous_entry%mtime_sec /= current_entry%mtime_sec) changed = .true.
767 if (previous_entry%mtime_nsec /= current_entry%mtime_nsec) changed = .true.
768 if (previous_entry%is_directory .neqv. current_entry%is_directory) changed = .true.
769 end function entry_changed
770
771 subroutine parse_snapshot_buffer(buffer, count, entries)
772 character(kind=c_char), intent(in) :: buffer(:)
773 integer, intent(in) :: count
774 type(watch_entry), allocatable, intent(out) :: entries(:)
775 integer :: i
776 integer :: field_count
777 integer :: record_count
778 integer :: start
779 integer :: terminator_index
780 character(len=:), allocatable :: kind_text
781 character(len=:), allocatable :: inode_text
782 character(len=:), allocatable :: size_text
783 character(len=:), allocatable :: mtime_sec_text
784 character(len=:), allocatable :: mtime_nsec_text
785 character(len=:), allocatable :: path_text
786
787 if (count <= 0) then
788 allocate(entries(0))
789 return
790 end if
791
792 field_count = 0
793 do i = 1, count
794 if (buffer(i) == c_null_char) field_count = field_count + 1
795 end do
796 if (field_count == 0 .or. mod(field_count, 6) /= 0) then
797 allocate(entries(0))
798 return
799 end if
800
801 record_count = field_count / 6
802 allocate(entries(record_count))
803 start = 1
804 do i = 1, record_count
805 call next_nul_field(buffer, count, start, kind_text, terminator_index)
806 if (terminator_index == 0) exit
807 call next_nul_field(buffer, count, start, inode_text, terminator_index)
808 if (terminator_index == 0) exit
809 call next_nul_field(buffer, count, start, size_text, terminator_index)
810 if (terminator_index == 0) exit
811 call next_nul_field(buffer, count, start, mtime_sec_text, terminator_index)
812 if (terminator_index == 0) exit
813 call next_nul_field(buffer, count, start, mtime_nsec_text, terminator_index)
814 if (terminator_index == 0) exit
815 call next_nul_field(buffer, count, start, path_text, terminator_index)
816 if (terminator_index == 0) exit
817 call parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entries(i))
818 end do
819 end subroutine parse_snapshot_buffer
820
821 subroutine parse_snapshot_fields(kind_text, inode_text, size_text, mtime_sec_text, mtime_nsec_text, path_text, entry)
822 character(len=*), intent(in) :: kind_text
823 character(len=*), intent(in) :: inode_text
824 character(len=*), intent(in) :: size_text
825 character(len=*), intent(in) :: mtime_sec_text
826 character(len=*), intent(in) :: mtime_nsec_text
827 character(len=*), intent(in) :: path_text
828 type(watch_entry), intent(out) :: entry
829 integer :: iostat_value
830
831 entry = watch_entry()
832 if (len(kind_text) == 0) then
833 entry%path = ""
834 return
835 end if
836
837 entry%is_directory = (kind_text(1:1) == "D")
838 read(inode_text, *, iostat=iostat_value) entry%inode
839 if (iostat_value /= 0) entry%inode = 0
840 read(size_text, *, iostat=iostat_value) entry%size
841 if (iostat_value /= 0) entry%size = 0
842 read(mtime_sec_text, *, iostat=iostat_value) entry%mtime_sec
843 if (iostat_value /= 0) entry%mtime_sec = 0
844 read(mtime_nsec_text, *, iostat=iostat_value) entry%mtime_nsec
845 if (iostat_value /= 0) entry%mtime_nsec = 0
846 entry%path = path_text
847 end subroutine parse_snapshot_fields
848
849 subroutine next_nul_field(buffer, count, start_index, field, terminator_index)
850 character(kind=c_char), intent(in) :: buffer(:)
851 integer, intent(in) :: count
852 integer, intent(inout) :: start_index
853 character(len=:), allocatable, intent(out) :: field
854 integer, intent(out) :: terminator_index
855 integer :: i
856 integer :: width
857
858 if (start_index > count) then
859 field = ""
860 terminator_index = 0
861 return
862 end if
863
864 terminator_index = 0
865 do i = start_index, count
866 if (buffer(i) == c_null_char) then
867 terminator_index = i
868 exit
869 end if
870 end do
871 if (terminator_index == 0) then
872 field = ""
873 return
874 end if
875
876 width = terminator_index - start_index
877 allocate(character(len=width) :: field)
878 do i = 1, width
879 field(i:i) = char(iachar(buffer(start_index + i - 1)))
880 end do
881 start_index = terminator_index + 1
882 end subroutine next_nul_field
883
884 function errno_message(prefix, errnum) result(message)
885 character(len=*), intent(in) :: prefix
886 integer, intent(in) :: errnum
887 character(len=:), allocatable :: message
888 character(len=32) :: code_text
889
890 write(code_text, '(I0)') errnum
891 message = trim(prefix) // " (errno=" // trim(code_text) // ")"
892 end function errno_message
893
894 function to_c_string(str) result(buf)
895 character(len=*), intent(in) :: str
896 character(kind=c_char), allocatable :: buf(:)
897 integer :: i
898 integer :: n
899
900 n = len(str)
901 allocate(buf(n + 1))
902 do i = 1, n
903 buf(i) = str(i:i)
904 end do
905 buf(n + 1) = c_null_char
906 end function to_c_string
907
908 function empty_c_string() result(buf)
909 character(kind=c_char), allocatable :: buf(:)
910
911 allocate(buf(1))
912 buf(1) = c_null_char
913 end function empty_c_string
914
915 subroutine append_entry(entries, entry)
916 type(watch_entry), allocatable, intent(inout) :: entries(:)
917 type(watch_entry), intent(in) :: entry
918 type(watch_entry), allocatable :: grown(:)
919 integer :: n
920
921 n = size(entries)
922 allocate(grown(n + 1))
923 if (n > 0) grown(1:n) = entries
924 grown(n + 1) = entry
925 call move_alloc(grown, entries)
926 end subroutine append_entry
927
928 subroutine append_event(events, kind, path, previous_path, is_directory)
929 type(watch_event), allocatable, intent(inout) :: events(:)
930 integer, intent(in) :: kind
931 character(len=*), intent(in) :: path
932 character(len=*), intent(in) :: previous_path
933 logical, intent(in) :: is_directory
934 type(watch_event), allocatable :: grown(:)
935 integer :: n
936
937 n = size(events)
938 allocate(grown(n + 1))
939 if (n > 0) grown(1:n) = events
940 grown(n + 1)%kind = kind
941 grown(n + 1)%is_directory = is_directory
942 grown(n + 1)%path = path
943 if (len(previous_path) > 0) then
944 grown(n + 1)%previous_path = previous_path
945 else
946 grown(n + 1)%previous_path = ""
947 end if
948 call move_alloc(grown, events)
949 end subroutine append_event
950
951 subroutine append_event_object(events, event)
952 type(watch_event), allocatable, intent(inout) :: events(:)
953 type(watch_event), intent(in) :: event
954 type(watch_event), allocatable :: grown(:)
955 integer :: n
956
957 n = size(events)
958 allocate(grown(n + 1))
959 if (n > 0) grown(1:n) = events
960 grown(n + 1) = event
961 call move_alloc(grown, events)
962 end subroutine append_event_object
963
964 subroutine sort_entries(entries)
965 type(watch_entry), intent(inout) :: entries(:)
966 type(watch_entry) :: temp
967 integer :: i
968 integer :: j
969
970 do i = 1, size(entries) - 1
971 do j = i + 1, size(entries)
972 if (entry_less(entries(j)%path, entries(i)%path)) then
973 temp = entries(i)
974 entries(i) = entries(j)
975 entries(j) = temp
976 end if
977 end do
978 end do
979 end subroutine sort_entries
980
981 subroutine sort_events(events)
982 type(watch_event), intent(inout) :: events(:)
983 type(watch_event) :: temp
984 integer :: i
985 integer :: j
986
987 do i = 1, size(events) - 1
988 do j = i + 1, size(events)
989 if (event_less(events(j), events(i))) then
990 temp = events(i)
991 events(i) = events(j)
992 events(j) = temp
993 end if
994 end do
995 end do
996 end subroutine sort_events
997
998 logical function entry_less(left, right) result(is_less)
999 character(len=*), intent(in) :: left
1000 character(len=*), intent(in) :: right
1001 integer :: i
1002 integer :: limit
1003
1004 limit = min(len(left), len(right))
1005 do i = 1, limit
1006 if (left(i:i) < right(i:i)) then
1007 is_less = .true.
1008 return
1009 end if
1010 if (left(i:i) > right(i:i)) then
1011 is_less = .false.
1012 return
1013 end if
1014 end do
1015
1016 is_less = (len(left) < len(right))
1017 end function entry_less
1018
1019 integer function max_string_length(values) result(max_len)
1020 character(len=*), intent(in) :: values(:)
1021 integer :: i
1022
1023 max_len = 1
1024 do i = 1, size(values)
1025 max_len = max(max_len, len(values(i)))
1026 end do
1027 end function max_string_length
1028
1029 logical function event_less(left, right) result(is_less)
1030 type(watch_event), intent(in) :: left
1031 type(watch_event), intent(in) :: right
1032
1033 if (left%path /= right%path) then
1034 is_less = entry_less(left%path, right%path)
1035 return
1036 end if
1037
1038 if (left%kind /= right%kind) then
1039 is_less = (left%kind < right%kind)
1040 return
1041 end if
1042
1043 is_less = entry_less(left%previous_path, right%previous_path)
1044 end function event_less
1045
1046 end module fgof_watch
1047