? 优质资源分享 ?
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
? Python实战微信订餐小程序 ? | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
?Python量化交易实战? | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
目录
关于文件跟踪,我们有很多的实际场景,比如查看某个系统日志的输出,当有变化时立即体现,以便进行问题排查;比如查看文件结尾的内容是啥,总之是刚需了。
返回顶部### 1. 自己实现的文件跟踪
我们平时做功能开发时,也会遇到类似的需求,比如当有人传输文件到某个位置后,我们需要触发后续处理操作。
那么,我们自己实现的话,也就只能通过定时检查文件是否变化,比如检测最后修改时间,从而感知到变化。如果要想让文件传输完成之后,再进行动作,则一般需要用户上传一个空的done文件,以报备事务处理完成。
那么,如果是系统实现呢?如题,tail -f 的文件跟踪,是否也是这样实现呢?想想感觉应该不会这么简单,毕竟操作系统肯定会比自己厉害此的。
返回顶部### 2. tail -f的源码位置
我们知道,每个linux系统安装之后,都会有很多的基础命令可用,比如cat/vi/sh/top/tail... 那么,是否这些命令就是内核提供的东西呢?实际上不是的,linux kernel 部分,并未提供相应的实现,即这些工具类的都不是在kernel中实现的,而是作为外部核心工具包组件实现。即 coreutils 。 这也是我们想分析一些工具类实现时需要注意的,因为它可能在你找不到的地方。
源码访问路径: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tail.c
返回顶部### 3. tail -f 实现
tail -f 作为核心工具,虽与操作系统一起出现,但毕竟是独立实现,所以还是需要考虑具体的操作系统环境,所以它的实现往往需要分情况进行处理。
即它有多种实现,一种是和我们一样,定时去检测文件化,然后输出到控制台;第二种则高级些,利用操作系统提供的文件通知功能,进行实时内容输出。具体如下:
int
main (int argc, char **argv)
{
enum header\_mode header\_mode = multiple\_files;
bool ok = true;
/* If from\_start, the number of items to skip before printing; otherwise,
the number of items at the end of the file to print. Although the type
is signed, the value is never negative. */
uintmax\_t n\_units = DEFAULT\_N\_LINES;
size\_t n\_files;
char **file;
struct File\_spec *F;
size\_t i;
bool obsolete\_option;
/* The number of seconds to sleep between iterations.
During one iteration, every file name or descriptor is checked to
see if it has changed. */
double sleep\_interval = 1.0;
initialize\_main (&argc, &argv);
set\_program\_name (argv[0]);
setlocale (LC\_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close\_stdout);
have\_read\_stdin = false;
count\_lines = true;
forever = from\_start = print\_headers = false;
line\_end = '\n';
obsolete\_option = parse\_obsolete\_option (argc, argv, &n\_units);
argc -= obsolete\_option;
argv += obsolete\_option;
parse\_options (argc, argv, &n\_units, &header\_mode, &sleep\_interval);
/* To start printing with item N\_UNITS from the start of the file, skip
N\_UNITS - 1 items. 'tail -n +0' is actually meaningless, but for Unix
compatibility it's treated the same as 'tail -n +1'. */
if (from\_start)
{
if (n\_units)
--n\_units;
}
if (optind < argc)
{
n\_files = argc - optind;
file = argv + optind;
}
else
{
static char *dummy\_stdin = (char *) "-";
n\_files = 1;
file = &dummy\_stdin;
}
{
bool found\_hyphen = false;
for (i = 0; i < n\_files; i++)
if (STREQ (file[i], "-"))
found\_hyphen = true;
/* When following by name, there must be a name. */
if (found\_hyphen && follow\_mode == Follow\_name)
die (EXIT\_FAILURE, 0, \_("cannot follow %s by name"), quoteaf ("-"));
/* When following forever, and not using simple blocking, warn if
any file is '-' as the stats() used to check for input are ineffective.
This is only a warning, since tail's output (before a failing seek,
and that from any non-stdin files) might still be useful. */
if (forever && found\_hyphen)
{
struct stat in\_stat;
bool blocking\_stdin;
blocking\_stdin = (pid == 0 && follow\_mode == Follow\_descriptor
&& n\_files == 1 && ! fstat (STDIN\_FILENO, &in\_stat)
&& ! S\_ISREG (in\_stat.st\_mode));
if (! blocking\_stdin && isatty (STDIN\_FILENO))
error (0, 0, \_("warning: following standard input"
" indefinitely is ineffective"));
}
}
/* Don't read anything if we'll never output anything. */
if (! n\_units && ! forever && ! from\_start)
return EXIT\_SUCCESS;
F = xnmalloc (n\_files, sizeof *F);
for (i = 0; i < n\_files; i++)
F[i].name = file[i];
if (header\_mode == always
|| (header\_mode == multiple\_files && n\_files > 1))
print\_headers = true;
xset\_binary\_mode (STDOUT\_FILENO, O\_BINARY);
for (i = 0; i < n\_files; i++)
ok &= tail\_file (&F[i], n\_units);
if (forever && ignore\_fifo\_and\_pipe (F, n\_files))
{
/* If stdout is a fifo or pipe, then monitor it
so that we exit if the reader goes away. */
struct stat out\_stat;
if (fstat (STDOUT\_FILENO, &out\_stat) < 0)
die (EXIT\_FAILURE, errno, \_("standard output"));
monitor\_output = (S\_ISFIFO (out\_stat.st\_mode)
|| (HAVE\_FIFO\_PIPES != 1 && isapipe (STDOUT\_FILENO)));
#if HAVE\_INOTIFY
/* tailable\_stdin() checks if the user specifies stdin via "-",
or implicitly by providing no arguments. If so, we won't use inotify.
Technically, on systems with a working /dev/stdin, we *could*,
but would it be worth it? Verifying that it's a real device
and hooked up to stdin is not trivial, while reverting to
non-inotify-based tail\_forever is easy and portable.
any\_remote\_file() checks if the user has specified any
files that reside on remote file systems. inotify is not used
in this case because it would miss any updates to the file
that were not initiated from the local system.
any\_non\_remote\_file() checks if the user has specified any
files that don't reside on remote file systems. inotify is not used
if there are no open files, as we can't determine if those file
will be on a remote file system.
any\_symlinks() checks if the user has specified any symbolic links.
inotify is not used in this case because it returns updated \_targets\_
which would not match the specified names. If we tried to always
use the target names, then we would miss changes to the symlink itself.
ok is false when one of the files specified could not be opened for
reading. In this case and when following by descriptor,
tail\_forever\_inotify() cannot be used (in its current implementation).
FIXME: inotify doesn't give any notification when a new
(remote) file or directory is mounted on top a watched file.
When follow\_mode == Follow\_name we would ideally like to detect that.
Note if there is a change to the original file then we'll
recheck it and follow the new file, or ignore it if the
file has changed to being remote.
FIXME-maybe: inotify has a watch descriptor per inode, and hence with
our current hash implementation will only --follow data for one
of the names when multiple hardlinked files are specified, or
for one name when a name is specified multiple times. */
if (!disable\_inotify && (tailable\_stdin (F, n\_files)
|| any\_remote\_file (F, n\_files)
|| ! any\_non\_remote\_file (F, n\_files)
|| any\_symlinks (F, n\_files)
|| any\_non\_regular\_fifo (F, n\_files)
|| (!ok && follow\_mode == Follow\_descriptor)))
disable\_inotify = true;
if (!disable\_inotify)
{
int wd = inotify\_init ();
if (0 <= wd)
{
/* Flush any output from tail\_file, now, since
tail\_forever\_inotify flushes only after writing,
not before reading. */
if (fflush (stdout) != 0)
die (EXIT\_FAILURE, errno, \_("write error"));
Hash\_table *ht;
tail\_forever\_inotify (wd, F, n\_files, sleep\_interval, &ht);
hash\_free (ht);
close (wd);
errno = 0;
}
error (0, errno, \_("inotify cannot be used, reverting to polling"));
}
#endif
disable\_inotify = true;
tail\_forever (F, n\_files, sleep\_interval);
}
if (have\_read\_stdin && close (STDIN\_FILENO) < 0)
die (EXIT\_FAILURE, errno, "-");
main\_exit (ok ? EXIT\_SUCCESS : EXIT\_FAILURE);
}
简单地说两种实现就是: tail_forever 轮询式跟踪; tail_forever_inotify 更新时通知; 下面我们就简单瞅瞅两个具体实现吧。
3.1. 定时扫描跟踪实现
这里的定时扫描,和我们自己处理的不太一样的地方,主要是它会跟踪多个文件,而如果是我们自己实现,则一般只会跟踪一个文件即可。
/* Tail N\_FILES files forever, or until killed.
The pertinent information for each file is stored in an entry of F.
Loop over each of them, doing an fstat to see if they have changed size,
and an occasional open/fstat to see if any dev/ino pair has changed.
If none of them have changed size in one iteration, sleep for a
while and try again. Continue until the user interrupts us. */
static void
tail\_forever (struct File\_spec *f, size\_t n\_files, double sleep\_interval)
{
/* Use blocking I/O as an optimization, when it's easy. */
bool blocking = (pid == 0 && follow\_mode == Follow\_descriptor
&& n\_files == 1 && f[0].fd != -1 && ! S\_ISREG (f[0].mode));
size\_t last;
bool writer\_is\_dead = false;
last = n\_files - 1;
// 一直循环检测,直到用户主动终止进程
while (true)
{
size\_t i;
bool any\_input = false;
for (i = 0; i < n\_files; i++)
{
int fd;
char const *name;
mode\_t mode;
struct stat stats;
uintmax\_t bytes\_read;
if (f[i].ignore)
continue;
if (f[i].fd < 0)
{
recheck (&f[i], blocking);
continue;
}
fd = f[i].fd;
name = pretty\_name (&f[i]);
mode = f[i].mode;
if (f[i].blocking != blocking)
{
int old\_flags = fcntl (fd, F\_GETFL);
int new\_flags = old\_flags | (blocking ? 0 : O\_NONBLOCK);
if (old\_flags < 0
|| (new\_flags != old\_flags
&& fcntl (fd, F\_SETFL, new\_flags) == -1))
{
/* Don't update f[i].blocking if fcntl fails. */
if (S\_ISREG (f[i].mode) && errno == EPERM)
{
/* This happens when using tail -f on a file with
the append-only attribute. */
}
else
die (EXIT\_FAILURE, errno,
\_("%s: cannot change nonblocking mode"),
quotef (name));
}
else
f[i].blocking = blocking;
}
if (!f[i].blocking)
{
// 使用 fstat 进行文件变更检测,结果存入 stats 变量中
if (fstat (fd, &stats) != 0)
{
f[i].fd = -1;
f[i].errnum = errno;
error (0, errno, "%s", quotef (name));
close (fd); /* ignore failure */
continue;
}
// 通过比较 mtime 判断文件是否发生变化
if (f[i].mode == stats.st\_mode
&& (! S\_ISREG (stats.st\_mode) || f[i].size == stats.st\_size)
&& timespec\_cmp (f[i].mtime, get\_stat\_mtime (&stats)) == 0)
{
if ((max\_n\_unchanged\_stats\_between\_opens
<= f[i].n\_unchanged\_stats++)
&& follow\_mode == Follow\_name)
{
recheck (&f[i], f[i].blocking);
f[i].n\_unchanged\_stats = 0;
}
continue;
}
/* This file has changed. Print out what we can, and
then keep looping. */
// 记录最后一次变更情况
f[i].mtime = get\_stat\_mtime (&stats);
f[i].mode = stats.st\_mode;
/* reset counter */
f[i].n\_unchanged\_stats = 0;
/* XXX: This is only a heuristic, as the file may have also
been truncated and written to if st\_size >= size
(in which case we ignore new data <= size). */
if (S\_ISREG (mode) && stats.st\_size < f[i].size)
{
error (0, 0, \_("%s: file truncated"), quotef (name));
/* Assume the file was truncated to 0,
and therefore output all "new" data. */
xlseek (fd, 0, SEEK\_SET, name);
f[i].size = 0;
}
if (i != last)
{
if (print\_headers)
write\_header (name);
last = i;
}
}
/* Don't read more than st\_size on networked file systems
because it was seen on glusterfs at least, that st\_size
may be smaller than the data read on a \_subsequent\_ stat call. */
uintmax\_t bytes\_to\_read;
if (f[i].blocking)
bytes\_to\_read = COPY\_A\_BUFFER;
else if (S\_ISREG (mode) && f[i].remote)
bytes\_to\_read = stats.st\_size - f[i].size;
else
bytes\_to\_read = COPY\_TO\_EOF;
// 输出变更内容到控制台
bytes\_read = dump\_remainder (false, name, fd, bytes\_to\_read);
any\_input |= (bytes\_read != 0);
f[i].size += bytes\_read;
}
if (! any\_live\_files (f, n\_files))
{
error (0, 0, \_("no files remaining"));
break;
}
if ((!any\_input || blocking) && fflush (stdout) != 0)
die (EXIT\_FAILURE, errno, \_("write error"));
check\_output\_alive ();
/* If nothing was read, sleep and/or check for dead writers. */
if (!any\_input)
{
if (writer\_is\_dead)
break;
/* Once the writer is dead, read the files once more to
avoid a race condition. */
writer\_is\_dead = (pid != 0
&& kill (pid, 0) != 0
/* Handle the case in which you cannot send a
signal to the writer, so kill fails and sets
errno to EPERM. */
&& errno != EPERM);
// 等待下一次轮询
if (!writer\_is\_dead && xnanosleep (sleep\_interval))
die (EXIT\_FAILURE, errno, \_("cannot read realtime clock"));
}
}
}
我们运行tail -f 命令时,就是控制台会一直停留在输出界面,等待跟踪结果,也就是说这时的tail进程,会一直在前台运行。这时这个进程交独占用户界面,如果用户不想跟踪了,那么就必须主动终止进程,即ctrl+c 或其他进程终止方式。所以,实现还是比较简单的,如表面意思,就是不停地检测文件,输出内容,如果其中一些文件失效,则跳过即可。
检测主要依赖于函数: fstat (fd, &stats) , 通过比较 mtime 进行文件是否变化判定。大致不出意料。
3.2. 基于异步通知的跟踪实现
上一个实现是基于轮询的方式实现的,这个实现是基于通知的文件跟踪。基于轮询的实现,要求有比较合适的轮询间隔,太长不容易发现变更,太短则容易导致系统压力大。而基于通知的实现,则优雅许多,它只会在文件发生了变化进进行一次通知,其他时间几乎不会占用系统资源(实际上还是有事件轮询的资源消耗)。我们来看一下。
它会先进行 inotify_init(); 然后再进行 tail_forever_inotify;
/* Attempt to tail N\_FILES files forever, or until killed.
Check modifications using the inotify events system.
Exit if finished or on fatal error; return to revert to polling. */
static void
tail\_forever\_inotify (int wd, struct File\_spec *f, size\_t n\_files,
double sleep\_interval, Hash\_table **wd\_to\_namep)
{
# if TAIL\_TEST\_SLEEP
/* Delay between open() and inotify\_add\_watch()
to help trigger different cases. */
xnanosleep (1000000);
# endif
unsigned int max\_realloc = 3;
/* Map an inotify watch descriptor to the name of the file it's watching. */
Hash\_table *wd\_to\_name;
bool found\_watchable\_file = false;
bool tailed\_but\_unwatchable = false;
bool found\_unwatchable\_dir = false;
bool no\_inotify\_resources = false;
bool writer\_is\_dead = false;
struct File\_spec *prev\_fspec;
size\_t evlen = 0;
char *evbuf;
size\_t evbuf\_off = 0;
size\_t len = 0;
wd\_to\_name = hash\_initialize (n\_files, NULL, wd\_hasher, wd\_comparator, NULL);
if (! wd\_to\_name)
xalloc\_die ();
*wd\_to\_namep = wd\_to\_name;
/* The events mask used with inotify on files (not directories). */
uint32\_t inotify\_wd\_mask = IN\_MODIFY;
/* TODO: Perhaps monitor these events in Follow\_descriptor mode also,
to tag reported file names with "deleted", "moved" etc. */
if (follow\_mode == Follow\_name)
inotify\_wd\_mask |= (IN\_ATTRIB | IN\_DELETE\_SELF | IN\_MOVE\_SELF);
/* Add an inotify watch for each watched file. If -F is specified then watch
its parent directory too, in this way when they re-appear we can add them
again to the watch list. */
size\_t i;
// 依次设置跟踪标识到文件的通知列表中
for (i = 0; i < n\_files; i++)
{
if (!f[i].ignore)
{
size\_t fnlen = strlen (f[i].name);
if (evlen < fnlen)
evlen = fnlen;
f[i].wd = -1;
if (follow\_mode == Follow\_name)
{
size\_t dirlen = dir\_len (f[i].name);
char prev = f[i].name[dirlen];
f[i].basename\_start = last\_component (f[i].name) - f[i].name;
f[i].name[dirlen] = '\0';
/* It's fine to add the same directory more than once.
In that case the same watch descriptor is returned. */
f[i].parent\_wd = inotify\_add\_watch (wd, dirlen ? f[i].name : ".",
(IN\_CREATE | IN\_DELETE
| IN\_MOVED\_TO | IN\_ATTRIB
| IN\_DELETE\_SELF));
f[i].name[dirlen] = prev;
if (f[i].parent\_wd < 0)
{
if (errno != ENOSPC) /* suppress confusing error. */
error (0, errno, \_("cannot watch parent directory of %s"),
quoteaf (f[i].name));
else
error (0, 0, \_("inotify resources exhausted"));
found\_unwatchable\_dir = true;
/* We revert to polling below. Note invalid uses
of the inotify API will still be diagnosed. */
break;
}
}
// 注意回调到全局
f[i].wd = inotify\_add\_watch (wd, f[i].name, inotify\_wd\_mask);
if (f[i].wd < 0)
{
if (f[i].fd != -1) /* already tailed. */
tailed\_but\_unwatchable = true;
if (errno == ENOSPC || errno == ENOMEM)
{
no\_inotify\_resources = true;
error (0, 0, \_("inotify resources exhausted"));
break;
}
else if (errno != f[i].errnum)
error (0, errno, \_("cannot watch %s"), quoteaf (f[i].name));
continue;
}
if (hash\_insert (wd\_to\_name, &(f[i])) == NULL)
xalloc\_die ();
// 只要有一个文件需要处理,就需要保持进程的跟踪状态
found\_watchable\_file = true;
}
}
/* Linux kernel 2.6.24 at least has a bug where eventually, ENOSPC is always
returned by inotify\_add\_watch. In any case we should revert to polling
when there are no inotify resources. Also a specified directory may not
be currently present or accessible, so revert to polling. Also an already
tailed but unwatchable due rename/unlink race, should also revert. */
if (no\_inotify\_resources || found\_unwatchable\_dir
|| (follow\_mode == Follow\_descriptor && tailed\_but\_unwatchable))
return;
if (follow\_mode == Follow\_descriptor && !found\_watchable\_file)
exit (EXIT\_FAILURE);
prev\_fspec = &(f[n\_files - 1]);
/* Check files again. New files or data can be available since last time we
checked and before they are watched by inotify. */
for (i = 0; i < n\_files; i++)
{
if (! f[i].ignore)
{
/* check for new files. */
if (follow\_mode == Follow\_name)
recheck (&(f[i]), false);
else if (f[i].fd != -1)
{
/* If the file was replaced in the small window since we tailed,
then assume the watch is on the wrong item (different to
that we've already produced output for), and so revert to
polling the original descriptor. */
struct stat stats;
if (stat (f[i].name, &stats) == 0
&& (f[i].dev != stats.st\_dev || f[i].ino != stats.st\_ino))
{
error (0, errno, \_("%s was replaced"),
quoteaf (pretty\_name (&(f[i]))));
return;
}
}
/* check for new data. */
check\_fspec (&f[i], &prev\_fspec);
}
}
evlen += sizeof (struct inotify\_event) + 1;
evbuf = xmalloc (evlen);
/* Wait for inotify events and handle them. Events on directories
ensure that watched files can be re-added when following by name.
This loop blocks on the 'safe\_read' call until a new event is notified.
But when --pid=P is specified, tail usually waits via poll. */
while (true)
{
struct File\_spec *fspec;
struct inotify\_event *ev;
void *void\_ev;
/* When following by name without --retry, and the last file has
been unlinked or renamed-away, diagnose it and return. */
if (follow\_mode == Follow\_name
&& ! reopen\_inaccessible\_files
&& hash\_get\_n\_entries (wd\_to\_name) == 0)
die (EXIT\_FAILURE, 0, \_("no files remaining"));
if (len <= evbuf\_off)
{
/* Poll for inotify events. When watching a PID, ensure
that a read from WD will not block indefinitely.
If MONITOR\_OUTPUT, also poll for a broken output pipe. */
int file\_change;
struct pollfd pfd[2];
do
{
/* How many ms to wait for changes. -1 means wait forever. */
int delay = -1;
if (pid)
{
if (writer\_is\_dead)
exit (EXIT\_SUCCESS);
writer\_is\_dead = (kill (pid, 0) != 0 && errno != EPERM);
if (writer\_is\_dead || sleep\_interval <= 0)
delay = 0;
else if (sleep\_interval < INT\_MAX / 1000 - 1)
{
/* delay = ceil (sleep\_interval * 1000), sans libm. */
double ddelay = sleep\_interval * 1000;
delay = ddelay;
delay += delay < ddelay;
}
}
pfd[0].fd = wd;
pfd[0].events = POLLIN;
pfd[1].fd = STDOUT\_FILENO;
pfd[1].events = pfd[1].revents = 0;
// 读取文件变更事件,当然还是会有超时处理,不然发生意外就不好了
file\_change = poll (pfd, monitor\_output + 1, delay);
}
while (file\_change == 0);
if (file\_change < 0)
die (EXIT\_FAILURE, errno,
\_("error waiting for inotify and output events"));
if (pfd[1].revents)
die\_pipe ();
len = safe\_read (wd, evbuf, evlen);
evbuf\_off = 0;
/* For kernels prior to 2.6.21, read returns 0 when the buffer
is too small. */
if ((len == 0 || (len == SAFE\_READ\_ERROR && errno == EINVAL))
&& max\_realloc--)
{
len = 0;
evlen *= 2;
evbuf = xrealloc (evbuf, evlen);
continue;
}
if (len == 0 || len == SAFE\_READ\_ERROR)
die (EXIT\_FAILURE, errno, \_("error reading inotify event"));
}
void\_ev = evbuf + evbuf\_off;
ev = void\_ev;
evbuf\_off += sizeof (*ev) + ev->len;
/* If a directory is deleted, IN\_DELETE\_SELF is emitted
with ev->name of length 0.
We need to catch it, otherwise it would wait forever,
as wd for directory becomes inactive. Revert to polling now. */
if ((ev->mask & IN\_DELETE\_SELF) && ! ev->len)
{
for (i = 0; i < n\_files; i++)
{
if (ev->wd == f[i].parent\_wd)
{
error (0, 0,
\_("directory containing watched file was removed"));
return;
}
}
}
// 遍历找出变化的文件
if (ev->len) /* event on ev->name in watched directory. */
{
size\_t j;
for (j = 0; j < n\_files; j++)
{
/* With N=hundreds of frequently-changing files, this O(N^2)
process might be a problem. FIXME: use a hash table? */
if (f[j].parent\_wd == ev->wd
&& STREQ (ev->name, f[j].name + f[j].basename\_start))
break;
}
/* It is not a watched file. */
if (j == n\_files)
continue;
fspec = &(f[j]);
int new\_wd = -1;
bool deleting = !! (ev->mask & IN\_DELETE);
if (! deleting)
{
/* Adding the same inode again will look up any existing wd. */
new\_wd = inotify\_add\_watch (wd, f[j].name, inotify\_wd\_mask);
}
if (! deleting && new\_wd < 0)
{
if (errno == ENOSPC || errno == ENOMEM)
{
error (0, 0, \_("inotify resources exhausted"));
return; /* revert to polling. */
}
else
{
/* Can get ENOENT for a dangling symlink for example. */
error (0, errno, \_("cannot watch %s"), quoteaf (f[j].name));
}
/* We'll continue below after removing the existing watch. */
}
/* This will be false if only attributes of file change. */
bool new\_watch;
new\_watch = (! deleting) && (fspec->wd < 0 || new\_wd != fspec->wd);
if (new\_watch)
{
if (0 <= fspec->wd)
{
inotify\_rm\_watch (wd, fspec->wd);
hash\_remove (wd\_to\_name, fspec);
}
fspec->wd = new\_wd;
if (new\_wd == -1)
continue;
/* If the file was moved then inotify will use the source file wd
for the destination file. Make sure the key is not present in
the table. */
struct File\_spec *prev = hash\_remove (wd\_to\_name, fspec);
if (prev && prev != fspec)
{
if (follow\_mode == Follow\_name)
recheck (prev, false);
prev->wd = -1;
close\_fd (prev->fd, pretty\_name (prev));
}
if (hash\_insert (wd\_to\_name, fspec) == NULL)
xalloc\_die ();
}
if (follow\_mode == Follow\_name)
recheck (fspec, false);
}
else
{
struct File\_spec key;
key.wd = ev->wd;
fspec = hash\_lookup (wd\_to\_name, &key);
}
if (! fspec)
continue;
if (ev->mask & (IN\_ATTRIB | IN\_DELETE | IN\_DELETE\_SELF | IN\_MOVE\_SELF))
{
/* Note for IN\_MOVE\_SELF (the file we're watching has
been clobbered via a rename) we leave the watch
in place since it may still be part of the set
of watched names. */
if (ev->mask & IN\_DELETE\_SELF)
{
inotify\_rm\_watch (wd, fspec->wd);
hash\_remove (wd\_to\_name, fspec);
}
/* Note we get IN\_ATTRIB for unlink() as st\_nlink decrements.
The usual path is a close() done in recheck() triggers
an IN\_DELETE\_SELF event as the inode is removed.
However sometimes open() will succeed as even though
st\_nlink is decremented, the dentry (cache) is not updated.
Thus we depend on the IN\_DELETE event on the directory
to trigger processing for the removed file. */
recheck (fspec, false);
continue;
}
// 输出变化的内容
check\_fspec (fspec, &prev\_fspec);
}
}
/* Output (new) data for FSPEC->fd.
PREV\_FSPEC records the last File\_spec for which we output. */
static void
check\_fspec (struct File\_spec *fspec, struct File\_spec **prev\_fspec)
{
struct stat stats;
char const *name;
if (fspec->fd == -1)
return;
name = pretty\_name (fspec);
if (fstat (fspec->fd, &stats) != 0)
{
fspec->errnum = errno;
close\_fd (fspec->fd, name);
fspec->fd = -1;
return;
}
/* XXX: This is only a heuristic, as the file may have also
been truncated and written to if st\_size >= size
(in which case we ignore new data <= size).
Though in the inotify case it's more likely we'll get
separate events for truncate() and write(). */
if (S\_ISREG (fspec->mode) && stats.st\_size < fspec->size)
{
error (0, 0, \_("%s: file truncated"), quotef (name));
xlseek (fspec->fd, 0, SEEK\_SET, name);
fspec->size = 0;
}
else if (S\_ISREG (fspec->mode) && stats.st\_size == fspec->size
&& timespec\_cmp (fspec->mtime, get\_stat\_mtime (&stats)) == 0)
return;
bool want\_header = print\_headers && (fspec != *prev\_fspec);
uintmax\_t bytes\_read = dump\_remainder (want\_header, name, fspec->fd,
COPY\_TO\_EOF);
fspec->size += bytes\_read;
if (bytes\_read)
{
*prev\_fspec = fspec;
if (fflush (stdout) != 0)
die (EXIT\_FAILURE, errno, \_("write error"));
}
}
基于通知的方式实现文件跟踪,明显是复杂了许多,首先是注册事件,然后是轮询事件,然后是事件处理。但是这样的实现,针对大量的文件跟踪是很省资源的呢。总之,是一种好的实现方式,算是一劳永逸吧。
比如我们的io实现方式就有:阻塞io, select/poll io, 异步io, ... 异步总是实现复杂,但是收益也是比较可观的一种方法。
转载请注明:xuhss » 关于linux的一点好奇心(四):tail -f文件跟踪实现