optimize a program with inotify - c

I write a program by inotify to check file change in loop for grab any changes to that.
but i think this can be done better.
can anybody write this code better?
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/inotify.h>
#include <sys/select.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
int event_check (int fd)
{
fd_set rfds;
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
/* Wait until an event happens or we get interrupted
by a signal that we catch */
return select (FD_SETSIZE, &rfds, NULL, NULL, NULL);
}
int main( )
{
int length, i = 0;
int fd;
int wd;
while(1){
i=0;
fd = inotify_init();
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch( fd, "/tmp/test", IN_CLOSE_WRITE);
if (event_check (fd) > 0)
{
char buffer[EVENT_BUF_LEN];
int count = 0;
length = read( fd, buffer, EVENT_BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
while ( i < length ) { struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
printf( "New file %s Editted.\n", event->name );
i += EVENT_SIZE + event->len;
}
}
}
inotify_rm_watch( fd, wd );
close( fd );
}

I think you only need one inotify_init for one program and only one inotify_add_watch for one directory.
Can you please paste the "init and add_watch outside the loop" version which you said doesn't work?

I've never used inotify, so its possible that this might be wrong but here's a couple of changes that I think might "improve" your code.
I don't think there's any reason to put either inotify_init or inotify_add_watch inside the loop. Just do this initialisation work once, before the loop.
I'm not sure why you've created the event_check function. You aren't specifying a timeout and you're only using a single file descriptor, so I think read will give you the same funcionality.

Related

Inotify to monitor a file notifies twice, even if the file is modified once

I am using inotify to monitor the a file, but am facing an issue when ever the monitored file is changed (file.xml), it notifies twice even though the modification is done once (am opening the file using vim writing to it and am closing the file),an i also see that sometimes it just notifies once and in majority times, it notifies twice, can i get some input regarding as to what i am doing wrong in the code.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <unistd.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
const char *file_path = "/home/file.xml";
int main( int argc, char **argv )
{
int length, i;
int fd;
int wd;
char buffer[BUF_LEN];
fd = inotify_init();
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch(fd,
file_path,
IN_ALL_EVENTS);
if(wd < 0) {
perror ("inotify_add_watch");
exit(EXIT_FAILURE);
}
while(1)
{
length = read( fd, buffer, BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
i = 0;
while ( i < length ) {
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->mask & IN_MODIFY) {
printf( "The file %s was modified.\n", file_path );
break;
}
i += EVENT_SIZE + event->len;
}
}
( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );
exit( 0 );
}

inotify api stops working after reporting once or twice

I wanted to test inotify, so took a couple of examples from internet, modified it to learn various aspects, but failed as it didn't work like i wanted to. First i tried to watch over a directory where it worked pretty nicely.
So i extended that example for file with some modification, but it works for one time only and gets blocked on read function
#include <sys/inotify.h>
#include <unistd.h>
#include <stdio.h>
#define EVENT_SIZE (sizeof (struct inotify_event))
#define BUF_LEN (16 * (EVENT_SIZE + 16))
int main()
{
int fd;
fd = inotify_init();
if (fd < 0)
perror("inotify_init()");
int wd;
wd = inotify_add_watch(fd, "target", IN_CLOSE_WRITE);
if (wd < 0)
perror("inotify_add_watch");
char buf[BUF_LEN];
int len;
while(1) {
len = read(fd, buf, BUF_LEN);
printf("after read\n");
if (len > 0)
{
int i = 0;
while (i < len)
{
struct inotify_event *event;
event = (struct inotify_event *) &buf[i];
printf("wd=%d mask=%x cookie=%u len=%u\n",
event->wd, event->mask,
event->cookie, event->len);
if (event->mask & IN_MODIFY)
printf("file modified %s", event->name);
if (event->len)
printf("name=%s\n", event->name);
i += EVENT_SIZE + event->len;
}
}
}
return 0;
}
So, i shifted to select(), but here also, it works once,reports twice then stop reporting the changes.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
int main( int argc, char **argv )
{
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];
struct timeval timeout;
fd = inotify_init();
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch( fd, "target",
IN_CLOSE_WRITE );
fd_set rfds,rfdss;
int ret;
/* zero-out the fd_set */
FD_ZERO (&rfds);
FD_ZERO (&rfdss);
FD_SET (fd, &rfds);
timeout.tv_sec = 5;
timeout.tv_usec = 0;
while(1){
printf("Before select\n");
//rfds = rfdss;
ret = select (fd + 1, &rfds, NULL, NULL, NULL);
printf("After Select\n");
timeout.tv_sec = 5;
timeout.tv_usec = 0;
if (ret < 0)
perror ("select");
else if (!ret){
}
/* timed out! */
else if (FD_ISSET (fd, &rfds)){
printf("file changed============\n");
length = read( fd, buffer, BUF_LEN );
}
}
( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );
exit( 0 );
}
A bit of research showed that that the popular editors save it in different way.
Instead of overwriting that file directly, they actually make a temporary file and then replace original file with new temporary one. So what actually happens is the file which you were actually watching no longer exists and therefore any changes that would be making won't be reflected back.
Editors that actually follow this method are(many more may exist)
Gedit, Geany, vi
Editors that directly overwrite the file is(many more may exist)
nano
Hence even though code was correct, anomalous behavior of editor can be problematic
For the blocking read call, see: https://stackoverflow.com/a/914520/149111
You may also want to switch from IN_CLOSE_WRITE to IN_ALL_EVENTS to make sure you're not missing something; likely a delete:
It is a best practice to watch the dir that contains the file(s) of interest rather than the individual files, as this will consume fewer resources in the kernel. It will also allow you to observe "atomic" file replacement operations where the writer creates a temporary file on the file system (perhaps in the same dir), write to the temporary file and then at the end rename(2) it over the top of the original file.
This style of replacement ensures that observers of the file will only ever notice the complete content of the file and not a half-written version of it.

