]> git.proxmox.com Git - mirror_ovs.git/blame - lib/stream-windows.c
odp-execute: Rename 'may_steal' to 'should_steal'.
[mirror_ovs.git] / lib / stream-windows.c
CommitLineData
922247c6 1/*
310984c1 2 * Copyright (c) 2016, 2017 Cloudbase Solutions Srl
922247c6
AS
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <config.h>
18#include <errno.h>
19#include <sys/types.h>
20#include <stdlib.h>
21#include <string.h>
22#include <unistd.h>
fd016ae3 23#include "openvswitch/poll-loop.h"
922247c6 24#include "dirs.h"
310984c1 25#include "fatal-signal.h"
922247c6
AS
26#include "util.h"
27#include "stream-provider.h"
28#include "openvswitch/vlog.h"
29
30VLOG_DEFINE_THIS_MODULE(stream_windows);
31
32static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
33
34static void maybe_unlink_and_free(char *path);
35
36/* Suggested buffer size at the creation of the named pipe for reading and
37 * and writing operations. */
38#define BUFSIZE 65000
39
40/* Default prefix of a local named pipe. */
41#define LOCAL_PREFIX "\\\\.\\pipe\\"
42
06732e89
SV
43/* Size of the allowed PSIDs for securing Named Pipe. */
44#define ALLOWED_PSIDS_SIZE 2
45
922247c6
AS
46/* This function has the purpose to remove all the slashes received in s. */
47static char *
48remove_slashes(char *s)
49{
50 char *p1, *p2;
51 p1 = p2 = s;
52
53 while (*p1) {
54 if ((*p1) == '\\' || (*p1) == '/') {
55 p1++;
56 } else {
57 *p2 = *p1;
58 p2++;
59 p1++;
60 }
61 }
62 *p2 = '\0';
63 return s;
64}
65
66/* Active named pipe. */
67struct windows_stream
68{
69 struct stream stream;
70 HANDLE fd;
71 /* Overlapped operations used for reading/writing. */
72 OVERLAPPED read;
73 OVERLAPPED write;
74 /* Flag to check if a reading/writing operation is pending. */
75 bool read_pending;
76 bool write_pending;
77 /* Flag to check if fd is a server HANDLE. In the case of a server handle
78 * we have to issue a disconnect before closing the actual handle. */
79 bool server;
80 bool retry_connect;
81 char *pipe_path;
82};
83
84static struct windows_stream *
85stream_windows_cast(struct stream *stream)
86{
87 stream_assert_class(stream, &windows_stream_class);
88 return CONTAINER_OF(stream, struct windows_stream, stream);
89}
90
91static HANDLE
92create_snpipe(char *path)
93{
94 return CreateFile(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
95 OPEN_EXISTING,
96 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
97 FILE_FLAG_NO_BUFFERING,
98 NULL);
99}
100
101/* Active named pipe open. */
102static int
103windows_open(const char *name, char *suffix, struct stream **streamp,
104 uint8_t dscp OVS_UNUSED)
105{
106 char *connect_path;
107 HANDLE npipe;
108 DWORD mode = PIPE_READMODE_BYTE;
109 char *path;
110 FILE *file;
111 bool retry = false;
112 /* If the path does not contain a ':', assume it is relative to
113 * OVS_RUNDIR. */
114 if (!strchr(suffix, ':')) {
115 path = xasprintf("%s/%s", ovs_rundir(), suffix);
116 } else {
117 path = xstrdup(suffix);
118 }
119
120 /* In case of "unix:" argument, the assumption is that there is a file
121 * created in the path (name). */
122 file = fopen(path, "r");
123 if (!file) {
124 free(path);
125 VLOG_DBG_RL(&rl, "%s: could not open %s (%s)", name, suffix,
126 ovs_strerror(errno));
127 return ENOENT;
128 } else {
129 fclose(file);
130 }
131
132 /* Valid pipe names do not have slashes. The assumption is that the named
133 * pipe was created with the name "path", with slashes removed and the
134 * default prefix \\.\pipe\ appended.
135 * Strip the slashes from the parameter name and append the default prefix.
136 */
137 connect_path = xasprintf("%s%s", LOCAL_PREFIX, remove_slashes(path));
138 free(path);
139
140 /* Try to connect to the named pipe. In case all pipe instances are
141 * busy we set the retry flag to true and retry again during the
142 * connect function. Use overlapped flag and file no buffering to ensure
143 * asynchronous operations. */
144 npipe = create_snpipe(connect_path);
145
146 if (npipe == INVALID_HANDLE_VALUE && GetLastError() == ERROR_PIPE_BUSY) {
147 retry = true;
148 }
149
150 if (!retry && npipe == INVALID_HANDLE_VALUE) {
151 VLOG_ERR_RL(&rl, "Could not connect to named pipe: %s",
152 ovs_lasterror_to_string());
153 free(connect_path);
154 return ENOENT;
155 }
156 if (!retry && !SetNamedPipeHandleState(npipe, &mode, NULL, NULL)) {
157 VLOG_ERR_RL(&rl, "Could not set named pipe options: %s",
158 ovs_lasterror_to_string());
159 free(connect_path);
160 CloseHandle(npipe);
161 return ENOENT;
162 }
163 struct windows_stream *s = xmalloc(sizeof *s);
b7636967 164 stream_init(&s->stream, &windows_stream_class, 0, xstrdup(name));
922247c6
AS
165 s->pipe_path = connect_path;
166 s->fd = npipe;
167 /* This is an active stream. */
168 s->server = false;
169 /* Create events for reading and writing to be signaled later. */
170 memset(&s->read, 0, sizeof(s->read));
171 memset(&s->write, 0, sizeof(s->write));
172 s->read.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
173 s->write.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
174 /* Initial read and write operations are not pending. */
175 s->read_pending = false;
176 s->write_pending = false;
177 s->retry_connect = retry;
178 *streamp = &s->stream;
179 return 0;
180}
181
182/* Active named pipe close. */
183static void
184windows_close(struct stream *stream)
185{
186 struct windows_stream *s = stream_windows_cast(stream);
187 /* Disconnect the named pipe in case it was created from a passive stream.
188 */
189 if (s->server) {
67fd9060
AS
190 /* Flush the pipe to allow the client to read the pipe's contents
191 * before disconnecting. */
192 FlushFileBuffers(s->fd);
922247c6
AS
193 DisconnectNamedPipe(s->fd);
194 }
195 CloseHandle(s->fd);
196 CloseHandle(s->read.hEvent);
197 CloseHandle(s->write.hEvent);
198 if (s->pipe_path) {
199 free(s->pipe_path);
200 }
201 free(s);
202}
203
204/* Active named pipe connect. */
205static int
206windows_connect(struct stream *stream)
207{
208 struct windows_stream *s = stream_windows_cast(stream);
209
210 if (!s->retry_connect) {
211 return 0;
212 } else {
213 HANDLE npipe;
214 npipe = create_snpipe(s->pipe_path);
215 if (npipe == INVALID_HANDLE_VALUE) {
216 if (GetLastError() == ERROR_PIPE_BUSY) {
217 return EAGAIN;
218 } else {
219 s->retry_connect = false;
220 return ENOENT;
221 }
222 }
223 s->retry_connect = false;
224 s->fd = npipe;
225 return 0;
226 }
227}
228
229/* Active named pipe receive. */
230static ssize_t
231windows_recv(struct stream *stream, void *buffer, size_t n)
232{
233 struct windows_stream *s = stream_windows_cast(stream);
234 ssize_t retval = 0;
235 boolean result = false;
236 DWORD last_error = 0;
237 LPOVERLAPPED ov = NULL;
238 ov = &s->read;
239
240 /* If the read operation was pending, we verify its result. */
241 if (s->read_pending) {
242 if (!GetOverlappedResult(s->fd, ov, &(DWORD)retval, FALSE)) {
243 last_error = GetLastError();
244 if (last_error == ERROR_IO_INCOMPLETE) {
245 /* If the operation is still pending, retry again. */
246 s->read_pending = true;
247 return -EAGAIN;
248 } else if (last_error == ERROR_PIPE_NOT_CONNECTED
249 || last_error == ERROR_BAD_PIPE
250 || last_error == ERROR_NO_DATA
251 || last_error == ERROR_BROKEN_PIPE) {
252 /* If the pipe was disconnected, return 0. */
253 return 0;
254 } else {
255 VLOG_ERR_RL(&rl, "Could not receive data on named pipe. Last "
256 "error: %s", ovs_lasterror_to_string());
257 return -EINVAL;
258 }
259 }
260 s->read_pending = false;
261 return retval;
262 }
263
264 result = ReadFile(s->fd, buffer, n, &(DWORD)retval, ov);
265
266 if (!result && GetLastError() == ERROR_IO_PENDING) {
267 /* Mark the read operation as pending. */
268 s->read_pending = true;
269 return -EAGAIN;
270 } else if (!result) {
271 last_error = GetLastError();
272 if (last_error == ERROR_PIPE_NOT_CONNECTED
273 || last_error == ERROR_BAD_PIPE
274 || last_error == ERROR_NO_DATA
275 || last_error == ERROR_BROKEN_PIPE) {
276 /* If the pipe was disconnected, return 0. */
277 return 0;
278 }
279 VLOG_ERR_RL(&rl, "Could not receive data synchronous on named pipe."
280 "Last error: %s", ovs_lasterror_to_string());
281 return -EINVAL;
282 }
283
284 return retval;
285}
286
287/* Active named pipe send. */
288static ssize_t
289windows_send(struct stream *stream, const void *buffer, size_t n)
290{
291 struct windows_stream *s = stream_windows_cast(stream);
292 ssize_t retval = 0;
293 boolean result = false;
294 DWORD last_error = 0;
295 LPOVERLAPPED ov = NULL;
296 ov = &s->write;
297
298 /* If the send operation was pending, we verify the result. */
299 if (s->write_pending) {
300 if (!GetOverlappedResult(s->fd, ov, &(DWORD)retval, FALSE)) {
301 last_error = GetLastError();
302 if (last_error == ERROR_IO_INCOMPLETE) {
303 /* If the operation is still pending, retry again. */
304 s->write_pending = true;
305 return -EAGAIN;
306 } else if (last_error == ERROR_PIPE_NOT_CONNECTED
307 || last_error == ERROR_BAD_PIPE
308 || last_error == ERROR_NO_DATA
309 || last_error == ERROR_BROKEN_PIPE) {
310 /* If the pipe was disconnected, return connection reset. */
5fffa669 311 return -EPIPE;
922247c6
AS
312 } else {
313 VLOG_ERR_RL(&rl, "Could not send data on named pipe. Last "
314 "error: %s", ovs_lasterror_to_string());
315 return -EINVAL;
316 }
317 }
318 s->write_pending = false;
319 return retval;
320 }
321
322 result = WriteFile(s->fd, buffer, n, &(DWORD)retval, ov);
323 last_error = GetLastError();
5fffa669 324 if (!result && last_error == ERROR_IO_PENDING) {
922247c6
AS
325 /* Mark the send operation as pending. */
326 s->write_pending = true;
327 return -EAGAIN;
328 } else if (!result && (last_error == ERROR_PIPE_NOT_CONNECTED
329 || last_error == ERROR_BAD_PIPE
330 || last_error == ERROR_NO_DATA
331 || last_error == ERROR_BROKEN_PIPE)) {
332 /* If the pipe was disconnected, return connection reset. */
5fffa669 333 return -EPIPE;
922247c6
AS
334 } else if (!result) {
335 VLOG_ERR_RL(&rl, "Could not send data on synchronous named pipe. Last "
336 "error: %s", ovs_lasterror_to_string());
337 return -EINVAL;
338 }
339 return (retval > 0 ? retval : -EAGAIN);
340}
341
342/* Active named pipe wait. */
343static void
344windows_wait(struct stream *stream, enum stream_wait_type wait)
345{
346 struct windows_stream *s = stream_windows_cast(stream);
347 switch (wait) {
348 case STREAM_SEND:
349 poll_wevent_wait(s->write.hEvent);
350 break;
351
352 case STREAM_CONNECT:
353 poll_immediate_wake();
354 break;
355
356 case STREAM_RECV:
357 poll_wevent_wait(s->read.hEvent);
358 break;
359
360 default:
361 OVS_NOT_REACHED();
362 }
363}
364
365/* Passive named pipe. */
366const struct stream_class windows_stream_class = {
367 "unix", /* name */
368 false, /* needs_probes */
369 windows_open, /* open */
370 windows_close, /* close */
371 windows_connect, /* connect */
372 windows_recv, /* recv */
373 windows_send, /* send */
374 NULL, /* run */
375 NULL, /* run_wait */
376 windows_wait, /* wait */
377};
378
379struct pwindows_pstream
380{
381 struct pstream pstream;
382 HANDLE fd;
383 /* Unlink path to be deleted during close. */
384 char *unlink_path;
385 /* Overlapped operation used for connect. */
386 OVERLAPPED connect;
387 /* Flag to check if an operation is pending. */
388 bool pending;
389 /* String used to create the named pipe. */
390 char *pipe_path;
391};
392
393const struct pstream_class pwindows_pstream_class;
394
395static struct pwindows_pstream *
396pwindows_pstream_cast(struct pstream *pstream)
397{
398 pstream_assert_class(pstream, &pwindows_pstream_class);
399 return CONTAINER_OF(pstream, struct pwindows_pstream, pstream);
400}
401
402/* Create a named pipe with read/write access, overlapped, message mode for
403 * writing, byte mode for reading and with a maximum of 64 active instances. */
404static HANDLE
405create_pnpipe(char *name)
406{
407 SECURITY_ATTRIBUTES sa;
06732e89
SV
408 SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
409 DWORD aclSize;
410 PSID allowedPsid[ALLOWED_PSIDS_SIZE];
411 PSID remoteAccessSid;
412 PACL acl = NULL;
413 PSECURITY_DESCRIPTOR psd = NULL;
414 HANDLE npipe;
415
416 /* Disable access over network. */
417 if (!AllocateAndInitializeSid(&sia, 1, SECURITY_NETWORK_RID,
418 0, 0, 0, 0, 0, 0, 0, &remoteAccessSid)) {
419 VLOG_ERR_RL(&rl, "Error creating Remote Access SID.");
420 goto handle_error;
421 }
422
423 aclSize = sizeof(ACL) + sizeof(ACCESS_DENIED_ACE) +
424 GetLengthSid(remoteAccessSid) - sizeof(DWORD);
425
426 /* Allow Windows Services to access the Named Pipe. */
427 if (!AllocateAndInitializeSid(&sia, 1, SECURITY_LOCAL_SYSTEM_RID,
428 0, 0, 0, 0, 0, 0, 0, &allowedPsid[0])) {
429 VLOG_ERR_RL(&rl, "Error creating Services SID.");
430 goto handle_error;
431 }
432
433 /* Allow Administrators to access the Named Pipe. */
434 if (!AllocateAndInitializeSid(&sia, 2, SECURITY_BUILTIN_DOMAIN_RID,
435 DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
436 &allowedPsid[1])) {
437 VLOG_ERR_RL(&rl, "Error creating Administrator SID.");
438 goto handle_error;
439 }
440
441 for (int i = 0; i < ALLOWED_PSIDS_SIZE; i++) {
442 aclSize += sizeof(ACCESS_ALLOWED_ACE) +
443 GetLengthSid(allowedPsid[i]) -
444 sizeof(DWORD);
445 }
446
447 acl = xmalloc(aclSize);
448 if (!InitializeAcl(acl, aclSize, ACL_REVISION)) {
449 VLOG_ERR_RL(&rl, "Error initializing ACL.");
450 goto handle_error;
451 }
452
453 /* Add denied ACL. */
454 if (!AddAccessDeniedAce(acl, ACL_REVISION,
455 GENERIC_ALL, remoteAccessSid)) {
456 VLOG_ERR_RL(&rl, "Error adding remote access ACE.");
457 goto handle_error;
458 }
459
460 /* Add allowed ACLs. */
461 for (int i = 0; i < ALLOWED_PSIDS_SIZE; i++) {
462 if (!AddAccessAllowedAce(acl, ACL_REVISION,
463 GENERIC_ALL, allowedPsid[i])) {
464 VLOG_ERR_RL(&rl, "Error adding ACE.");
465 goto handle_error;
466 }
467 }
468
469 psd = xmalloc(SECURITY_DESCRIPTOR_MIN_LENGTH);
470 if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION)) {
471 VLOG_ERR_RL(&rl, "Error initializing Security Descriptor.");
472 goto handle_error;
473 }
474
475 /* Set DACL. */
476 if (!SetSecurityDescriptorDacl(psd, TRUE, acl, FALSE)) {
477 VLOG_ERR_RL(&rl, "Error while setting DACL.");
478 goto handle_error;
479 }
480
481 sa.nLength = sizeof sa;
922247c6 482 sa.bInheritHandle = TRUE;
06732e89
SV
483 sa.lpSecurityDescriptor = psd;
484
922247c6
AS
485 if (strlen(name) > 256) {
486 VLOG_ERR_RL(&rl, "Named pipe name too long.");
06732e89 487 goto handle_error;
922247c6 488 }
06732e89
SV
489
490 npipe = CreateNamedPipe(name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
491 PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT,
492 64, BUFSIZE, BUFSIZE, 0, &sa);
493 free(acl);
494 free(psd);
495 return npipe;
496
497handle_error:
498 free(acl);
499 free(psd);
500 return INVALID_HANDLE_VALUE;
922247c6
AS
501}
502
503/* Passive named pipe connect. This function creates a new named pipe and
504 * passes the old handle to the active stream. */
505static int
506pwindows_accept(struct pstream *pstream, struct stream **new_streamp)
507{
508 struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
509 DWORD last_error = 0;
510 DWORD cbRet;
511 HANDLE npipe;
512
513 /* If the connect operation was pending, verify the result. */
514 if (p->pending) {
515 if (!GetOverlappedResult(p->fd, &p->connect, &cbRet, FALSE)) {
516 last_error = GetLastError();
517 if (last_error == ERROR_IO_INCOMPLETE) {
518 /* If the operation is still pending, retry again. */
519 p->pending = true;
520 return EAGAIN;
521 } else {
522 VLOG_ERR_RL(&rl, "Could not connect named pipe. Last "
523 "error: %s", ovs_lasterror_to_string());
f4df0f09 524 DisconnectNamedPipe(p->fd);
922247c6
AS
525 return EINVAL;
526 }
527 }
528 p->pending = false;
529 }
530
531 if (!p->pending && !ConnectNamedPipe(p->fd, &p->connect)) {
532 last_error = GetLastError();
533 if (last_error == ERROR_IO_PENDING) {
534 /* Mark the accept operation as pending. */
535 p->pending = true;
536 return EAGAIN;
537 } else if (last_error != ERROR_PIPE_CONNECTED) {
538 VLOG_ERR_RL(&rl, "Could not connect synchronous named pipe. Last "
539 "error: %s", ovs_lasterror_to_string());
f4df0f09 540 DisconnectNamedPipe(p->fd);
922247c6
AS
541 return EINVAL;
542 } else {
543 /* If the pipe is connected, signal an event. */
544 SetEvent(&p->connect.hEvent);
545 }
546 }
547
548 npipe = create_pnpipe(p->pipe_path);
549 if (npipe == INVALID_HANDLE_VALUE) {
550 VLOG_ERR_RL(&rl, "Could not create a new named pipe after connect. ",
551 ovs_lasterror_to_string());
552 return ENOENT;
553 }
554
555 /* Give the handle p->fd to the new created active stream and specify it
556 * was created by an active stream. */
557 struct windows_stream *p_temp = xmalloc(sizeof *p_temp);
b7636967 558 stream_init(&p_temp->stream, &windows_stream_class, 0, xstrdup("unix"));
922247c6
AS
559 p_temp->fd = p->fd;
560 /* Specify it was created by a passive stream. */
561 p_temp->server = true;
562 /* Create events for read/write operations. */
563 memset(&p_temp->read, 0, sizeof(p_temp->read));
564 memset(&p_temp->write, 0, sizeof(p_temp->write));
565 p_temp->read.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
566 p_temp->write.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
567 p_temp->read_pending = false;
568 p_temp->write_pending = false;
569 p_temp->retry_connect = false;
570 p_temp->pipe_path = NULL;
571 *new_streamp = &p_temp->stream;
572
573 /* The passive handle p->fd will be the new created handle. */
574 p->fd = npipe;
575 memset(&p->connect, 0, sizeof(p->connect));
576 p->connect.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
577 p->pending = false;
578 return 0;
579}
580
581/* Passive named pipe close. */
582static void
583pwindows_close(struct pstream *pstream)
584{
585 struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
586 DisconnectNamedPipe(p->fd);
587 CloseHandle(p->fd);
588 CloseHandle(p->connect.hEvent);
589 maybe_unlink_and_free(p->unlink_path);
590 free(p->pipe_path);
591 free(p);
592}
593
594/* Passive named pipe wait. */
595static void
596pwindows_wait(struct pstream *pstream)
597{
598 struct pwindows_pstream *p = pwindows_pstream_cast(pstream);
599 poll_wevent_wait(p->connect.hEvent);
600}
601
602/* Passive named pipe. */
603static int
604pwindows_open(const char *name OVS_UNUSED, char *suffix,
605 struct pstream **pstreamp, uint8_t dscp OVS_UNUSED)
606{
607 char *bind_path;
608 int error;
609 HANDLE npipe;
610 char *orig_path;
611
612 char *path;
613 if (!strchr(suffix, ':')) {
614 path = xasprintf("%s/%s", ovs_rundir(), suffix);
615 } else {
616 path = xstrdup(suffix);
617 }
618
619 /* Try to create a file under the path location. */
620 FILE *file = fopen(path, "w");
621 if (!file) {
622 free(path);
623 error = errno;
624 VLOG_DBG_RL(&rl, "could not open %s (%s)", path, ovs_strerror(error));
625 return error;
626 } else {
627 fclose(file);
628 }
629
630 orig_path = xstrdup(path);
631 /* Strip slashes from path and create a named pipe using that newly created
632 * string. */
633 bind_path = xasprintf("%s%s", LOCAL_PREFIX, remove_slashes(path));
634 free(path);
635
636 npipe = create_pnpipe(bind_path);
637
638 if (npipe == INVALID_HANDLE_VALUE) {
639 VLOG_ERR_RL(&rl, "Could not create named pipe. Last error: %s",
640 ovs_lasterror_to_string());
641 return ENOENT;
642 }
643
644 struct pwindows_pstream *p = xmalloc(sizeof *p);
b7636967 645 pstream_init(&p->pstream, &pwindows_pstream_class, xstrdup(name));
922247c6
AS
646 p->fd = npipe;
647 p->unlink_path = orig_path;
648 memset(&p->connect, 0, sizeof(p->connect));
649 p->connect.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
650 p->pending = false;
651 p->pipe_path = bind_path;
652 *pstreamp = &p->pstream;
653 return 0;
654}
655
656const struct pstream_class pwindows_pstream_class = {
657 "punix",
658 false, /* probes */
659 pwindows_open, /* open */
660 pwindows_close, /* close */
661 pwindows_accept, /* accept */
662 pwindows_wait, /* wait */
663};
664
665/* Helper functions. */
666static void
667maybe_unlink_and_free(char *path)
668{
669 if (path) {
670 fatal_signal_unlink_file_now(path);
671 free(path);
672 }
673}