How to write log files correctly? - c

How to reliably record logs to be sure of the atomicity of entries in multi-threaded applications? In addition, I would like to be able to rotate logs using the logrotate utility.
The simplest variant to write logs is next:
open/reopen log file
write entries by printf()
close log file at exit
Here is my example:
// default log level
static Cl_loglevl loglevel = LOGLEVEL_NONE;
// log file descriptor (open with Cl_openlog)
static FILE *logfd = NULL;
/**
* #brief Cl_openlog - open log file
* #param logfile - file name
* #return FILE struct or NULL if failed
*/
FILE *Cl_openlog(const char *logfile, Cl_loglevl loglvl){
if(logfd){
Cl_putlog(LOGLEVEL_ERROR, "Reopen log file\n");
fclose(logfd);
logfd = NULL;
char newname[PATH_MAX];
snprintf(newname, PATH_MAX, "%s.old", logfile);
if(rename(logfile, newname)) WARN("Can't rename old log file");
}
if(loglvl < LOGLEVEL_CNT) loglevel = loglvl;
if(!logfile) return NULL;
if(!(logfd = fopen(logfile, "w"))) WARN(_("Can't open log file"));
return logfd;
}
/**
* #brief Cl_putlog - put message to log file
* #param lvl - message loglevel (if lvl > loglevel, message won't be printed)
* #param fmt - format and the rest part of message
* #return amount of symbols saved in file
*/
int Cl_putlog(Cl_loglevl lvl, const char *fmt, ...){
if(lvl > loglevel || !logfd) return 0;
char strtm[128];
time_t t = time(NULL);
struct tm *curtm = localtime(&t);
strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
int i = fprintf(logfd, "%s\t", strtm);
va_list ar;
va_start(ar, fmt);
i += vfprintf(logfd, fmt, ar);
va_end(ar);
fflush(logfd);
return i;
}
The call of Cl_openlog allows to rotate log once. I can call this function in SIG_USR1 handler and send this signal by logrotate. But it still remains unclear how to write to a file correctly in order to achieve atomicity of records.
I don't want to use external libraries like log4c for such simple problem.

Either you can assure that all logging is done through a single thread, or you protect logging function with mutexes.
For first case, you can have a worker thread that polls on a pipe's read-end; then each thread will use pipe's write-end to wakeup worker thread and let it manage its log (allocated somewhere on heap and passed by address through pipe).
The same can be done for your SIGUSR1 handler (logrotate).
Please note that writing less than PIPE_BUF to a pipe() is guaranteed not to be interleaved (thus it is atomic).
Consequently writing just an heap storage address will always be atomic.
Last but not least, you may use different logfiles for each thread.

Well, at last I did this:
// array with all opened logs - for error/warning messages
static Cl_log *errlogs = NULL;
static int errlogsnum = 0;
/**
* #brief Cl_createlog - create log file: init mutex, test file open ability
* #param log - log structure
* #return 0 if all OK
*/
int Cl_createlog(Cl_log *log){
if(!log || !log->logpath || log->loglevel <= LOGLEVEL_NONE || log->loglevel >= LOGLEVEL_CNT) return 1;
FILE *logfd = fopen(log->logpath, "a");
if(!logfd){
WARN("Can't open log file");
return 2;
}
fclose(logfd);
pthread_mutex_init(&log->mutex, NULL);
errlogs = realloc(errlogs, (++errlogsnum) *sizeof(Cl_log));
if(!errlogs) errlogsnum = 0;
else memcpy(&errlogs[errlogsnum-1], log, sizeof(Cl_log));
return 0;
}
/**
* #brief Cl_putlog - put message to log file with/without timestamp
* #param timest - ==1 to put timestamp
* #param log - pointer to log structure
* #param lvl - message loglevel (if lvl > loglevel, message won't be printed)
* #param fmt - format and the rest part of message
* #return amount of symbols saved in file
*/
int Cl_putlogt(int timest, Cl_log *log, Cl_loglevl lvl, const char *fmt, ...){
if(!log || !log->logpath || log->loglevel < 0 || log->loglevel >= LOGLEVEL_CNT) return 0;
if(lvl > log->loglevel) return 0;
if(pthread_mutex_lock(&log->mutex)) return 0;
int i = 0;
FILE *logfd = fopen(log->logpath, "a");
if(!logfd) goto rtn;
if(timest){
char strtm[128];
time_t t = time(NULL);
struct tm *curtm = localtime(&t);
strftime(strtm, 128, "%Y/%m/%d-%H:%M", curtm);
i = fprintf(logfd, "%s\t", strtm);
}
va_list ar;
va_start(ar, fmt);
i += vfprintf(logfd, fmt, ar);
va_end(ar);
fclose(logfd);
rtn:
pthread_mutex_unlock(&log->mutex);
return i;
}
In Cl_createlog() I just test ability of opening of given file and init mutex. In Cl_putlogt() I open given file, write to it and close it. The mutex used to make writing atomic for any threads.
The array errlogs consists all opened logs for writing critical messages.

