C: Thread Safe Logging to a File - c

I am trying to write a thread-safe C logging function, and I am having some serious problems with the file IO. So, basically, I start with an interesting fopen call that lets me open the log in binary update mode:
FILE *log, *start;
int timeout = 0, error;
//make file (fails if file exists)
log = fopen(LOG_FILE, "wx");
//Close the file if a new one was created
if(log)
fclose(log);
//Open file in update mode (it must exist for this)
log = fopen(LOG_FILE, "rb+");
Next, I lock the file, incorporating a timeout if another thread locks it for too long:
//Init other file pointer
start = log;
//Lock file (with timeout)
rewind(start);
error = lockf(fileno(start), F_TLOCK, 0);
while( error == EACCES || error == EAGAIN)
{
//sleep for a bit
usleep(LOCKED_FILE_RETRY_TIME);
//Incremement timeout
timeout += LOCKED_FILE_RETRY_TIME;
//Check for time out
if(timeout > LOCKED_FILE_TIMEOUT)
{
return;
}
//Retry the lock operation
error = lockf(fileno(start), F_TLOCK, 0);
}
And finally, I add the required message to the end of the file, unlock it and close the file:
//Print log entry
fseek(log, 0, SEEK_END);
fwrite((const void*) log_msg, 1, strlen(log_msg), log);
//Unlock the block
rewind(start);
lockf(fileno(start), F_ULOCK, 0);
//Close file
fclose(log);
However, it seems like the majority of the messages are overwritten in the log, rather than appended, almost as if fopen took a "snapshot" of the file, waited for it to be unlocked, and wrote to where the end of the file would have been if another process didn't add to it. Does anyone have any idea as to how I could go about fixing this problem?
As an aside, I want to be in binary update mode because I will eventually add some trimming functionality that will ensure the log file does not exceed a certain size, and this is easier for me to accomplish with working fseek calls and R/W functionality.
Any tips are appreciated. Thanks beforehand!

You didn't call fflush() on the file pointer before unlocking. That way your log message remained in a stdio buffer, to be written out in fclose(), at a time when the lock is no longer being held.
To fix the problem, either add an fflush(log) before the unlock operation, or simply move the fclose(log) before it.

Related

Does fclose release the file immediately?

I have 2 functions. The first function is opening a file in write mode and writing some contents to it and then closing it.
FILE *fp = fopen("file.txt", "w");
//writing itnot file using fwrite
fclose(fp);
The second function opens the file in read mode, parses the content and then closes the file.
FILE *fp = fopen("file.txt", "r");
//parsing logic
fclose(fp);
In main, I am calling function1 and function2 sequentially.
int main()
{
function1();
function2();
return 1;
}
Sometimes, function1 fopen fails with error number 13 i.e. Permission Denied. I am observing this only sometimes. I introduced a sleep in function1 after fclose for 2 seconds and it started working fine without any issues.
So I am suspecting file is not immediately released after fclose. Sleep is not the right solution. Can anyone suggest how to resolve this problem? The example I have given here is a use case and the actual code is running in a thread environment.
Draft N1570 for C11 says as 7.21.5.1 The fclose function
A successful call to the fclose function causes the stream pointed to by stream to be
flushed and the associated file to be closed. Any unwritten buffered data for the stream
are delivered to the host environment to be written to the file; any unread buffered data
are discarded. Whether or not the call succeeds, the stream is disassociated from the file
and any buffer set by the setbuf or setvbuf function is disassociated from the stream
(and deallocated if it was automatically allocated).
It makes no assumption on what happens at the host environment level, that is does the function returns only when the whole operation is finished, or does it returns as soon as a request has been queued.
As race conditions can happen in your environment, you should retry a failed open a number of times, eventually with a delay between them. If portability is not a problem and if your system supports the POSIX sync function, you can also force a disk synchronisation of the file after closing it:
Close part:
...
fclose(fp)
sync(); // forces synchronization of io buffers to disk
Re-open part
ntries = ...; // number of open tries
while (ntries-- > 0) {
fp = fopen(...);
if (fp != NULL) break; // open was successful
// optionaly add a delay
}
In an environment and with a C implementation where you must accommodate such behavior, the best approach is probably to implement some fault tolerance around the fopen()s. Although an unconditional sleep() is not the right answer, short, conditional delays via sleep() or a similar function may indeed be part of such an strategy. For example, you might do something along these lines:
#include <stdio.h>
#include <errno.h>
#define MAX_ATTEMPTS 3
FILE *tolerant_fopen(const char *path, const char *mode) {
FILE *result;
int attempts = 0;
while (!(result = fopen(path, mode))) {
if (errno != EACCES || attempts >= MAX_ATTEMPTS) {
break;
}
if (sleep(1) == 0) {
attempts += 1;
}
}
return result;
}
That attempts to open the file immediately, and in the event that that fails on account of access permissions, it waits a short time and then makes another attempt. Overall it may make three or more attempts to open the file, spaced up to a second apart or perhaps slightly more. (Note that sleep() can be interrupted early; in that case it returns the number of seconds left to sleep.)
You can of course implement a different strategy for the timing and duration of the retries if you prefer, retry more error conditions, etc..
If a child process is spawned (even from another thread) while a file is open, then the child process will inherit the file handle and the file will not be fully closed until after the child process has terminated.
To prevent this behavior pass the "N" flag to fopen:
FILE *fp = fopen("file.txt", "wN");

