portable way to monitor a file for changes - c

I'm actually implementing a very simple version of tail(1). In FreeBSD, I use kqueue to monitor a file for changes and then printing appended lines to the output. But this is not a portable way, as kqueue is only available in BSD family. Is there a general, efficient and platform-independent way to monitor files for changes in UNIX? I prefer not to use external libraries.
This is the code I've written:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
void die(const char*);
#define MAXLINE 1024
int main(int argc, char *argv[])
{
int fdes;
int kq;
int nev;
int flags;
off_t curoff;
char line[MAXLINE + 1];
ssize_t nbytes;
struct kevent change, event;
if (2 != argc)
{
fprintf(stderr, "Usage: %s path\n", argv[0]);
exit(EXIT_FAILURE);
}
if (-1 == (fdes = open(argv[1], O_RDONLY)))
die("open()");
if (-1 == (curoff = lseek(fdes, 0, SEEK_END)))
die("lseek()");
int ch = 0;
int i = 0;
while (i < 10)
{
read(fdes, &ch, 1);
if (ch == '\n')
i++;
if (10 > i)
lseek(fdes, --curoff, SEEK_SET);
}
if (-1 == (flags = fcntl(fdes, F_GETFL)))
die("fcntl()");
flags |= O_NONBLOCK;
if (-1 == fcntl(fdes, F_SETFL, flags))
die("fcntl()1");
while ((nbytes = read(fdes, line, MAXLINE)) > 0)
if (write(STDOUT_FILENO, line, nbytes) != nbytes)
die("write()");
if (-1 == (kq = kqueue()))
die("kqueue()");
EV_SET(&change, fdes, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_ONESHOT,
NOTE_EXTEND | NOTE_WRITE | NOTE_DELETE, 0, NULL);
if (-1 == kevent(kq, &change, 1, NULL, 0, NULL))
die("kevent()");
for (;;)
{
if (-1 == (nev = kevent(kq, NULL, 0, &event, 1, NULL)))
die("kevent()");
if (nev > 0)
{
if (event.fflags & NOTE_WRITE || event.fflags & NOTE_EXTEND)
{
while ((nbytes = read(fdes, line, MAXLINE)) > 0)
if (write(STDOUT_FILENO, line, nbytes) != nbytes)
die("write()");
}
else if (NOTE_DELETE & event.fflags)
{
printf("The file has been deleted\n");
break;
}
}
}
return 0;
}
void die(const char *str)
{
perror(str);
exit(EXIT_FAILURE);
}

You can just keep doing the read() in a loop. If you read zero bytes,
check for an error. If there is no error, then you've hit EOF. At
EOF, stat() the filename, if it's gone, then the file was deleted. If
stat returns, compare the st_dev and st_ino fields of the stat to
the results from fstat (cache this when you open the file). If they're
different, the path was deleted and re-created. Sleep as long as you
care to after the delete check before trying another read.

Related

How do I read from a file and output specific strings in c