Related

ffmpeg Resource temporarily unavailable

I'm trying to encode an audio frame using ffmpeg library and Opus codec
but i faced with this error :
Resource temporarily unavailable
My source code :
void encode_audio(uint8_t *frame , int frame_size , void (*onPacket)(uint8_t *packet , int packet_size)){
if(audio_encoder_codec_context != NULL){
memset(audio_encoder_frame_buffer , 0 , (size_t) audio_encoder_frame_size);
swr_convert(
s16_to_flt_resampler,
&audio_encoder_frame_buffer,
audio_encoder_frame_size,
(const uint8_t **) &frame,
frame_size
);
int result = avcodec_send_frame(audio_encoder_codec_context , audio_encoder_frame);
while(result >= 0){
result = avcodec_receive_packet(audio_encoder_codec_context , audio_encoder_packet);
char *a = malloc(1024);
av_strerror(result , a , 1024);
printf("%s\n",a);
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF || result < 0){
break;
}
onPacket(audio_encoder_packet->data , audio_encoder_packet->size);
av_packet_unref(audio_encoder_packet);
}
}
}
This is coming from AVERROR(EAGAIN).
You should send more frames when AVERROR(EAGAIN) is returned.
Docs state this for avcodec_receive_packet
avcodec.h:
/**
* Read encoded data from the encoder.
*
* #param avctx codec context
* #param avpkt This will be set to a reference-counted packet allocated by the
* encoder. Note that the function will always call
* av_packet_unref(avpkt) before doing anything else.
* #return 0 on success, otherwise negative error code:
* AVERROR(EAGAIN): output is not available in the current state - user
* must try to send input
* AVERROR_EOF: the encoder has been fully flushed, and there will be
* no more output packets
* AVERROR(EINVAL): codec not opened, or it is a decoder
* other errors: legitimate encoding errors
*/
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
Depending on the data, FFmpeg can require additional input/data before output can be produced.
More from the docs:
AVERROR(EAGAIN): output is not available in the current state - user must try to send input.
Keep giving it data until you get a return code of 0 (success).

getpwuid_r and getgrgid_r are slow, how can I cache them?

