summaryrefslogtreecommitdiff
path: root/src/daemon/pidfile.c
blob: 2b3818a4a87f5d3cec85ffc84e48266c8d1f0952 (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
/*
    Copyright (C) 2008 Andreas Baumann <abaumann@yahoo.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "port/sys_internal.h"
#include "port/stdio.h"			/* for snprintf */
#include "port/string.h"		/* for strdup, strcspn */
#include "port/stdbool.h"		/* for bool */
#include "port/unistd.h"		/* for getpid, open, write, read,
					 * unlink, lockf */
#define DEFAULT_TEXT_DOMAIN "libwolf"
#include "port/gettext.h"		/* for i18n */

#include "daemon/pidfile.h"

#include "errors.h"
#include "log/log.h"
#include "log/messages.h"		/* for i18n */

#include <stdlib.h>			/* for strtol */
#include <errno.h>			/* for errno */
#include <sys/types.h>			/* for pid_t, ssize_t, off_t */
#include <sys/stat.h>			/* for umask */
#include <fcntl.h>			/* for O_RDWR */
#include <signal.h>			/* for kill */

/* abstraction of the pid file handling in order to unclutter the
 * daemon start and stop function
 */

#define VAR_RUN_DIR "/var/run"

void pidfile_init( struct pidfile_t *pidfile ) {
	pidfile->filename[0] = '\0';
	pidfile->fd = -1;
	pidfile->locked = false;
	pidfile->running = false;
}

void pidfile_set_from_daemon_name( struct pidfile_t *pidfile, const char *name ) {
	pidfile_init( pidfile );
	snprintf( pidfile->filename, PATH_MAX, "%s/%s.pid", VAR_RUN_DIR, name );
}

void pidfile_set_from_filename( struct pidfile_t *pidfile, const char *filename ) {
	pidfile_init( pidfile );
	/* make sure the filename is shorter than the POSIX.1 length */
	snprintf( pidfile->filename, PATH_MAX, "%s", filename );
}

bool is_daemon_running( struct pidfile_t *pidfile, pid_t *pid, wolf_error_t *error ) {
	int res;
	ssize_t bytes_read;
	char buf[256];
	char *end_ptr;
	
	/* assume daemon is not running */
	pidfile->running = false;
	
	/* open pidfile with correct permissions */
	pidfile->fd = open( pidfile->filename, O_RDWR, 0644 );
	if( pidfile->fd < 0 ) {
		if( errno == ENOENT ) {
			/* this is good, pid file doesn't exist at all */
			wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_NO_PIDFILE,
				_( "No pidfile '%s' found, daemon is not running" ), pidfile->filename );
			(void)close( pidfile->fd );
			*error = WOLF_OK;
			return( pidfile->running = false );
		} else {
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_OPEN_PIDFILE_FOR_READING,
				_( "Unable to open pidfile '%s' for reading: %s" ), pidfile->filename, strerror( errno ) );
			*error = WOLF_ERR_INTERNAL;
			return( pidfile->running = false );
		}
	}
	
	/* try to lock the pid file (non-blocking) */
	res = lockf( pidfile->fd, F_TLOCK, (off_t)0 );
	if( res < 0 ) {
		if( errno == EAGAIN ) {
			/* another process locks the file already */
			wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_ANOTHER_IS_RUNNING,
				_( "Another process locks the pidfile, daemon already running" ) );
			*error = WOLF_OK;
			pidfile->locked = true;
			pidfile->running = true;
		} else {
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_LOCK_PIDFILE,
				_( "Unable to lock pidfile '%s': %s" ), pidfile->filename, strerror( errno ) );
			(void)close( pidfile->fd );
			*error = WOLF_ERR_INTERNAL;
			return( pidfile->running = false );
		}
	} else {
		/* we can lock the file but it could be a relict, so we are not sure
		 * yet if the daemon is already running or not
		 */
		pidfile->locked = true;
	}
	
	/* try to read the pid from the file */
	bytes_read = read( pidfile->fd, buf, sizeof( buf ) - 1 );
	if( bytes_read < 0 ) {
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_READ_PID_FROM_PIDFILE,
			_( "Unable to read pid from pidfile '%s': %s" ), pidfile->filename, strerror( errno ) );
		(void)close( pidfile->fd );
		*error = WOLF_ERR_INTERNAL;
		return( pidfile->running = false );
	}
	
	/* parse the file and see if we have a valid pid on the first line */
	buf[bytes_read] = 0;
	buf[strcspn( buf, "\n" )] = 0;
	
	errno = 0;
	*pid = (pid_t)strtol( buf, &end_ptr, 10 );
	if( ( errno != 0 ) /* ERANGE or valid base */ ||
	    ( end_ptr == NULL ) /* pre-condition for check for '\0'! */ ||
	    ( end_ptr - buf < 1 ) /* too short */ ||
	    ( *pid < 2 ) /* too small value */ ) {
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIDFILE_WITH_ILLEGAL_DATA,
			_( "pidfile '%s' contains invalid data, can't read PID from it!" ), pidfile->filename );
		(void)close( pidfile->fd );
		*error = WOLF_ERR_INTERNAL;
		return( pidfile->running = false );
	}
	wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_FOUND_PID_IN_PIDFILE,
		_( "Found PID '%lu' in pidfile" ), *pid );
	
	/* the pid is valid, but is there a process with the pid actually running?
	 * (this handles the case of kill -9!)
	 */
	res = kill( *pid, 0 );
	if( res < 0 ) {
		if( errno == ESRCH ) {
			/* this is fine, process doesn't exist with this PID */
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_FOUND_PID_BUT_NO_PROCESS_RUNNING,
				_( "Found PID '%lu' in pidfile '%s', but no such process is running. Check and manually delete the pidfile!" ), *pid, pidfile->filename );
			(void)close( pidfile->fd );
			*error = WOLF_ERR_INTERNAL;
			return( pidfile->running = false );
		} else {
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PROCESS_ALIVE_CHECK_FAILED,
				_( "Can't check if process with PID '%lu' is alive: %s" ), *pid, strerror( errno ) );
			(void)close( pidfile->fd );
			*error = WOLF_ERR_INTERNAL;
			return( pidfile->running = false );
		}
	}
	wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_ALREADY_RUNNING_WITH_PID,
		_( "A process with PID '%lu' is already running" ), *pid );

	/* process successfuly signaled, so a process with this PID exists
	 * (worst case, we assume, it's the daemon)
	 */
	(void)close( pidfile->fd );
	*error = WOLF_OK;
	return( pidfile->running = true );
}

