-
-
Notifications
You must be signed in to change notification settings - Fork 410
/
Copy pathprogress_ipc.c
254 lines (223 loc) · 5.97 KB
/
progress_ipc.c
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
/*
* Author: Christian Storm
* Copyright (C) 2017, Siemens AG
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdbool.h>
#include <progress_ipc.h>
#if defined(__FreeBSD__)
#define ENODATA ENOATTR
#define ETIME ETIMEDOUT
#endif
#ifdef CONFIG_SOCKET_PROGRESS_PATH
char *SOCKET_PROGRESS_PATH = (char*)CONFIG_SOCKET_PROGRESS_PATH;
#else
char *SOCKET_PROGRESS_PATH = NULL;
#endif
#define SOCKET_PROGRESS_DEFAULT "swupdateprog"
static inline int progress_is_major_version_compatible(unsigned int other_version)
{
return PROGRESS_API_MAJOR == ((other_version >> 16) & 0xFFFF);
}
char *get_prog_socket(void) {
if (!SOCKET_PROGRESS_PATH || !strlen(SOCKET_PROGRESS_PATH)) {
const char *socketdir = getenv("RUNTIME_DIRECTORY");
if(!socketdir){
socketdir = getenv("TMPDIR");
}
if(!socketdir){
socketdir = "/tmp";
}
if (asprintf(&SOCKET_PROGRESS_PATH, "%s/%s", socketdir, SOCKET_PROGRESS_DEFAULT) == -1)
return (char *)"/tmp/"SOCKET_PROGRESS_DEFAULT;
}
return SOCKET_PROGRESS_PATH;
}
/* Decrease timeout depending on elapsed time */
static int update_timeout(const struct timespec *initial_time, int *timeout_ms)
{
struct timespec current_time;
int diff_timeout_ms;
struct timespec elapsed;
int err = clock_gettime(CLOCK_MONOTONIC, ¤t_time);
if (err) {
return -errno;
}
elapsed.tv_sec = current_time.tv_sec - initial_time->tv_sec;
elapsed.tv_nsec = current_time.tv_nsec - initial_time->tv_nsec;
diff_timeout_ms = *timeout_ms - (elapsed.tv_sec*1000 + elapsed.tv_nsec/1E6);
*timeout_ms = diff_timeout_ms;
return 0;
}
static int progress_ipc_recv_ack(int fd, struct progress_connect_ack *ack)
{
struct timespec initial_time;
int err;
int ret = 0; /* 0 == success */
int timeout_ms = 5000; /* 5 s should be enough in most cases as the socket is local */
err = clock_gettime(CLOCK_MONOTONIC, &initial_time);
if (err) {
return -errno;
}
unsigned int size_to_read = sizeof(struct progress_connect_ack);
unsigned int offset = 0;
while (size_to_read > 0) {
int err_poll;
struct pollfd pfds[1];
pfds[0].fd = fd;
pfds[0].events = POLLIN;
do {
err_poll = poll(pfds, 1, timeout_ms);
int err_update_timeout = update_timeout(&initial_time, &timeout_ms);
if (err_update_timeout) return err_update_timeout;
} while (err_poll < 0 && errno == EINTR);
if (err_poll == -1) {
ret = -errno;
break;
} else if (err_poll == 0) {
/* Timeout */
ret = -ETIME;
break;
} else if (pfds[0].revents & POLLHUP) {
/* The peer closed its end of the channel */
/* (note that some operating systems also set POLLIN in this case) */
ret = -ECONNRESET;
break;
} else if (pfds[0].revents & POLLIN) {
/* There is a message to read */
int n = read(fd, (void*)ack + offset, size_to_read);
if (n == -1 && errno == EINTR) {
continue; /* redo poll() and timeout management */
} else if (0 == n) {
/* error, as at least 1 byte should be pending */
ret = -ENODATA;
} else if (n < 0) {
/* read error */
ret = -errno;
break;
}
size_to_read -= n;
offset += n;
} else {
/* unexpected error */
ret = -EOPNOTSUPP;
}
}
return ret;
}
/* Wait for the daemon to send an ACK
*
* Returns:
* 0 success
* <0 error (timeout, peer closed, invalid ACK, ...)
*/
static int progress_ipc_wait_for_ack(int fd)
{
struct progress_connect_ack ack;
int err = progress_ipc_recv_ack(fd, &ack);
if (err) {
return err;
}
if (! progress_is_major_version_compatible(ack.apiversion)) {
return -EBADMSG;
}
if (0 != strcmp(ack.magic, PROGRESS_CONNECT_ACK_MAGIC)) {
return -EBADMSG;
}
return 0;
}
static int _progress_ipc_connect(const char *socketpath, bool reconnect)
{
struct sockaddr_un servaddr;
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
int err;
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strncpy(servaddr.sun_path, socketpath, sizeof(servaddr.sun_path) - 1);
/*
* Check to get a valid socket
*/
if (fd < 0)
return -1;
do {
if (connect(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == 0) {
break;
}
if (!reconnect) {
fprintf(stderr, "cannot communicate with SWUpdate via %s\n", socketpath);
close(fd);
return -1;
}
usleep(10000);
} while (true);
/* Connected. Wait for ACK */
err = progress_ipc_wait_for_ack(fd);
if (err < 0) {
close(fd);
return -1;
}
return fd;
}
int progress_ipc_connect_with_path(const char *socketpath, bool reconnect) {
return _progress_ipc_connect(socketpath, reconnect);
}
int progress_ipc_connect(bool reconnect)
{
return _progress_ipc_connect(get_prog_socket(), reconnect);
}
int progress_ipc_receive(int *connfd, struct progress_msg *msg) {
int ret = read(*connfd, msg, sizeof(*msg));
if (ret == -1 && (errno == EAGAIN || errno == EINTR))
return 0;
/*
* size of message can vary if the API version does not match
* First check it to return a correct error, else it always
* return -1.
*/
if (ret > sizeof(msg->apiversion) && (msg->apiversion != PROGRESS_API_VERSION))
return -EBADMSG;
if (ret != sizeof(*msg)) {
close(*connfd);
*connfd = -1;
return -1;
}
return ret;
}
int progress_ipc_receive_nb(int *connfd, struct progress_msg *msg) {
int ret = -1;
int err_poll;
struct pollfd pfds[1];
pfds[0].fd = *connfd;
pfds[0].events = POLLIN;
do {
err_poll = poll(pfds, 1, 0);
} while (err_poll == -1 && errno == EINTR);
if (err_poll == -1) {
/* poll error */
ret = -1;
} else if (err_poll == 0) {
/* no pending message */
ret = 0;
} else if (pfds[0].revents & POLLIN) {
/* there is a message to read or the peer closed its end of the channel */
/* (some operating systems set POLLIN|POLLHUP on this later case) */
ret = progress_ipc_receive(connfd, msg);
} else if (pfds[0].revents & POLLHUP) {
/* the peer closed its end of the channel */
ret = -1;
} else {
/* unexpected error */
ret = -1;
}
return ret;
}