Does inotify really work for files or only for directories?

I am trying to use inotify for monitoring changes to a file /dev/hvc0 using the following:
#include <stdio.h>
#include <sys/inotify.h>
#include <stdlib.h>
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
#define WATCH_FILE "/dev/hvc0" /* This file should be present
before this program is run */
int main() {
int notify_fd;
int length;
int i = 0;
char buffer[BUF_LEN];
notify_fd = inotify_init();
if (notify_fd < 0) {
perror("inotify_init");
}
int wd = inotify_add_watch(notify_fd, WATCH_FILE, IN_MODIFY | IN_ACCESS);
int length_read = read(notify_fd, buffer, BUF_LEN);
if (length_read) {
perror("read");
}
while (i < length_read) {
struct inotify_event *event =
(struct inotify_event *) &buffer[i];
if (event->len) {
if (event->mask & IN_ACCESS) {
printf("The file %s was accessed.\n", event->name);
} else if (event->mask & IN_MODIFY) {
printf("The file %s was modified.\n", event->name);
}
}
}
(void) inotify_rm_watch(notify_fd, wd);
(void) close(notify_fd);
return 0;
}
However, this does not print if the file was accessed/modified. However whenever I change the path to be monitored to a directory and a file was changed, it prints the correct event occurred.
Does inotify work for monitoring file changes too? Or am I doing something wrong?
Thanks!
You are missing to increase i . Add this before the end of the loop:
i += EVENT_SIZE + event->len;
If you want to monitor the changes you also need to wrap the read / print operation in an endless loop.
I am not understanding why the event->len is return zero may be because the file name is not returned. but for only testing purpose you just comment it. And in second point
Your handling of i is broken, you never reset it to 0 in the loop. This causes any later inotify events to be considered only when they are longer than the longest event before them, which is not likely what you want.And please in while loop please put i += EVENT_SIZE + event->len;
int main() {
int notify_fd;
int length;
int i = 0;
char buffer[BUF_LEN];
notify_fd = inotify_init();
if (notify_fd < 0) {
perror("inotify_init");
}
int wd = inotify_add_watch(notify_fd, WATCH_FILE, IN_MODIFY | IN_ACCESS);
while(1)
{
length = read( notify_fd, buffer, BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
while ( i < length )
{
struct inotify_event *event =
(struct inotify_event *) &buffer[i];
// if (event->len) {
if (event->mask & IN_ACCESS) {
printf("The file %s was accessed.\n", event->name);
} else if (event->mask & IN_MODIFY) {
printf("The file %s was modified.\n", event->name);
}
// }
i += EVENT_SIZE + event->len;
}
}
( void ) inotify_rm_watch( notify_fd, wd );
( void ) close( notify_fd);
exit( 0 );
}
Per this answer, it appears that Linux does not watch on specific files. If you were to listen to specific files, you should listen to its parent directory first.

How to listen to the change in dir or file?

I have code to listen change in directory,
When I run it writes nothing in the file neither shows in out out on terminal.
Can any one please help me out to listen the changes take place in particular text file? I want to store timestamp and what change took place.
Code is:
/*This is the sample program to notify us for the file creation and file deletion takes place in “/tmp” directory*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/inotify.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
int main( )
{
int length, i = 0;
int fd;
int wd;
char buffer[EVENT_BUF_LEN];
char str[] = "This is tutorialspoint.com";
FILE* fp=NULL;;
fp=fopen("f1.txt","rw+");
/*creating the INOTIFY instance*/
fd = inotify_init();
/*checking for error*/
if ( fd < 0 ) {
perror( "inotify_init" );
}
/*adding the “/tmp” directory into watch list. Here, the suggestion is to validate the existence of the directory before adding into monitoring list.*/
wd = inotify_add_watch( fd, "/tmp", IN_CREATE | IN_DELETE );
/*read to determine the event change happens on “/tmp” directory. Actually this read blocks until the change event occurs*/
length = read(fd, buffer, EVENT_BUF_LEN );
/*checking for error*/
if ( length < 0 ) {
perror( "read" );
}
/*actually read return the list of change events happens. Here, read the change event one by one and process it accordingly.*/
while ( i < length ) { struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ]; if ( event->len ) {
if ( event->mask & IN_CREATE ) {
if ( event->mask & IN_ISDIR ) {
printf( "New directory %s created.1\n", event->name );
fwrite(str , 1 , sizeof(str) , fd );
}
else {
printf( "New file %s created.\n2", event->name );
fwrite(str , 1 , sizeof(str) , fd );
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
fwrite(str , 1 , sizeof(str) , fd );
printf( "Directory %s deleted.\n", event->name );
}
else {
printf( "File %s deleted.\n", event->name );
}
}
}
i += EVENT_SIZE + event->len;
}
/*removing the “/tmp” directory from the watch list.*/
inotify_rm_watch( fd, wd );
fclose(fd);
/*closing the INOTIFY instance*/
close( fd );
}
My guess is that it simply blocks on the read call. From the comment above it:
Actually this read blocks until the change event occurs
There simply isn't happening any of the events you're waiting for, and this read call will block until there is.
I suggest you run this program in one terminal, and in another do e.g.
$ touch /tmp/foo

