I have a file called "input.txt" that holds some values. I'm writing program that will find minimum value in that file, and replace that minimum with number given as command line argument - if that command line argument is bigger then minimum. These values represent room temperatures so that fact can be used in order to find minimum. In addition, that part of the file (where new number replaces minimum) needs to be locked.
Example:
$ ./prog 23
FILE: 21 25 19 22 24
FILE: 21 25 23 22 24
$ ./prog 29
FILE: 21 25 23 22 24
FILE: 29 25 23 22 24
Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdint.h>
/* Function that handles errors */
void fatal_error(const char *message){
perror(message);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv){
if(argc != 2)
fatal_error("Bad arguments.\n");
/* Fetching command line argument */
int temp = atoi(argv[1]);
/* Opening file and checking for errors */
FILE *file = fopen("input.txt", "r+");
if(!file)
fatal_error("Unable to open file.\n");
/* Finding minimum in the file */
int min = 200;
int value;
while(fscanf(file, "%d", &value) != EOF)
if(value < min)
min = value;
/* Exiting if nothing needs to change */
if(temp <= min)
return 0;
/* Creating file descriptor from stream and checking for errors */
int fdOpen = fileno(file);
if(fdOpen == -1)
fatal_error("Unable to open file descriptor.\n");
/* Moving offset to the beginning of the file */
off_t of = lseek(fdOpen,0,SEEK_SET);
printf("Ofset pre petlje: %jd\n", (intmax_t)of);
while(1){
/* I'm reading file all over again */
if(fscanf(file, "%d", &value) == EOF)
fatal_error("Reached end of file.\n");
/* If I reached minimum */
if(value == min){
/* I lock that part of the file - temperatures are two digit numbers*/
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_CUR;
lock.l_start = -2;
lock.l_len = 2;
/* I create lock */
if(fcntl(fdOpen,F_SETLK, &lock) == -1){
if(errno == EACCES || errno == EAGAIN)
fatal_error("File is locked.\n");
}
/* Moving two positions back from current position */
off_t offset;
if((offset = lseek(fdOpen, -2, SEEK_CUR)) == -1)
fatal_error("lseek error.\n");
/* Inserting read command line value into file */
fprintf(file, "%d", temp);
/* Unlocking */
lock.l_type = F_UNLCK;
if(fcntl(fdOpen, F_SETLK, &lock) == -1)
fatal_error("Unable to destroy lock.\n");
/* Closing file descriptor, and breaking loop */
close(fdOpen);
break;
}
}
return 0;
}
This however doesn't work. File won't change. The problem is - I know this is correct way to do it. I have written basically the same program that changes for example every appearance of word "aa" to "bb". It is basically the same concept.
I've tried:
fscanf()
in
while()
loop. Before first fscanf
offset is set to 0 - beginning
of the file. After first fscanf
offset is set to end of the file,
and offset remains at the end of the file after each iteration.ftell()
in these same printf()
and ftell()
gives me correct offset. However that ftell()
in while loop still
gives end of the file. I've also tried using fseek()
instead of
lseek()
(Even tho I know fseek()
uses lseek()
in its
implementation). Still, same result.char*
buffer instead of values. Basically to use
fscanf(file,"%s",buffer)
, convert that value to check whether read
value is minimum, and afterwards I've used
fprintf(file,"%s",buffer)
to write that. However same result.Code:
Here is second program I mentioned that uses same concept. This code works, but I've tried printing offset here too, and offset is also at the end of the file.
int main(int argc, char **argv){
if(argc != 4)
fatal_error("You must enter exactly 4 arguments.\n");
FILE *f = fopen(argv[1], "r+");
if(!f)
fatal_error("Unable to open file for reading and writing.\n");
int fd = fileno(f);
if(fd == -1)
fatal_error("Unable to fetch file descriptor for file.\n");
char word[MAX_LEN + 1];
int word_len = strlen(argv[2]);
while(fscanf(f,"%s",word) != EOF){
printf("%jd\n", lseek(fd,0,SEEK_CUR));
if(!strcmp(argv[2],word)){
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_CUR;
lock.l_start = -word_len;
lock.l_len = word_len;
if(fcntl(fd, F_SETLKW, &lock) == -1)
fatal_error("File locking failed.\n");
if(lseek(fd, -word_len, SEEK_CUR) == -1)
fatal_error("Lseek error.\n");
fprintf(f, "%s", argv[3]);
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLK, &lock) == -1)
fatal_error("Failed to release lock.\n");
}
}
}
As you can see it is exactly same concept. Second program works, first one doesn't.
I'm very confused now. If I create file descriptor from file stream, and then use lseek()
on that file descriptor to change offset, does offset of stream also change? And if you use fscanf()
to read something from stream, do offset_t
change just as much as you read from file? Is there any difference in changing offset if I use fscanf()
with format specifier %d
and %s
?
The way you want to replace values will work ONLY when both (source and replacement text) have the same length in you case lenght(aa)==length(bb). Mainly you should be careful using FILE*
and int fd
descriptor and always close the file before exit.
Calling close(fd)
before fclose(f)
will cause the buffered data not to be written.
The other problem - locking a file region relatively to SEEK_CUR will not lock the part of file you want to modify
Here you have code a little modified that is working:
int main(int argc, char **argv){
if(argc != 4)
fatal_error("You must enter exactly 3 arguments.\n");
if(strlen(argv[2])!=strlen(argv[3]))
fatal_error("src&dst words must be the length.\n");
FILE *f = fopen(argv[1], "r+");
if(!f)
fatal_error("Unable to open file for reading and writing.\n");
int fd = fileno(f);
if(fd == -1)
fatal_error("Unable to fetch file descriptor for file.\n");
char word[MAX_LEN + 1];
int word_len = strlen(argv[2]);
while(fscanf(f,"%s",word) != EOF){
printf("%jd\n", ftell(f));
if(!strcmp(argv[2],word)){
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = ftell(f)-word_len;
lock.l_len = word_len;
if(fcntl(fd, F_SETLKW, &lock) == -1)
fatal_error("File locking failed.\n");
fseek(f,-word_len,SEEK_CUR); //FILE* based seek
fprintf(f, "%s", argv[3]);
fflush(f); //sync output
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLK, &lock) == -1)
fatal_error("Failed to release lock.\n");
}
}
fclose(f); // close the file
}
Update1: FILE
interface has its own buffering, which is not in sync with int fd
.
So the main problem is using lseek, while fseek should be used
Update2: code with loop looking for min value
int main(int argc, char **argv){
if(argc != 3)
fatal_error("You must enter exactly 2 arguments.\n");
if(strlen(argv[2]) != 2)
fatal_error("replace num must have 2 digits.\n");
FILE *f = fopen(argv[1], "r+");
if(!f)
fatal_error("Unable to open file for reading and writing.\n");
int fd = fileno(f);
if(fd == -1)
fatal_error("Unable to fetch file descriptor for file.\n");
// search for minimum
int word_len = strlen(argv[2]);
int value, minValue;
long minOffs=-1;
while(fscanf(f,"%d",&value) == 1){ //compare number of parsed items
printf("test value %d\n", value);
if (minValue > value) {
minValue = value;
minOffs = ftell(f) - word_len;
}
}
// replace if found
if (minOffs >= 0) {
printf("replacing v=%d at %ld\n", minValue, minOffs);
struct flock lock;
memset(&lock, 0, sizeof(lock));
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = minOffs;
lock.l_len = word_len;
fseek(f,minOffs,SEEK_SET);
if(fcntl(fd, F_SETLK, &lock) == -1)
fatal_error("File locking failed.\n");
fprintf(f, "%s", argv[2]);
fflush(f); //sync output
lock.l_type = F_UNLCK;
if(fcntl(fd, F_SETLK, &lock) == -1)
fatal_error("Failed to release lock.\n");
}
fclose(f);
}