QUESTION
inotify event만 모으면 directory의 현재 상태를 항상 복원할 수 있는가?
inotify는 filesystem object 변화의 event stream을 제공하지만 완전한 transaction log나 recursive snapshot은 아니다. queue가 넘치면 IN_Q_OVERFLOW 한 건만 남고 어떤 변화가 빠졌는지 알 수 없어 전체 rescan이 필요하다.
directory watch는 현재 directory 자체와 바로 아래 entry event를 보고할 뿐 새로 생긴 하위 directory에 자동 watch를 추가하지 않는다. event 처리와 watch 추가 사이의 변경을 다루는 재검사 절차가 필요하다.
STRUCTURE
구조 그림
wd=1 /project
- IN_CREATE
- IN_MOVED_*
- child name record
wd=2 /project/src
- 별도 watch mark
- inode event
- rename cookie
새 /project/build
- CREATE|ISDIR
- scan 필요
- add_watch 필요
queue
- wd/mask/cookie/name
- 가변 길이 record
- IN_Q_OVERFLOW → rescan
watch는 recursive하지 않다. 새 subdirectory에는 별도 mark가 필요하고 queue overflow 뒤에는 전체 tree를 다시 scan해야 한다.
CALL PATH
호출 흐름
event를 최종 상태로 쓰지 말고 rescan을 줄이는 invalidation hint로 사용한다. event sequence와 실제 directory scan 사이 일관성 요구를 따로 정의한다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| fs/notify/inotify/inotify_user.c | inotify_add_watch(), inotify_read() | wd 생성과 가변 길이 event 반환 |
| fs/notify/inotify/inotify_fsnotify.c | inotify_handle_inode_event() | fsnotify event를 inotify 형식으로 변환 |
| fs/notify/notification.c | fsnotify_add_event() | group queue limit와 overflow 처리 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 watch_dir.c -o watch_dir01#define _GNU_SOURCE
02#include <errno.h>
03#include <stdio.h>
04#include <sys/inotify.h>
05#include <unistd.h>
06
07int main(int argc, char **argv)
08{
09 const char *path = argc > 1 ? argv[1] : ".";
10 int fd = inotify_init1(IN_CLOEXEC);
11 if (fd < 0)
12 return 1;
13 int wd = inotify_add_watch(fd, path,
14 IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_Q_OVERFLOW);
15 if (wd < 0)
16 return 1;
17
18 _Alignas(struct inotify_event) char buffer[8192];
19 for (;;) {
20 ssize_t count = read(fd, buffer, sizeof(buffer));
21 if (count < 0 && errno == EINTR)
22 continue;
23 if (count <= 0)
24 break;
25 for (char *p = buffer; p < buffer + count; ) {
26 struct inotify_event *event = (struct inotify_event *)p;
27 printf("wd=%d mask=0x%x cookie=%u name=%s\n",
28 event->wd, event->mask, event->cookie,
29 event->len ? event->name : "-");
30 p += sizeof(*event) + event->len;
31 }
32 }
33 close(fd);
34 return 0;
35}
CODE NOTES
코드 조각별 설명
inotify_init1(IN_CLOEXEC)event queue를 fd로 만들고 exec 상속을 막는다. epoll에 합칠 경우 IN_NONBLOCK도 함께 사용한다.
IN_MOVED_FROM | IN_MOVED_TO같은 inotify instance 안의 rename 양쪽 event는 cookie로 짝지을 수 있다. 다른 filesystem 이동은 create/delete처럼 보일 수 있다.
_Alignas(struct inotify_event)char buffer가 inotify_event를 읽기에 필요한 정렬을 갖도록 한다.
p < buffer + count한 read에 여러 가변 길이 record가 들어오므로 반환 byte 범위 안에서 직접 순회한다.
sizeof(*event) + event->lenevent->len에는 name과 padding이 포함된다. strlen(name)만 더하면 다음 record 정렬을 잃는다.
DETAILS
세부 동작
wd는 pathname이 아니다
watch descriptor는 inotify instance 안의 정수 key다. watched object가 rename돼도 inode watch는 이어질 수 있고, IN_IGNORED 뒤 같은 wd 숫자가 재사용될 수 있다.
application map에는 wd와 generation, 현재 추정 path를 함께 두고 stale event를 구분한다.
rename 짝은 timeout이 필요하다
IN_MOVED_FROM을 받았지만 corresponding IN_MOVED_TO가 같은 queue에 오지 않을 수 있다. watched tree 밖으로 이동하거나 overflow가 생길 수 있기 때문이다.
cookie map entry를 무한히 보관하지 말고 짧은 timeout 뒤 delete/out-of-tree 이동으로 확정한다.
overflow는 전체 rescan 경계다
IN_Q_OVERFLOW 뒤에는 어떤 entry가 바뀌었는지 추론할 수 없다. queue를 비우고 authoritative directory scan으로 상태를 다시 만들고 watch set도 검증한다.
event 처리 속도, max_queued_events, 폭발적인 build output을 계측하되 limit 증가만으로 정확성 protocol을 대신하지 않는다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
inotify group | inotify_init1에서 생기고 fd close에서 queue와 mark가 해제된다 | queue length, overflow state |
watch mark/wd | add_watch에서 inode에 붙고 rm_watch/object delete/close에서 제거된다 | mask, inode, generation |
inotify_event record | kernel queue에서 read buffer로 복사된 뒤 application이 소비한다 | mask, cookie, len, name |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| 변경이 누락됨 | IN_Q_OVERFLOW 또는 watch 추가 전 변화 | overflow 처리와 full rescan |
| rename 짝이 없음 | watched tree 밖 이동/queue 경계 | cookie timeout policy |
| 하위 directory 변화 없음 | recursive watch를 자동으로 기대 | 새 directory scan + add_watch |
LAB
직접 확인
- 감시 directory에서 mv로 이름을 바꾸고 FROM/TO cookie가 같은지 확인한다.
- 짧은 시간에 대량 파일을 만들어 queue overflow를 유도하고 rescan 경로를 테스트한다.
- 새 하위 directory 생성 event를 받자마자 내부 scan과 watch 추가를 수행하는 recursive tracker를 구현한다.
./watch_dir .strace -e trace=inotify_init1,inotify_add_watch,read,close ./watch_dir .PRIMARY REFERENCES