inotify file in C

I am trying to run an example of inotify in C..but it's not working.
I want to monitor modifications to a file (the file is tmp.cfg), but it doesn't work..I don't know if i'm running it correctly, because I understand how to monitor directories, but not a single file
Here´s the example:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <unistd.h>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
int main( int argc, char **argv )
{
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];
fd = inotify_init();
if ( fd < 0 ) {
perror( "inotify_init" );
}
wd = inotify_add_watch( fd, "/home/name/tmp.cfg",
IN_MODIFY | IN_CREATE | IN_DELETE );
length = read( fd, buffer, BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
while ( i < length ) {
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->mask & IN_CREATE ) {
printf( "The file %s was created.\n", event->name );
}
else if ( event->mask & IN_DELETE ) {
printf( "The file %s was deleted.\n", event->name );
}
else if ( event->mask & IN_MODIFY ) {
printf( "The file %s was modified.\n", event->name );
}
i += EVENT_SIZE + event->len;
}
( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );
return 0;
}
Once i run it, if i write something on the file and then save it, nothing happens.
i've tryed debugging it..the problem seems to be the if ( event->mask & IN_MODIFY ), as it doesn't recognize it as a modification
You have 2 issues. First, as far as I can tell, inotify does not really work on files - it needs directory name to watch.
Second, you missed if (event->len) { inside while loop.
This code works for me for creating, deleting and modifying files in current directory:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <unistd.h>
#define EVENT_SIZE (sizeof(struct inotify_event))
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
int main(int argc, char **argv) {
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];
fd = inotify_init();
if (fd < 0) {
perror("inotify_init");
}
wd = inotify_add_watch(fd, ".",
IN_MODIFY | IN_CREATE | IN_DELETE);
length = read(fd, buffer, BUF_LEN);
if (length < 0) {
perror("read");
}
while (i < length) {
struct inotify_event *event =
(struct inotify_event *) &buffer[i];
if (event->len) {
if (event->mask & IN_CREATE) {
printf("The file %s was created.\n", event->name);
} else if (event->mask & IN_DELETE) {
printf("The file %s was deleted.\n", event->name);
} else if (event->mask & IN_MODIFY) {
printf("The file %s was modified.\n", event->name);
}
}
i += EVENT_SIZE + event->len;
}
(void) inotify_rm_watch(fd, wd);
(void) close(fd);
return 0;
}
It doesn't work on a single file because, when we use a editor to modify file, the editor opens a copy of the file and when we save the edited version from the text editor, the existing file is deleted and a new file of the same name is created with the modifications.
When the old file is deleted, the watch created on that file becomes invalid and it is deleted automatically.
You can see the old file being replaced by the new file if you monitor the parent directory.
There are two ways to solve it, monitor the parent directory and print the message when modifications is done to the particular that you want to watch.
Else create a new watch on the file whenever modifications are made. When the old file is deleted, IN_DELETE_SELF event is triggered.
event->name will be non-empty only when you watch a directory, as it will contain the name of the file on which the event has occurred in the watched directory.
I think you're not using your user name, which is your home directory, and you're not checking the return of inotify_add_watch which probably fails:
"/home/name/tmp.cfg"
Edit: okay second problem, you shouldn't print name because
The name field is only present when an event is returned for a file
inside a watched directory;
Edit2: third problem, the file must exist before you run the program because you add a watch on the file, I suggest you check the error from inotify_add_watch
In watching a file, if the file is manipulated by an editor which you might do to edit it and create a change, it is likely to be doing some operations that results in the original file you asked to watch being deleted. Hence the notifications will stop if you only watch the one file.

Resources