I'm writing a program that will read from /etc/passwd and output the username and shell.
For example, here is the first line of the /etc/passwd file:
root:x:0:0:root:/root:/bin/bash
I need to only output the user and the shell. In this instance it would print:
root:/bin/bash
The values are separated by ':' so I just need to print the string before the first ':' and the string after the 6th ':'
Here is the code I have so far:
#include <string.h>
#define BUFFERSIZE 4096
int printf(const char *text, ...);
int main(void) {
int fd;
int buff_size = 1;
char buff[BUFFERSIZE];
int size;
fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) {
printf("Error opening file \n");
return -1;
}
size = strlen(buff - 17);
size = size + 1;
while ((size = read(fd, buff, 1)) > 0) {
buff[1] = '\0';
write(STDOUT_FILENO, buff, size);
}
}
(I am creating prototypes for printf because one of the requirements was to write the program without including <stdio.h> or <stdlib.h>)
Another approach is to use a single loop and a state variable to track the state of where you are in each line based on the number of colons read. The state-variable ncolon does that below. Essentially you read every character and check whether the loop is in a state where you should write the character as output or not. You condition the write on the number of colons, whether you are before the 1st or after the last.
Putting it altogether, you could do:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (int argc, char **argv) {
int fd, /* file descriptor */
ofd = STDOUT_FILENO, /* output file descriptor */
ncolon = 0; /* counter - number of colons seen */
/* open file given on command line or read from stdin otherwise */
if ((fd = argc > 1 ? open (argv[1], O_RDONLY) : STDIN_FILENO) == -1) {
return 1;
}
for (;;) { /* loop continually */
unsigned char c; /* storage for character */
int rtn; /* var to save return */
if ((rtn = read (fd, &c, 1)) < 1) { /* validate read of 1 char */
if (rtn == -1) { /* return on error */
return 1;
}
break; /* break read loop on EOF */
}
if (ncolon < 1 || ncolon == 6) { /* if before 1st or after last */
write (ofd, &c, 1); /* output char */
}
if (c == '\n') { /* reset ncolon on newline */
ncolon = 0;
}
else if (c == ':') { /* increment on colon */
ncolon += 1;
}
}
if (fd != STDIN_FILENO) { /* close file */
close (fd);
}
}
Example Use/Output
$ ./read_etc-passwd /etc/passwd
root:/bin/bash
messagebus:/usr/bin/false
systemd-network:/usr/sbin/nologin
systemd-timesync:/usr/sbin/nologin
nobody:/bin/bash
mail:/usr/sbin/nologin
chrony:/usr/sbin/nologin
...
Confirm the Format
$ diff <(./read_etc-passwd /etc/passwd) <(awk -F: '{print $1":"$7}' /etc/passwd)
(no output means program output and awk output were identical)
Your program has undefined behavior when you evaluate strlen(buff - 17). It is unclear why you do this.
You can solve the problem with these simple steps:
read one byte at a time
count the ':' on the line
output the byte if the count is equal to 0 or equal to 6.
reset the count at newline (and print the newline)
Note that read(fd, &b, 1) and write(1, &b, 1) return -1 in case of error or interruption and should be restarted if errno is EINTR.
Here is a modified version:
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd;
unsigned char b;
int count;
ssize_t ret;
fd = open("/etc/passwd", O_RDONLY);
if (fd < 0) {
write(2, "Error opening /etc/password\n", 28);
return 1;
}
count = 0;
for (;;) {
ret = read(fd, &b, 1);
if (ret == 0) { // end of file
break;
}
if (ret < 0) { // error
if (errno == EINTR)
continue;
write(2, "Read error on /etc/password\n", 28);
return 1;
}
if (b == '\n') {
// reset count, print b
count = 0;
} else
if (b == ':') {
// increment count, print ':' only if count == 1
count = count + 1;
if (count != 1)
continue;
} else
if (count != 0 && count != 6) {
// print b only if count is 0 or 6
continue;
}
for (;;) {
ret = write(1, &b, 1);
if (ret == 1)
break;
if (ret < 0 && errno = EINTR)
continue;
write(2, "Write error\n", 12);
return 1;
}
}
close(fd);
return 0;
}

Why is this code giving bad file descriptor

I am having issue with the following code. It is giving me bad file descriptor error.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
int main()
{
int fd = open("/tmp/test", O_RDONLY);
long len = 20;
long word;
ssize_t ret;
if(fd == -1)
{
perror("open");
return 1;
}
if(len > SSIZE_MAX)
{
len = SSIZE_MAX;
}
ret = read(fd, &word, len);
while(len != 0 && ret != 0)
{
if(ret == -1)
{
if(errno == EINTR)
{
continue;
}
perror("read");
break;
}
len -= ret;
word += ret;
ret = read(fd, &word, len);
}
close(fd);
}
Upon debugging I have found that after first iteration the value of fd is changed to a large number. I do not know how this is happening.
In your code
ret = read(fd, &word, len);
causes undefined behavior,as you're trying to read 20 bytes into a memory designated for sizeof(long) which is much less than what is required.
One correct way of writing this would be
ret = read(fd, &word, sizeof(word));

