summaryrefslogtreecommitdiff
path: root/src/usyslog.c
blob: 611e74da171ea1922b9cbc7659652d316f8a2156 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*
 * License: BSD-style license
 * Copyright: Bernd Schubert <bernd.schubert@fastmail.fm>
 *
 * Details: 
 *   Log files might be located on our own filesystem. If we then want to log
 *   a message to syslog, we would need to log to ourself, which easily ends up
 *   in a deadlock. Initializing openlog() using the flags 
 *   LOG_NDELAY | LOG_NOWAIT should prevent that, but real live has shown that
 *   this does not work reliable and systems 'crashed' just because we 
 *   tried to log a harmless message. 
 *   So this file introduces a syslog thread and a syslog buffer. usyslog() 
 *   calls write without a risk to deadlock into the syslog buffer (chained 
 *   list) and then the seperate syslog_thread call syslog(). That way our
 *   our filesystem thread(s) cannot stall from syslog() calls.
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#if __APPLE__
	#include <malloc/malloc.h>
#else
	#include <malloc.h>
#endif

#include <pthread.h>
#include <stdarg.h>

#include "usyslog.h"
#include "debug.h"

static ulogs_t *free_head, *free_bottom; // free chained list log entries
static ulogs_t *used_head = NULL, *used_bottom = NULL; //used chained list pointers

static pthread_mutex_t list_lock; 	// locks the entire chained list
static pthread_cond_t cond_message;		// used to wake up the syslog thread

// Only used for debugging, protected by list_lock
static int free_entries;  
static int used_entries = 0;

//#define USYSLOG_DEBUG

#ifdef USYSLOG_DEBUG
static void verify_lists()
{
	pthread_mutex_lock(&list_lock);
	
	ulogs_t *entry = free_head;
	int free_count = -1;
	bool first_free = true;
	while (entry) {
		if (first_free) {
			first_free = false;
			free_count = 1;
		} else
			free_count++;
		entry = entry->next;
	}
	if (free_count != free_entries && free_entries != 0) 
		DBG("usyslog list error detected: number of free entries inconsistent!"
		     " %d vs. %d", free_count, free_entries);
		     
	entry = used_head;
	int used_count = -1;
	bool first_used = true;
	while (entry) {
		if (first_used) {
			first_used = false;
			used_count = 1;
		} else
			used_count++;
		entry = entry->next;
	}
	if (used_count != used_entries && used_entries != 0) 
		DBG("usyslog list error detected: number of used entries inconsistent!"
		     " (used: %d vs. %d) (free: %d vs. %d) \n", 
		     used_count, used_entries, free_count, free_entries);
		     
	pthread_mutex_unlock(&list_lock);
}
#else
#define verify_lists()
#endif


/**
 * Walks the chained used-list and calls syslog()
 */
static void do_syslog(void)
{
	pthread_mutex_lock(&list_lock); // we MUST ensure not to keep that forever

	ulogs_t *log_entry = used_head;

	while (log_entry) {
		pthread_mutex_t *entry_lock = &log_entry->lock;
		int res = pthread_mutex_trylock(entry_lock);
		if (res) {
			if (res != EBUSY) 
				DBG("Entirely unexpected locking error %s\n", 
				    strerror(res));
			// If something goes wrong with the log_entry we do not
			// block the critical list_lock forever!
			// EBUSY might come up rarely, if we race with usyslog()
			pthread_mutex_unlock(&list_lock);
			sleep(1);
			pthread_mutex_lock(&list_lock);
			log_entry = used_head;
			continue;
		}
		pthread_mutex_unlock(&list_lock);
		
		// This syslog call and so this lock might block, so be
		// carefull on using locks! The filesystem IO thread
		// *MUST* only try to lock it using pthread_mutex_trylock()
		syslog(log_entry->priority, "%s", log_entry->message);
		log_entry->used = false;

		// NOTE: The list is only locked now, after syslog() succeeded!
		pthread_mutex_lock(&list_lock);
		ulogs_t *next_entry = log_entry->next; // just to save the pointer
		
		used_head = log_entry->next;
		if (!used_head)
			used_bottom = NULL; // no used entries left
		
		if (free_bottom)
			free_bottom->next = log_entry;
		free_bottom = log_entry;
		free_bottom->next = NULL; 
		
		if (!free_head)
			free_head = log_entry;
			
		free_entries++;
		used_entries--;
			
		pthread_mutex_unlock(&list_lock); // unlock ist ASAP
		
		log_entry = next_entry;
		pthread_mutex_unlock(entry_lock);
	}
	
	verify_lists();
}