Why is the data write not reflected to the file using fprintf file stream

This is my program:
#include <stdio.h>
int main() {
FILE *logh;
logh = fopen("/home/user1/data.txt", "a+");
if (logh == NULL)
{
printf("error creating file \n");
return -1;
}
// write some data to the log handle and check if it gets written..
int result = fprintf(logh, "this is some test data \n");
if (result > 0)
printf("write successful \n");
else
printf("couldn't write the data to filesystem \n");
while (1) {
};
fclose(logh);
return 0;
}
When i run this program, i see that the file is getting created but it does not contain any data. what i understand i that there is data caching in memory before the data is actually written to the filesystem to avoid multiple IOs to increase performance. and I also know that i can call fsync/fdatasync inside the program to force a sync. but can i force the sync from outside without having to change the program?
I tried running sync command from Linux shell but it does not make the data to appear on the file. :(
Please help if anybody knows any alternative to do the same.
One useful information: I was researching some more on this and finally found this, to remove internal buffering altogether, the FILE mode can be set to _IONBF using int setvbuf(FILE *stream, char *buf, int mode, size_t size)
The IO functions usingFILE pointers cache the data to be written in an internal buffer within the program's memory until they decide to perform a system call to 'really' write it (which is for normal files usually when the size of the data cached reaches BUFSIZ).
Until then, there is no way to force writing from outside the progam.
The problem is that your program does not close the file because of your while statement. Remove these lines:
while (1) {
};
If the intent is to wait forever, then close the file with fclose before executing the while statement.

C - Inaccurate write() position obtained with lseek() when using multiple threads

I'm having a problem getting the correct file position at which I'm writing when simultaneously writing to different parts of the same file using multiple threads.
I have one global file descriptor to the file. In my writing function, I
first lock a mutex, then do lseek(global_fd, 0, SEEK_CUR) to get the current file
position. I next write 31 zero bytes (31 is my entry size) using write(), in effect to reserve space for later. I then unlock the mutex.
Later in the function, I declare a local fd variable to the same file, and open
it. I now do an lseek on that local fd to get to the position I learned from
earlier, where my space is reserved. Finally, I write() 31 data bytes there for
the entry, and close the local fd.
The issue seems to be that rarely, an entry doesn't get written to the expected location (it's not mangled data - it seems that either it is swapped with a different entry, or two entries were written to the same location). There are multiple threads running that
"writing function" I described.
I since learned that pwrite() can be used to write to a specific offset, which would be more efficient, and eliminate the lseek(). However, I first want to find out: what is wrong with my original algorithm? Is there any type of buffering that could be causing the discrepancy between the expected write location, and where the data actually ends up getting stored in the file?
The relevant code snippet is below. The reason this is an issue is that in a second data file, I record the location where the entry I'm writing will be stored. If that location, based on the lseek() before the write, is not accurate, my data doesn't match up properly -- which is what happens on occasion (it's hard to reproduce - it happens in maybe 1 in 100k writes). Thanks!
db_entry_add(...)
{
char dbrecord[DB_ENTRY_SIZE];
int retval;
pthread_mutex_lock(&db_mutex);
/* determine the EOF index, at which we will add the log entry */
off_t ndb_offset = lseek(cfg.curr_fd, 0, SEEK_CUR);
if (ndb_offset == -1)
{
fprintf(stderr, "Unable to determine ndb offset: %s\n", strerror_s(errno, ebuf, sizeof(ebuf)));
pthread_mutex_unlock(&db_mutex);
return 0;
}
/* reserve entry-size bytes at the location, at which we will
later add the log entry */
memset(dbrecord, 0, sizeof(dbrecord));
/* note: db_write() is a write() loop */
if (db_write(cfg.curr_fd, (char *) &dbrecord, DB_ENTRY_SIZE) < 0)
{
fprintf(stderr, "db_entry_add2db - db_write failed!");
close(curr_fd);
pthread_mutex_unlock(&db_mutex);
return 0;
}
pthread_mutex_unlock(&db_mutex);
/* in another data file, we now record that the entry we're going to write
will be at the specified location. if it's not (which is the problem,
on rare occasion), our data will be inconsistent */
advertise_entry_location(ndb_offset);
...
/* open the data file */
int write_fd = open(path, O_CREAT|O_LARGEFILE|O_WRONLY, 0644);
if (write_fd < 0)
{
fprintf(stderr, "%s: Unable to open file %s: %s\n", __func__, cfg.curr_silo_db_path, strerror_s(errno, ebuf, sizeof(ebuf)));
return 0;
}
pthread_mutex_lock(&db_mutex);
/* seek to our reserved write location */
if (lseek(write_fd, ndb_offset, SEEK_SET) == -1)
{
fprintf(stderr, "%s: lseek failed: %s\n", __func__, strerror_s(errno, ebuf, sizeof(ebuf)));
close(write_fd);
return 0;
}
pthread_mutex_unlock(&db_mutex);
/* write the entry */
/* note: db_write_with_mutex is a write() loop wrapped with db_mutex lock and unlock */
if (db_write_with_mutex(write_fd, (char *) &dbrecord, DB_ENTRY_SIZE) < 0)
{
fprintf(stderr, "db_entry_add2db - db_write failed!");
close(write_fd);
return 0;
}
/* close the data file */
close(write_fd);
return 1;
}
One more note, for completeness. I have a similar but simpler routine that could also be causing the problem. This one uses buffered output (FILE*, fopen, fwrite), but performs an fflush() at the end of each write. It writes to a different file than the earlier routine, but could cause the same symptom.
pthread_mutex_lock(&data_mutex);
/* determine the offset at which the data will be written. this has to be accurate,
otherwise it could be causing the problem */
offset = ftell(current_fp);
fwrite(data);
fflush(current_fp);
pthread_mutex_unlock(&data_mutex);
There seem to be several places where things could go wrong. I would make the following changes: (1) be consistent and use the same I/O library as per bdonlan's suggestion, (2) make the lseek() and the writes an atomic action guarded by a mutex so that only a single thread at a time can do those actions of adding to both files. SEEK_CUR does a seek based on the current location of the file offset pointer so would you not want SEEK_END to seek to the end of the file in order to append there? Then if you are modifying a particular section of the file you would use SEEK_SET to reposition to the location you want to write to. And you would want to do this in a mutex guarded section so as to allow only a single thread to do the file positioning and file update.
If you're using your 'simpler routine' at the same time, this could indeed be a problem. If these are separate file descriptors, there's nothing to ensure that they're both pointing at the end of the file at all times (unless you use append mode, however I'm not sure what the semantics around ftell for append mode are). If they're the same fd (ie, you have a raw fd and a FILE * pointing to the same place), you might have problems with the standard library getting confused about where you are in a file, when you use write() to bypass it.