How to get USB connected hard disk serial in linux?

I'm having a requirement to create a file in the externally mounted hard disk .created file should contain the serial no of the harddisk and that file can be used by other process.
I tried to use the following code
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
int main(int argc, char *argv[])
{
static struct hd_driveid hd;
int fd;
if (geteuid() > 0) {
printf("ERROR: Must be root to use\n");
exit(1);
}
if ((fd = open(argv[1], O_RDONLY|O_NONBLOCK)) < 0) {
printf("ERROR: Cannot open device %s\n", argv[1]);
exit(1);
}
if (!ioctl(fd, HDIO_GET_IDENTITY, &hd)) {
printf("Hard Disk Model: %.40s\n", hd.model);
printf(" Serial Number: %.20s\n", hd.serial_no);
} else if (errno == -ENOMSG) {
printf("No hard disk identification information available\n");
} else {
perror("ERROR: HDIO_GET_IDENTITY");
exit(1);
}
exit(0);
}
this is working fine for internal hard disk but when i do this for external hard disk(usb) it is giving me the following error
ERROR: HDIO_GET_IDENTITY: Invalid argument
Because the device is connected to a USB bridge, you can't send the HDIO_GET_IDENTITY command.
You can try hdparm to query the identity of the device. With the default options, hdparm fails to identify the device so you have to specify the type of the device with -d (see USB devices and smartmontools).
Without the -d option, I get:
$ sudo smartctl /dev/sdc
/dev/sdc: Unknown USB bridge [0x059f:0x1011 (0x000)]
Please specify device type with the -d option.
With -d sat,auto, hdparm manages to display some information about the device:
$ sudo smartctl -d sat,auto -i /dev/sdc
/dev/sdc [SCSI]: Device open changed type from 'sat,auto' to 'scsi'
=== START OF INFORMATION SECTION ===
Vendor: ST2000VN
Product: 000-1H3164
User Capacity: 2 000 398 934 016 bytes [2,00 TB]
Logical block size: 512 bytes
Device type: disk
Local Time is: Thu Mar 13 09:41:32 2014 CET
SMART support is: Unavailable - device lacks SMART capability.
You can try to do the same thing as smartctl in your C program, but it's probably easier to write a script that invokes smartctl.
Thanks for the explanation and i got the below to identify the serial no of a external hardisk
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <scsi/scsi.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
int scsi_get_serial(int fd, void *buf, size_t buf_len) {
// we shall retrieve page 0x80 as per http://en.wikipedia.org/wiki/SCSI_Inquiry_Command
unsigned char inq_cmd[] = {INQUIRY, 1, 0x80, 0, buf_len, 0};
unsigned char sense[32];
struct sg_io_hdr io_hdr;
int result;
memset(&io_hdr, 0, sizeof (io_hdr));
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof (inq_cmd);
io_hdr.dxferp = buf;
io_hdr.dxfer_len = buf_len;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof (sense);
io_hdr.timeout = 5000;
result = ioctl(fd, SG_IO, &io_hdr);
if (result < 0)
return result;
if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK)
return 1;
return 0;
}
void trim(char * s) {
char * p = s;
int l = strlen(p);
while(isspace(p[l - 1])) p[--l] = 0;
while(* p && isspace(* p)) ++p, --l;
memmove(s, p, l + 1);
}
int storeData (char *filepath, char *data) {
int rc = 0;
FILE *fOut = fopen (filepath, "a");
if (fOut != NULL) {
if (fputs (data, fOut) != EOF) {
rc = 1;
}
fclose (fOut); // or for the paranoid: if (fclose (fOut) == EOF) rc = 0;
}
return rc;
}
int main(int argc, char** argv) {
if(argc>1){
char *dev = (char *)argv[1];
char outStr[1024];
printf("\nEntered Serial no : %s\n",argv[1]);
char scsi_serial[255];
int rc;
int fd;
fd = open(dev, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror(dev);
}
memset(scsi_serial, 0, sizeof (scsi_serial));
rc = scsi_get_serial(fd, scsi_serial, 255);
// scsi_serial[3] is the length of the serial number
// scsi_serial[4] is serial number (raw, NOT null terminated)
if (rc < 0) {
printf("FAIL, rc=%d, errno=%d\n", rc, errno);
} else
if (rc == 1) {
printf("FAIL, rc=%d, drive doesn't report serial number\n", rc);
} else {
if (!scsi_serial[3]) {
printf("Failed to retrieve serial for %s\n", dev);
return -1;
}
printf("Serial Number: %.*s\n", (size_t) scsi_serial[3], (char *) & scsi_serial[4]);
scsi_serial[4+scsi_serial[3]]='\0';
trim(&scsi_serial[4]);
sprintf(outStr,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?> \n<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\"> \n<properties>\n<comment/>\n<entry key=\"SerialNo\">%s</entry>\n</properties>\n", (char *) & scsi_serial[4]);
//strcat((char *)argv[2],(char *)"/hdd.xml");
printf("\n%s",outStr);
// printf("\n%s",(char *)argv[2]);
//storeData((char *)argv[1],(char *) outStr);
}
close(fd);
}else{
printf("\nInsufficient no of arguments \n");
}
return (EXIT_SUCCESS);
}