I am writing a clone of find while learning C. When implementing the -ls option I've stumbled upon a problem that the getpwuid_r and getgrgid_r calls are really slow, the same applies to getpwuid and getgrgid. I need them to display the user/group names from ids provided by stat.h.
For example, listing the whole filesystem gets 3x slower:
# measurements were made 3 times and the fastest run was recorded
# with getgrgid_r
time ./myfind / -ls > list.txt
real 0m4.618s
user 0m1.848s
sys 0m2.744s
# getgrgid_r replaced with 'return "user";'
time ./myfind / -ls > list.txt
real 0m1.437s
user 0m0.572s
sys 0m0.832s
I wonder how GNU find maintains such a good speed. I've seen the sources, but they are not exactly easy to understand and to apply without special types, macros etc:
time find / -ls > list.txt
real 0m1.544s
user 0m0.884s
sys 0m0.648s
I thought about caching the uid - username and gid - groupname pairs in a data structure. Is it a good idea? How would you implement it?
You can find my complete code here.
UPDATE:
The solution was exactly what I was looking for:
time ./myfind / -ls > list.txt
real 0m1.480s
user 0m0.696s
sys 0m0.736s
Here is a version based on getgrgid (if you don't require thread safety):
char *do_get_group(struct stat attr) {
struct group *grp;
static unsigned int cache_gid = UINT_MAX;
static char *cache_gr_name = NULL;
/* skip getgrgid if we have the record in cache */
if (cache_gid == attr.st_gid) {
return cache_gr_name;
}
/* clear the cache */
cache_gid = UINT_MAX;
grp = getgrgid(attr.st_gid);
if (!grp) {
/*
* the group is not found or getgrgid failed,
* return the gid as a string then;
* an unsigned int needs 10 chars
*/
char group[11];
if (snprintf(group, 11, "%u", attr.st_gid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return "";
}
return group;
}
cache_gid = grp->gr_gid;
cache_gr_name = grp->gr_name;
return grp->gr_name;
}
getpwuid:
char *do_get_user(struct stat attr) {
struct passwd *pwd;
static unsigned int cache_uid = UINT_MAX;
static char *cache_pw_name = NULL;
/* skip getpwuid if we have the record in cache */
if (cache_uid == attr.st_uid) {
return cache_pw_name;
}
/* clear the cache */
cache_uid = UINT_MAX;
pwd = getpwuid(attr.st_uid);
if (!pwd) {
/*
* the user is not found or getpwuid failed,
* return the uid as a string then;
* an unsigned int needs 10 chars
*/
char user[11];
if (snprintf(user, 11, "%u", attr.st_uid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return "";
}
return user;
}
cache_uid = pwd->pw_uid;
cache_pw_name = pwd->pw_name;
return pwd->pw_name;
}
UPDATE 2:
Changed long to unsigned int.
UPDATE 3:
Added the cache clearing. It is absolutely necessary, because pwd->pw_name may point to a static area. getpwuid can overwrite its contents if it fails or simply when executed somewhere else in the program.
Also removed strdup. Since the output of getgrgid and getpwuid should not be freed, there is no need to require free for our wrapper functions.
The timings indeed indicate a strong suspicion on these functions.
Looking at your function do_get_group, there are some issues:
You use sysconf(_SC_GETPW_R_SIZE_MAX); for every call to do_get_group and do_get_user, definitely cache that, it will not change during the lifetime of your program, but you will not gain much.
You use attr.st_uid instead of attr.st_gid, which probably causes the lookup to fail for many files, possibly defeating the cacheing mechanism, if any. Fix this first, this is a bug!
You return values that should not be passed to free() by the caller, such as grp->gr_name and "". You should always allocate the string you return. The same issue is probably present in do_get_user().
Here is a replacement for do_get_group with a one shot cache. See if this improves the performance:
/*
* #brief returns the groupname or gid, if group not present on the system
*
* #param attr the entry attributes from lstat
*
* #returns the groupname if getgrgid() worked, otherwise gid, as a string
*/
char *do_get_group(struct stat attr) {
char *group;
struct group grp;
struct group *result;
static size_t length = 0;
static char *buffer = NULL;
static gid_t cache_gid = -1;
static char *cache_gr_name = NULL;
if (!length) {
/* only allocate the buffer once */
long sysconf_length = sysconf(_SC_GETPW_R_SIZE_MAX);
if (sysconf_length == -1) {
sysconf_length = 16384;
}
length = (size_t)sysconf_length;
buffer = calloc(length, 1);
}
if (!buffer) {
fprintf(stderr, "%s: malloc(): %s\n", program, strerror(errno));
return strdup("");
}
/* check the cache */
if (cache_gid == attr.st_gid) {
return strdup(cache_gr_name);
}
/* empty the cache */
cache_gid = -1;
free(cache_gr_name);
cache_gr_name = NULL;
if (getgrgid_r(attr.st_gid, &grp, buffer, length, &result) != 0) {
fprintf(stderr, "%s: getpwuid_r(): %s\n", program, strerror(errno));
return strdup("");
}
if (result) {
group = grp.gr_name;
} else {
group = buffer;
if (snprintf(group, length, "%ld", (long)attr.st_gid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return strdup("");
}
}
/* load the cache */
cache_gid = attr.st_gid;
cache_gr_name = strdup(group);
return strdup(group);
}
Whether the getpwuid and getgrgid calls are cached depends on how they are implemented and on how your system is configured. I recently wrote an implementation of ls and ran into a similar problem.
I found that on all modern systems I tested, the two functions are uncached unless you run the name service caching dæmon (nscd) in which case nscd makes sure that the cache stays up to date. It's easy to understand why this happens: Without an nscd, caching the information could lead to outdated output which is a violation of the specification.
I don't think you should rely on these functions caching the group and passwd databases because they often don't. I implemented custom caching code for this purpose. If you don't require to have up-to-date information in case the database contents change during program execution, this is perfectly fine to do.
You can find my implementation of such a cache here. I'm not going to publish it on Stack Overflow as I do not desire to publish the code under the MIT license.

Why does malloc provoke memory corruption here?

I keep getting the following error:
*** Error in `./vice': malloc(): memory corruption: 0x08e77530 ***
Aborted (core dumped)
The relevant code is:
open_result *
open_file_1_svc(open_args *argp, struct svc_req *rqstp)
{
static open_result result;
int obtained_fd;
int just_read;
int total_read = 0;
int max_bytes_read = 1024;
char *ptr_file;
char *pathName = "MyFiles/"; // strlen = 8
int toReserve;
xdr_free((xdrproc_t)xdr_open_result, (char *)&result);
// Construct full name of the file (in "MyFiles")
toReserve = strlen(argp->fname) + strlen(pathName) + 1; // "\0"
char *fullName = malloc(toReserve*sizeof(char));
fullName = strdup(pathName);
fullName = strcat(fullName, argp->fname);
// Call to open in POSIX
obtained_fd = open(fullName, argp->flags);
result.fd = obtained_fd;
/* If there was an error while reading, the error code will be sent, but not
the file (it might not even exist) */
if (obtained_fd < 0) {
result.characters = "";
result.number_characters = 0;
}
/* If the file opening was successful,
both the fd and the file will be sent */
else {
char *file_just_read = malloc(max_bytes_read * sizeof(char)); // This is the problem
ptr_file = file_just_read;
/* Reading the file byte by byte */
while((just_read = read(obtained_fd, ptr_file, max_bytes_read)) > 0) {
total_read += just_read;
file_just_read = realloc(file_just_read, (total_read+max_bytes_read) * sizeof(char));
ptr_file = file_just_read + total_read;
}
result.characters = file_just_read;
result.number_characters = total_read;
}
return &result;
}
Let me explain what the code does. This is a server named "vice" which communicates with its clients via RPC. This function is supposed to receive "open_args" and return "open_result". These are defined in the "vice.x" file. The relevant part of this file is:
struct open_args {
string fname<>;
int flags;
};
struct open_result {
string characters<>;
int number_characters;
int fd;
};
open_file_1_svc is supposed to try to open a file with the name given in argp->fname in the directory MyFiles. If open is successful, open_file_1_svc will attempt to copy the contents of the file in result.characters, sending a copy of the contents of the file to the client this way. The number_characters will allow me to know if there are any null bytes in between.
The error I'm getting appears when I attempt to allocate some memory for the part of the file I'm about to read.
I've been reading about this type of error, but I don't understand what's wrong with this particular case.
malloc does not "provoke" the corruption; malloc detects it.
This error is telling you that something had scribbled over the heap meta data before malloc was called (this time); you probably have a buffer overrun.
Both malloc calls in this code are before anything writes to memory, so the overrun is most likely elsewhere. (I've not done a detailed check that this code is right, but it's after-the-fact here.)
Edit: I missed the implicit malloc call inside the strdup. This will cause an overrun because the duplicated string has a smaller allocation. I think you mean strcpy, not strdup.

How to return a clean error on incorrect mount with VTreeFS?

When trying to mount a VTreeFS filesystem with a set of arguments (by using options -o when mounting) we want to let it fail cleanly if the user doesn't use the predefined arguments correctly. Currently we get this nasty error message when we do not mount the filesystem and let the main return 0. We basically want the filesystem to not be mounted if the arguments are inorrect
Current situations
mount -t filesystemtest -o testarguments none /mnt/filesystemtest
Arguments invalid
RS: service 'fs_00021' exited uring initialization
filesystemtest 109710 0xab6e 0x65f1 0x618d 0x6203 0x98ba 0x1010
Request to RS failed: unknown error (error 302)
mount: couldn't run /bin/sercie up /sbin/filesystemtest -label 'fs_00021'-args ''
mount: Can't mount none on /mnt/filesystemtest/: unknown error
Preferred situation
mount -t filesystemtest -o testarguments none /mnt/filesystemtest
Arguments invalid
Basically we wan't to know how to return a clean error message, when not calling start_vtreefs like below. The below example is not our actualy code and doesn't actually use arguments, but as an example there should be a way to have this piece of code to fail always. (sorry for that):
#include <minix/drivers.h>
#include <minix/vtreefs.h>
#include <sys/stat.h>
#include <time.h>
#include <assert.h>
static void my_init_hook(void)
{
/* This hook will be called once, after VTreeFS has initialized.
*/
struct inode_stat file_stat;
struct inode *inode;
/* We create one regular file in the root directory. The file is
* readable by everyone, and owned by root. Its size as returned by for
* example stat() will be zero, but that does not mean it is empty.
* For files with dynamically generated content, the file size is
* typically set to zero.
*/
file_stat.mode = S_IFREG | 0444;
file_stat.uid = 0;
file_stat.gid = 0;
file_stat.size = 0;
file_stat.dev = NO_DEV;
/* Now create the actual file. It is called "test" and does not have an
* index number. Its callback data value is set to 1, allowing it to be
* identified with this number later.
*/
inode = add_inode(get_root_inode(), "test", NO_INDEX, &file_stat, 0,
(cbdata_t) 1);
assert(inode != NULL);
}
static int my_read_hook(struct inode *inode, off_t offset, char **ptr,
size_t *len, cbdata_t cbdata)
{
/* This hook will be called every time a regular file is read. We use
* it to dyanmically generate the contents of our file.
*/
static char data[26];
const char *str;
time_t now;
/* We have only a single file. With more files, cbdata may help
* distinguishing between them.
*/
assert((int) cbdata == 1);
/* Generate the contents of the file into the 'data' buffer. We could
* use the return value of ctime() directly, but that would make for a
* lousy example.
*/
time(&now);
str = ctime(&now);
strcpy(data, str);
/* If the offset is beyond the end of the string, return EOF. */
if (offset > strlen(data)) {
*len = 0;
return OK;
}
/* Otherwise, return a pointer into 'data'. If necessary, bound the
* returned length to the length of the rest of the string. Note that
* 'data' has to be static, because it will be used after this function
* returns.
*/
*ptr = data + offset;
if (*len > strlen(data) - offset)
*len = strlen(data) - offset;
return OK;
}
/* The table with callback hooks. */
struct fs_hooks my_hooks = {
my_init_hook,
NULL, /* cleanup_hook */
NULL, /* lookup_hook */
NULL, /* getdents_hook */
my_read_hook,
NULL, /* rdlink_hook */
NULL /* message_hook */
};
int main(int argc, char* argv[])
{
/* The call above never returns. This just keeps the compiler happy. */
if (argc == 1) {
// We want it to fail right now!!!!
printf("Arguments invalid. (pass option with -o)");
}
else {
struct inode_stat root_stat;
/* Fill in the details to be used for the root inode. It will be a
* directory, readable and searchable by anyone, and owned by root.
*/
root_stat.mode = S_IFDIR | 0555;
root_stat.uid = 0;
root_stat.gid = 0;
root_stat.size = 0;
root_stat.dev = NO_DEV;
/* Now start VTreeFS. Preallocate 10 inodes, which is more than we'll
* need for this example. No indexed entries are used.
*/
start_vtreefs(&my_hooks, 10, &root_stat, 0);
}
return 0;
}

tmpfile() on windows 7 x64

Running the following code on Windows 7 x64
#include <stdio.h>
#include <errno.h>
int main() {
int i;
FILE *tmp;
for (i = 0; i < 10000; i++) {
errno = 0;
if(!(tmp = tmpfile())) printf("Fail %d, err %d\n", i, errno);
fclose(tmp);
}
return 0;
}
Gives errno 13 (Permission denied), on the 637th and 1004th call, it works fine on XP (haven't tried 7 x86). Am I missing something or is this a bug?
I've got similar problem on Windows 8 - tmpfile() was causing win32 ERROR_ACCESS_DENIED error code - and yes, if you run application with administrator privileges - then it works fine.
I guess problem is mentioned over here:
https://lists.gnu.org/archive/html/bug-gnulib/2007-02/msg00162.html
Under Windows, the tmpfile function is defined to always create
its temporary file in the root directory. Most users don't have
permission to do that, so it will often fail.
I would suspect that this is kinda incomplete windows port issue - so this should be an error reported to Microsoft. (Why to code tmpfile function if it's useless ?)
But who have time to fight with Microsoft wind mills ?! :-)
I've coded similar implementation using GetTempPathW / GetModuleFileNameW / _wfopen. Code where I've encountered this problem came from libjpeg - I'm attaching whole source code here, but you can pick up code from jpeg_open_backing_store.
jmemwin.cpp:
//
// Windows port for jpeg lib functions.
//
#define JPEG_INTERNALS
#include <Windows.h> // GetTempFileName
#undef FAR // Will be redefined - disable warning
#include "jinclude.h"
#include "jpeglib.h"
extern "C" {
#include "jmemsys.h" // jpeg_ api interface.
//
// Memory allocation and freeing are controlled by the regular library routines malloc() and free().
//
GLOBAL(void *) jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject)
{
return (void *) malloc(sizeofobject);
}
GLOBAL(void) jpeg_free_small (j_common_ptr cinfo, void * object, size_t sizeofobject)
{
free(object);
}
/*
* "Large" objects are treated the same as "small" ones.
* NB: although we include FAR keywords in the routine declarations,
* this file won't actually work in 80x86 small/medium model; at least,
* you probably won't be able to process useful-size images in only 64KB.
*/
GLOBAL(void FAR *) jpeg_get_large (j_common_ptr cinfo, size_t sizeofobject)
{
return (void FAR *) malloc(sizeofobject);
}
GLOBAL(void) jpeg_free_large (j_common_ptr cinfo, void FAR * object, size_t sizeofobject)
{
free(object);
}
//
// Used only by command line applications, not by static library compilation
//
#ifndef DEFAULT_MAX_MEM /* so can override from makefile */
#define DEFAULT_MAX_MEM 1000000L /* default: one megabyte */
#endif
GLOBAL(long) jpeg_mem_available (j_common_ptr cinfo, long min_bytes_needed, long max_bytes_needed, long already_allocated)
{
// jmemansi.c's jpeg_mem_available implementation was insufficient for some of .jpg loads.
MEMORYSTATUSEX status = { 0 };
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
if( status.ullAvailPhys > LONG_MAX )
// Normally goes here since new PC's have more than 4 Gb of ram.
return LONG_MAX;
return (long) status.ullAvailPhys;
}
/*
Backing store (temporary file) management.
Backing store objects are only used when the value returned by
jpeg_mem_available is less than the total space needed. You can dispense
with these routines if you have plenty of virtual memory; see jmemnobs.c.
*/
METHODDEF(void) read_backing_store (j_common_ptr cinfo, backing_store_ptr info, void FAR * buffer_address, long file_offset, long byte_count)
{
if (fseek(info->temp_file, file_offset, SEEK_SET))
ERREXIT(cinfo, JERR_TFILE_SEEK);
size_t readed = fread( buffer_address, 1, byte_count, info->temp_file);
if (readed != (size_t) byte_count)
ERREXIT(cinfo, JERR_TFILE_READ);
}
METHODDEF(void)
write_backing_store (j_common_ptr cinfo, backing_store_ptr info, void FAR * buffer_address, long file_offset, long byte_count)
{
if (fseek(info->temp_file, file_offset, SEEK_SET))
ERREXIT(cinfo, JERR_TFILE_SEEK);
if (JFWRITE(info->temp_file, buffer_address, byte_count) != (size_t) byte_count)
ERREXIT(cinfo, JERR_TFILE_WRITE);
// E.g. if you need to debug writes.
//if( fflush(info->temp_file) != 0 )
// ERREXIT(cinfo, JERR_TFILE_WRITE);
}
METHODDEF(void)
close_backing_store (j_common_ptr cinfo, backing_store_ptr info)
{
fclose(info->temp_file);
// File is deleted using 'D' flag on open.
}
static HMODULE DllHandle()
{
MEMORY_BASIC_INFORMATION info;
VirtualQuery(DllHandle, &info, sizeof(MEMORY_BASIC_INFORMATION));
return (HMODULE)info.AllocationBase;
}
GLOBAL(void) jpeg_open_backing_store(j_common_ptr cinfo, backing_store_ptr info, long total_bytes_needed)
{
// Generate unique filename.
wchar_t path[ MAX_PATH ] = { 0 };
wchar_t dllPath[ MAX_PATH ] = { 0 };
GetTempPathW( MAX_PATH, path );
// Based on .exe or .dll filename
GetModuleFileNameW( DllHandle(), dllPath, MAX_PATH );
wchar_t* p = wcsrchr( dllPath, L'\\');
wchar_t* ext = wcsrchr( p + 1, L'.');
if( ext ) *ext = 0;
wchar_t* outFile = path + wcslen(path);
static int iTempFileId = 1;
// Based on process id (so processes would not fight with each other)
// Based on some process global id.
wsprintfW(outFile, L"%s_%d_%d.tmp",p + 1, GetCurrentProcessId(), iTempFileId++ );
// 'D' - temporary file.
if ((info->temp_file = _wfopen(path, L"w+bD") ) == NULL)
ERREXITS(cinfo, JERR_TFILE_CREATE, "");
info->read_backing_store = read_backing_store;
info->write_backing_store = write_backing_store;
info->close_backing_store = close_backing_store;
} //jpeg_open_backing_store
/*
* These routines take care of any system-dependent initialization and
* cleanup required.
*/
GLOBAL(long)
jpeg_mem_init (j_common_ptr cinfo)
{
return DEFAULT_MAX_MEM; /* default for max_memory_to_use */
}
GLOBAL(void)
jpeg_mem_term (j_common_ptr cinfo)
{
/* no work */
}
}
I'm intentionally ignoring errors from some of functions - have you ever seen GetTempPathW or GetModuleFileNameW failing ?
A bit of a refresher from the manpage of on tmpfile(), which returns a FILE*:
The file will be automatically deleted when it is closed or the program terminates.
My verdict for this issue: Deleting a file on Windows is weird.
When you delete a file on Windows, for as long as something holds a handle, you can't call CreateFile on something with the same absolute path, otherwise it will fail with the NT error code STATUS_DELETE_PENDING, which gets mapped to the Win32 code ERROR_ACCESS_DENIED. This is probably where EPERM in errno is coming from. You can confirm this with a tool like Sysinternals Process Monitor.
My guess is that CRT somehow wound up creating a file that has the same name as something it's used before. I've sometimes witnessed that deleting files on Windows can appear asynchronous because some other process (sometimes even an antivirus product, in reaction to the fact that you've just closed a delete-on-close handle...) will leave a handle open to the file, so for some timing window you will see a visible file that you can't get a handle to without hitting delete pending/access denied. Or, it could be that tmpfile has simply chosen a filename that some other process is working on.
To avoid this sort of thing you might want to consider another mechanism for temp files... For example a function like Win32 GetTempFileName allows you to create your own prefix which might make a collision less likely. That function appears to resolve race conditions by retrying if a create fails with "already exists", so be careful about deleting the temp filenames that thing generates - deleting the file cancels your rights to use it concurrently with other processes/threads.

Resources