Read one line from file while rewriting that line from other process. C

so I used to use named pipes for IPC but then I lost the first value sent from one process because the other process wasn't started yet. So then I went over to using a file with only one line as middle storage.
So the file is being updated when my application is writing to it. Here's the code for that:
dmHubRead = fopen ("/tmp/file", "w");
if (!dmHubRead) {
log_error ("can't create /tmp/file: %m");
return 0;
}
fprintf (dmHubRead,
"value %02d:%02d:%02d;\n",
t->x, t->y, t->z);
fflush (dmHubRead);
fclose(dmHubRead);
My other program is then opening the file and wants to read the first line pretty often. This program does not close the file between the reads.
Here is the code for that program:
if ((_file = fopen(FILE_PATH, "r")) < 0) {
DebugLogger::put(DebugLogger::Error, "Could not open file.", __FILE__, __LINE__);
}
...
size_t sz = 0;
char *line = NULL;
if(fsync(fileno(_file)) < 0) {
perror("fsync");
}
rewind(_file);
getline(&line, &sz, _file);
So my problem is that this does not work. Does the fopen in the writing part creates a new file each time? Or what is the problem and how can it be solved?
Your "writing" side is creating a new file each time it runs. The reading side fails because the file handle becomes invalid each time you write a new file. If you re-open the file each time you access it, your code should work. As Joachim mentioned, there are more elegant ways to do this. You haven't mentioned what system you're running on. Depending on if it's Windows, Linux or some other OS, there are better mechanisms to do IPC. You also have the issue of synchronization. Can your read ever occur between the time the new file is opened and the data is written? How about using sockets? That way you can tell if there is new data waiting as well.