C - Read by basic I/O reads only "\n"

I have a problem at my C-lecture skill practice. My exercise is to read a text document (which is in the same directory like the program) char by char and write it reversed (so from the end to the beginning, char by char) at the Terminal (i have to work at Ubuntu).
Unfortunately it doesn't work - "read" only reads newline-chars (\n).
Can you find my mistake?
#include <sys/stat.h> //mode_t: accessing rights for the file
#include <fcntl.h> //for I/O
#include <unistd.h> //for file descriptors
#include <string.h> //for strlen
short const EXIT_FAILURE = 1;
short const EXIT_SUCCESS = 0;
char const* USAGE_CMD = "usage: write_file filename string_to_write\n";
char const* ERR_OPEN = "error in open\n";
char const* ERR_READ = "error in reading\n";
char const* ERR_CLOSE = "error in close\n";
char const* ERR_WRITE = "error in write\n";
int main(int argc, char** argv){
int fd = open(argv[1], O_RDONLY);
if(fd == -1){
write(STDERR_FILENO, ERR_OPEN, strlen(ERR_OPEN));
return EXIT_FAILURE;
}
int two_char_back = (-1)*sizeof(char); //shift-value for char
int one_back = -1; //shift-value for "no shift"
int length = lseek(fd, one_back, SEEK_END);//setting to one before oef
int i = 0; //for the loop
char buffer;
char* pbuffer = &buffer; //buffer for writing
while (i < length){
if (read(fd, pbuffer, sizeof(buffer)) == -1){ //READING
write(STDERR_FILENO, ERR_READ, strlen(ERR_READ));
return EXIT_FAILURE;
}
if(write(STDOUT_FILENO, pbuffer, sizeof(buffer)) == -1){ //WRITING
write(STDERR_FILENO, ERR_WRITE, strlen(ERR_WRITE));
return EXIT_FAILURE;
}
lseek(fd, two_char_back, SEEK_CUR); //STEPPING
i++;
}
if(close(fd) == -1){ //CLOSING
write(STDERR_FILENO, ERR_CLOSE, strlen(ERR_CLOSE));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
This is wrong:
int two_char_back = (-1)*sizeof(char);
sizeof(char) is 1, you need -2
Haven't tried running it, but looks like two_char_back should be -2. The read advances the cursor, so -1 keeps reading the same one.
Also, just an option, you could make it more efficient by reading the whole file in then reversing it, then writing.
You have a typo in following line:
int two_char_back = (-1)*sizeof(char);
It must be:
int two_char_back = (-2)*sizeof(char);
As read() increments a cursor, you are actually reading the same character all the time e.g:
example text
^
|
After reading:
example text
^
|
After seeking:
example text
^
|
Thanks for your advices a lot!
& Thanks to my colleagues!
Now it works but I created kind of a new version, here it is:
#include <sys/stat.h> //mode_t: accessing rights for the file
#include <fcntl.h> //for I/O
#include <unistd.h> //for file descriptors
#include <string.h> //for strlen
short const EXIT_FAILURE = 1;
short const EXIT_SUCCESS = 0;
char const* USAGE_CMD = "usage: write_file filename string_to_write\n";
char const* ERR_OPEN = "error in open\n";
char const* ERR_READ = "error in reading\n";
char const* ERR_CLOSE = "error in close\n";
char const* ERR_WRITE = "error in write\n";
int main(int argc, char** argv){
int fd = open(argv[1], O_RDONLY); //OPENING
if(fd == -1){
write(STDERR_FILENO, ERR_OPEN, strlen(ERR_OPEN));
return EXIT_FAILURE;
}
int file_size = lseek(fd, 0, SEEK_END); //setting to eof
int i = file_size-1; //for the loop, runs from the end to the start
char buffer;
//the files runs from the end to the back
do{
i--;
lseek(fd, i, SEEK_SET); //STEPPING from the start
if (read(fd, &buffer, sizeof(buffer)) != sizeof(buffer)){ //READING
write(STDERR_FILENO, ERR_READ, strlen(ERR_READ));
return EXIT_FAILURE;
}
if(write(STDOUT_FILENO, &buffer, sizeof(buffer)) != sizeof(buffer)){ //WRITING
write(STDERR_FILENO, ERR_WRITE, strlen(ERR_WRITE));
return EXIT_FAILURE;
}
}while (i != 0);
buffer = '\n';
write(STDOUT_FILENO, &buffer, sizeof(buffer));//no error-det. due to fixed value
if(close(fd) == -1){ //CLOSING
write(STDERR_FILENO, ERR_CLOSE, strlen(ERR_CLOSE));
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

Why does the following prints 'Resource temporarily unavailable'?

Why does the following code print ‘read(): Resource temporarily unavailable’ 80% of the time? That is the EAGAIN code, which is the same as WOULD BLOCK which means there is no data waiting to be read, but select is returning 1 saying there is data (tested in Linux):
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/errno.h>
int main(int argc, char** argv)
{
int fd = open("/dev/lp0", O_RDWR | O_NONBLOCK);
int ret = 0;
int status = 0;
char buffer[1024];
char teststr[] = "This is a test\n";
char XMIT_STATUS_OFFLINE[] = {0x10,0x04,0x02};
char XMIT_STATUS_ERROR[] = {0x10,0x04,0x03};
char XMIT_STATUS_ROLL[] = {0x10,0x04,0x04};
char XMIT_STATUS_SLIP[] = {0x10,0x04,0x05};
fd_set rfds;
FD_ZERO( &rfds );
FD_SET( fd, &rfds );
struct timeval sleep;
sleep.tv_sec = 5;
sleep.tv_usec = 0;
/* Offline status */
ret = write(fd, XMIT_STATUS_OFFLINE, sizeof(XMIT_STATUS_OFFLINE));
//printf("write() returned %d\n", ret);
do {
ret = select( fd + 1, &rfds, NULL, NULL, &sleep );
} while (ret < 0 && (errno == EINTR));
ret = read(fd, buffer, 1024);
if(ret == -1) {
perror("read(): ");
} else {
status = buffer[0];
if((status & 0x04) != 0)
{
printf("The cover is open.\n");
} else {
printf("OFFLINE is good.\n");
}
}
close(fd);
return 0;
}
Your select call will return 0 after the 5 second timeout elapses if no data is available. Your code will ignore this and try to read from the device anyways. Check for ret == 0 and that will fix your problem.

Resources