wolf_error_t pidfile_create( struct pidfile_t *pidfile ) {
	int res;
	char pid_string[20];
	ssize_t bytes_writen;
	
	/* create or open pid file with correct permissions */
	pidfile->fd = open( pidfile->filename, O_CREAT | O_WRONLY | O_EXCL, 0644 );
	if( pidfile->fd < 0 ) {
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_OPEN_PIDFILE_FOR_WRITTING,
			_( "Unable to open pidfile '%s' for writing: %s" ), pidfile->filename, strerror( errno ) );
		return WOLF_ERR_INTERNAL;
	}
	
	/* Try to lock the pid file (non-blocking) */
	res = lockf( pidfile->fd, F_TLOCK, (off_t)0 );
	if( res < 0 ) {
		if( errno == EAGAIN ) {
			/* another process locks the file already, this should not happen, maybe a
			 * race between to daemons started in parallel?
			 */
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_PIDFILE_IN_USE,
				_( "Unable to lock pidfile '%s' after creation, daemon started in parallel?" ), pidfile->filename );
			(void)close( pidfile->fd );
			(void)unlink( pidfile->filename );
			return WOLF_ERR_INVALID_STATE;
		} else {
			wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_LOCK_PIDFILE_AFTER_CREATION,
				_( "Unable to lock pidfile '%s' after creation: %s" ), pidfile->filename, strerror( errno ) );
			(void)close( pidfile->fd );
			(void)unlink( pidfile->filename );
			return WOLF_ERR_INTERNAL;
		}
	}

	/* Truncate the contents of the file, so we don't get funny values
	 * in the pid file
	 */
	if( ftruncate( pidfile->fd, (off_t)0 ) < 0 ) {
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_TRUNCATE_PIDFILE,
			_( "Unable to truncate the pidfile '%s' before writing to it" ), pidfile->filename, strerror( errno ) );
		(void)close( pidfile->fd );
		(void)unlink( pidfile->filename );
		return WOLF_ERR_INTERNAL;
	}

	/* We remember the pid in the file for init scripts which rely on the pid
	 * to be stored here.
	 */
	snprintf( pid_string, 20, "%lu\n", (unsigned long)getpid( ) );
	bytes_writen = write( pidfile->fd, pid_string, strlen( pid_string ) );
	if( bytes_writen < 0 ) {
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_WRITE_PID_INTO_PIDFILE,
			_( "Unable to write PID into the pidfile '%s': %s" ), pidfile->filename, strerror( errno ) );
		(void)close( pidfile->fd );
		(void)unlink( pidfile->filename );
		return WOLF_ERR_INTERNAL;
	} else if( bytes_writen != (ssize_t)strlen( pid_string ) ) {
		/* non-atomic write on files with so little data, strange, should never happen! */
		wolf_log( WOLF_LOG_EMERG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_ATOMIC_PIDFILE_WRITE_FAILED,
			_( "Non-atomic write failed when storing the PID into the pidfile '%s'" ), pidfile->filename );
	}
	wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_STORED_PIDFILE,
		_( "Stored '%lu' into the pidfile '%s' and locked it" ), (unsigned long)getpid( ), pidfile->filename );
	
	pidfile->locked = true;
	
	return WOLF_OK;
}