C fcntl abstraction function doesn't work

I'm writting an app and its in the specification that I need to lock
a file everytime I write on it (this file will be read for other apps
that other team is working on):
I made the following function:
int lock_file (int fd)
{
if (fd == -1)
return -1;
struct flock file_locker;
file_locker.l_type = F_WRLCK;
file_locker.l_whence = SEEK_SET;
file_locker.l_start = 0;
file_locker.l_len = 0; //lock the entire file
int locked = fcntl(fd, F_SETLK, &file_locker);
if (locked == -1){
/*handle errors*/
return 0;
}
return 1;
}
I can get the 1 return (means everything is ok) but when I made a test case
I could write in the locked file Oo
the test code was:
char *file = "lock_test_ok";
int fd = open(file, O_RDWR);
int locked = lock_file(fd);
/* call popen and try write 'ERROR' in the file */
/* if the file contains ERROR, than fail */
Locking in Unix is advisory: only programs testing the lock will not write in it. (Some offers mandatory locking, but not that way. It usually involves setting up special properties on the locked file.)
The lock is released when the first process exists and its file descriptors are all closed.
Edit: I think I misunderstood the test scenario -- a popen() call won't be following the locking protocol (which is only advisory, and not enforced by the OS), so the write occurs even if the process that called lock_file() still exists and is holding the lock.
In addition to what Jim said, fcntl locks are advisory. They do not prevent anyone from opening and writing to the file. The only thing they do is prevent other processes from acquiring their own fcntl locks.
If you control all writers to the file, this is fine, because you can just have every writer try to lock the file first. Otherwise you're hosed. Unix does not offer any "mandatory" locks (locks that cause open or write to fail).

Resources