/**
 * syslog backgroung thread that tries to to empty the syslog buffer
 */
static void * syslog_thread(void *arg)
{
	// FIXME: What is a better way to prevent a compiler warning about
	//        unused variable 'arg'
	int tinfo = *((int *) arg);
	if (1 == 0)
		printf("Starting thread %d", tinfo);

	pthread_mutex_t sleep_mutex; 

	pthread_mutex_init(&sleep_mutex, NULL);
	pthread_mutex_lock(&sleep_mutex);
	while (1) {
		pthread_cond_wait(&cond_message, &sleep_mutex);
		do_syslog();
	}

	return NULL;
}

/**
 * usyslog - function to be called if something shall be logged to syslog
 */
void usyslog(int priority, const char *format, ...)
{
	int res;
	ulogs_t *log;

	// Lock the entire list first, which means the syslog thread MUST NOT
	// lock it if there is any chance it might be locked forever.
	pthread_mutex_lock(&list_lock);
	
	// Some sanity checks. If we fail here, we will leak a log entry,
	// but will not lock up.

	if (free_head == NULL) {
		DBG("All syslog entries already busy\n");
		pthread_mutex_unlock(&list_lock);
		return;
	}
	
	log = free_head;
	free_head = log->next;

	res = pthread_mutex_trylock(&log->lock);
	if (res == EBUSY) {
			// huh, that never should happen!
			DBG("Critical log error, log entry is BUSY, but should not\n");
			pthread_mutex_unlock(&list_lock);
			return;
	} else if (res) {
		// huh, that never should happen either!
		DBG("Never should happen, can get lock: %s\n", strerror(res));
		pthread_mutex_unlock(&list_lock);
		return;
	}

	if (log->used) {
		// huh, that never should happen either!
		DBG("Never should happen, entry is busy, but should not!\n");
		pthread_mutex_unlock(&log->lock);
		pthread_mutex_unlock(&list_lock);
		return;
	}

	if (!used_head)
		used_head = used_bottom = log;
	else {
		used_bottom->next = log;
		used_bottom = log;
	}
	
	if (log->next) {
		// from free_list to end of used_list, so next is NULL now
		log->next = NULL; 
	} else {
		// so the last entry in free_list
		free_bottom = NULL;
	}
	
	
	free_entries--;
	used_entries++;
		
	// Everything below is log entry related, so we can free the list_lock
	pthread_mutex_unlock(&list_lock);
	
	va_list ap;
	va_start(ap, format);
	vsnprintf(log->message, MAX_MSG_SIZE, format, ap);
	log->priority = priority;
	log->used = 1;

	pthread_mutex_unlock(&log->lock);

	pthread_cond_signal(&cond_message); // wake up the syslog thread
}

/**
 * Initialize syslogs
 */
void init_syslog(void)
{
	openlog("unionfs-fuse: ", LOG_CONS | LOG_NDELAY | LOG_NOWAIT | LOG_PID, LOG_DAEMON);

	pthread_mutex_init(&list_lock, NULL);
	pthread_cond_init(&cond_message, NULL);
	pthread_t thread;
	pthread_attr_t attr;
	int t_arg = 0; // thread argument, not required for us

	int i;
	ulogs_t *log, *last = NULL;
	for (i = 0; i < MAX_SYSLOG_MESSAGES; i++) {
		log = malloc(sizeof(ulogs_t));
		if (log == NULL) {
			fprintf(stderr, "\nLog initialization failed: %s\n", strerror(errno));
			fprintf(stderr, "Aborting!\n");
			// Still initialazation phase, we can abort.
			exit (1);	
		}

		log->used = false;
		pthread_mutex_init(&log->lock, NULL);
		
		if (last) {
			last->next = log;
		} else {
			// so the very first entry
			free_head = log;
		}
		last = log;
	}
	last->next = NULL;
	free_bottom = last;
	
	free_entries = MAX_SYSLOG_MESSAGES;

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	int res = pthread_create(&thread, &attr, syslog_thread, (void *) &t_arg);
	if (res != 0) {
		fprintf(stderr, "Failed to initialize the syslog threads: %s\n",
			strerror(res));
		exit(1);
	}
}