wolf_error_t pidfile_release( struct pidfile_t *pidfile ) {
	wolf_error_t error = WOLF_OK;

	wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_RELEASING_PIDFILE,
		_( "Releasing (unlocking/closing) pidfile '%s' (fd: %d, locked: %d)" ),
		pidfile->filename, pidfile->fd, pidfile->locked );

	if( pidfile->locked ) {
		if( lockf( pidfile->fd, F_ULOCK, (off_t)0 ) < 0 ) {
			wolf_log( WOLF_LOG_ALERT, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_UNLOCK_PIDFILE,
				_( "Unable to unlock the pidfile '%s': %s" ),
				pidfile->filename, strerror( errno ) );
			error = WOLF_ERR_INTERNAL;
		}
	}
	
	if( pidfile->fd >= 0 ) {
		if( close( pidfile->fd ) < 0 ) {
			wolf_log( WOLF_LOG_ALERT, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_CLOSE_PIDFILE,
				_( "Unable to close the pidfile '%s': %s" ),
				pidfile->filename, strerror( errno ) );
			error = WOLF_ERR_INTERNAL;
		}
		pidfile->fd = -1;
	}

	return error;
}

wolf_error_t pidfile_remove( struct pidfile_t *pidfile ) {
	wolf_error_t error = WOLF_OK;

	wolf_log( WOLF_LOG_DEBUG, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_REMOVING_PIDFILE,
		_( "Removing pidfile '%s' (fd: %d, locked: %d, running: %d)" ),
		pidfile->filename, pidfile->fd, pidfile->locked, pidfile->running );

	/* if we know that another process is running, then we also know that we are
	 * not allowed to remove the pidfile
	 */
	if( pidfile->running ) {
		return WOLF_OK;
	}

	/* unconditionally remove the pidfile */
	if( unlink( pidfile->filename ) < 0 ) {
		wolf_log( WOLF_LOG_ALERT, WOLF_CATEGORY_DAEMON, WOLF_MSG_DAEMON_CANT_REMOVE_PIDFILE,
			_( "Unable to remove the pidfile '%s': %s" ),
			pidfile->filename, strerror( errno ) );
		error = WOLF_ERR_INTERNAL;
	}

	return error;
}