1 /*
2 * Copyright 2018 Oticon A/S
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6 #include "bs_pc_base_types.h"
7 #include "bs_pc_base.h"
8 #include "bs_tracing.h"
9 #include "bs_oswrap.h"
10 #include "bs_string.h"
11 #include <signal.h>
12 #include <string.h>
13 #include <dirent.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <errno.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <pwd.h>
20 #include <errno.h>
21
22
23 bool is_base_com_initialized = false; //Used by the BackChannel to avoid deadlocks
24 char *pb_com_path = NULL;
25 int pb_com_path_length = 0;
26
27 /**
28 * Create a FIFO if it doesn't exist
29 *
30 * Note that it may already exist, and/or that some other program
31 * may be racing to create at the same time
32 */
pb_create_fifo_if_not_there(const char * fifo_path)33 int pb_create_fifo_if_not_there(const char *fifo_path) {
34 if ((access(fifo_path, F_OK) == -1) && (mkfifo(fifo_path, S_IRWXG | S_IRWXU) != 0) && (access(fifo_path, F_OK) == -1)) {
35 bs_trace_warning_line("Can not create %s\n", fifo_path);
36 return -1;
37 }
38 return 0;
39 }
40
41 /**
42 * Create the comm folder if it doesn't exist
43 * And sets its name in cb_com_path
44 * Returns
45 * the length of the cb_com_path string if it succeeds
46 * -1 otherwise
47 */
pb_create_com_folder(const char * s)48 int pb_create_com_folder(const char *s) {
49 char *UserName = NULL;
50 int UserNameLength = 0;
51 struct passwd *pw;
52
53 uid_t euid = geteuid();
54 errno = 0;
55 pw = getpwuid(euid);
56 if (pw != NULL) {
57 UserName = pw->pw_name;
58 } else if (!((errno == 0) || (errno == ENOENT) || (errno== ESRCH) || (errno== EBADF) || (errno == EPERM))) {
59 bs_trace_warning_line("Warning, completely unexpected error trying to get the user name will try to recover but will likely fail"
60 "(euid=%i, errno=%i)\n", euid, errno);
61 } else {
62 //Otherwise (pw == NULL AND any of those errors), it seems the user is not a real user, or the connection to the NIS server is down or..
63 bs_trace_info_line(2, "You seem to be running this program as a not real user(?). Will try to recover"
64 "(euid=%i, errno=%i)\n", euid, errno);
65 }
66
67 if (UserName == NULL) {
68 //Let's hope the POSIX required LOGNAME environment variable is set
69 UserName = getenv("LOGNAME");
70 }
71 if (UserName == NULL) {
72 //It was not.. let's hope for USER..
73 UserName = getenv("USER");
74 }
75 if (UserName == NULL) {
76 bs_trace_error_line("Couldn't get the user name to build the tmp path for the interprocess comm (euid=%i)"
77 "(this shouldn't have happened, could not find the environment variables LOGNAME or USER either), errno=%i\n", euid, errno);
78 }
79
80 UserNameLength = strlen(UserName);
81
82 pb_com_path = (char*)bs_calloc(10 + strlen(s) + UserNameLength, sizeof(char));
83
84 sprintf(pb_com_path, "/tmp/bs_%s", UserName);
85
86 if (bs_createfolder(pb_com_path) != 0) {
87 free(pb_com_path);
88 pb_com_path = NULL;
89 return -1;
90 }
91 sprintf(&pb_com_path[8+UserNameLength], "/%s", s);
92 if (bs_createfolder(pb_com_path) != 0) {
93 free(pb_com_path);
94 pb_com_path = NULL;
95 return -1;
96 }
97 return 9 + UserNameLength + strlen(s);
98 }
99
pb_send_payload(int ff,void * buf,size_t size)100 void pb_send_payload(int ff, void *buf, size_t size) {
101 if (size) {
102 if (buf==NULL) {
103 bs_trace_error_line("Null pointer!!\n");
104 }
105 write(ff, buf, size);
106 }
107 }
108
109 //#define NO_LOCK_FILE
110
111 #if !defined(NO_LOCK_FILE)
lock_file_fill(const char * filename,long int my_pid)112 static void lock_file_fill(const char *filename, long int my_pid){
113 FILE *file;
114
115 file = bs_fopen(filename, "w");
116 fprintf(file, "%li\n",(long) my_pid);
117 #if defined(__linux)
118 long long unsigned int starttime = bs_get_process_start_time(my_pid);
119 fprintf(file,"%llu\n",starttime);
120 #endif
121 fclose(file);
122 }
123 #endif
124
125 /**
126 * Check if the lock file exists
127 * * If it doesn't create one for us
128 * * If the lock file exists:
129 * * If the process who created it is still running, we stop.
130 * * If the process who created it is not running, we print a warning and risk it
131 * * If we cannot determine who created it, we stop.
132 *
133 * Returns 0 if we consider it safe enough to continue
134 * Something else otherwise
135 */
test_and_create_lock_file(const char * filename)136 static int test_and_create_lock_file(const char *filename) {
137 #if !defined(NO_LOCK_FILE)
138 pid_t my_pid = getpid();
139
140 if (access(filename, F_OK) == -1) {
141 // The file does not exist. So unless somebody is racing us, we are safe to go
142 lock_file_fill(filename, my_pid);
143 return 0;
144 }
145
146 bs_trace_warning_line("Previous lock file found %s\n", filename);
147
148 FILE *file;
149 bool corrupt_file = false;
150 int other_dead = 1;
151 long int his_pid;
152 #if defined(__linux)
153 long long unsigned int his_starttime;
154 #endif
155
156 file = bs_fopen(filename, "r");
157 if ( fscanf(file, "%li\n", &his_pid) != 1 ) {
158 corrupt_file = true;
159 }
160 #if defined(__linux)
161 if ((corrupt_file == false) && (fscanf(file,"%llu\n", &his_starttime) != 1)) {
162 corrupt_file = true;
163 }
164 #endif
165
166 fclose(file);
167
168 if (corrupt_file) { //We are provably racing the other process, we stop
169 bs_trace_warning_line("Found previous lock owned by unknown process, we may be racing each other => aborting\n");
170 return 1;
171 }
172
173 other_dead = kill(his_pid, 0);
174
175 if (other_dead == 0) { //a process with the pid exists
176 #if defined(__linux)
177 /* To be sure the pid was not reused, let's check that the process start time matches */
178
179 uint64_t other_start_time = bs_get_process_start_time(his_pid);
180 if (his_starttime == other_start_time) { //it is the same process
181 bs_trace_warning_line("Found a previous, still RUNNING process w pid %li with the same sim_id and device port which would interfere with this one, aborting\n", his_pid);
182 } else {
183 other_dead = 1;
184 }
185 #else
186 bs_trace_warning_line("Found a previous, still RUNNING process w pid %li with the same sim_id and device port which would interfere with this one, aborting\n", his_pid);
187 #endif
188 }
189
190 if (other_dead){
191 bs_trace_warning_line("Found previous lock owned by DEAD process (pid was %li), will attempt to take over\n", his_pid);
192 lock_file_fill(filename, my_pid);
193 return 0;
194 } else {
195 return 1;
196 }
197
198 #else
199 return 0;
200 #endif
201 }
202
remove_lock_file(char ** file_path)203 static void remove_lock_file(char** file_path){
204 if (*file_path) {
205 remove(*file_path);
206 free(*file_path);
207 *file_path = NULL;
208 }
209 }
210
phy_test_and_create_lock_file(pb_phy_state_t * this,const char * phy_id)211 static int phy_test_and_create_lock_file(pb_phy_state_t *this, const char *phy_id){
212 int flen = pb_com_path_length + 20 + strlen(phy_id);
213 this->lock_path = (char*) bs_calloc(flen, sizeof(char));
214 sprintf(this->lock_path, "%s/%s.phy.lock", pb_com_path, phy_id);
215
216 int ret = test_and_create_lock_file(this->lock_path);
217 if (ret) {
218 free(this->lock_path);
219 this->lock_path = NULL;
220 }
221 return ret;
222 }
223
device_test_and_create_lock_file(pb_dev_state_t * this,const char * phy_id,unsigned int dev_nbr)224 static int device_test_and_create_lock_file(pb_dev_state_t *this, const char *phy_id, unsigned int dev_nbr){
225 int flen = pb_com_path_length + 20 + strlen(phy_id) + bs_number_strlen(dev_nbr);
226
227 this->lock_path = (char*) bs_calloc(flen, sizeof(char));
228
229 sprintf(this->lock_path, "%s/%s.d%i.lock", pb_com_path, phy_id, dev_nbr);
230 int ret = test_and_create_lock_file(this->lock_path);
231 if (ret) {
232 free(this->lock_path);
233 this->lock_path = NULL;
234 }
235 return ret;
236 }
237
238
239 /**
240 * Initialize the communication with the devices:
241 *
242 * inputs:
243 * this Pointer to structure where the connection status will be kept.
244 * MUST be initialized with zeroes.
245 * s String identifying the simulation
246 * p String identifying this phy in this simulation
247 * n How many devices we expect during the simulation
248 *
249 * returns:
250 * 0 if ok. Any other number on error
251 */
pb_phy_initcom(pb_phy_state_t * this,const char * s,const char * p,uint n)252 int pb_phy_initcom(pb_phy_state_t *this, const char* s, const char *p, uint n) {
253
254 signal(SIGPIPE, SIG_IGN);
255
256 if (this->device_connected) {
257 bs_trace_warning_line("%s called twice in a simulation\n", __func__);
258 return -1;
259 }
260
261 pb_com_path_length = pb_create_com_folder(s);
262
263 this->device_connected = NULL;
264 this->lock_path = NULL;
265
266 if ( phy_test_and_create_lock_file(this, p) ) {
267 return -1;
268 }
269
270 this->n_devices = n;
271 this->device_connected = (bool *) bs_calloc(n, sizeof(bool));
272 this->ff_path_dtp = (char **) bs_calloc(n, sizeof(char *));
273 this->ff_path_ptd = (char **) bs_calloc(n, sizeof(char *));
274 this->ff_dtp = (int *) bs_calloc(n, sizeof(int *));
275 this->ff_ptd = (int *) bs_calloc(n, sizeof(int *));
276
277 for (int d = 0; d < this->n_devices; d++) {
278 int flen = pb_com_path_length + 30 + strlen(p) + bs_number_strlen(d);
279 this->ff_path_dtp[d] = (char *)bs_calloc(flen, sizeof(char));
280 this->ff_path_ptd[d] = (char *)bs_calloc(flen, sizeof(char));
281 sprintf(this->ff_path_dtp[d], "%s/%s.d%i.dtp", pb_com_path, p, d);
282 sprintf(this->ff_path_ptd[d], "%s/%s.d%i.ptd", pb_com_path, p, d);
283
284 if ((pb_create_fifo_if_not_there(this->ff_path_dtp[d]) != 0)
285 || (pb_create_fifo_if_not_there(this->ff_path_ptd[d]) != 0)) {
286 pb_phy_disconnect_devices(this);
287 bs_trace_error_line("Could not create FIFOs to device %i\n", d);
288 }
289
290 if ((this->ff_ptd[d] = open(this->ff_path_ptd[d], O_WRONLY)) == -1) {
291 this->ff_ptd[d] = 0;
292 pb_phy_disconnect_devices(this);
293 bs_trace_error_line("Opening FIFO from phy to device %i failed\n", d);
294 }
295 if ((this->ff_dtp[d] = open(this->ff_path_dtp[d], O_RDONLY)) == -1) {
296 this->ff_dtp[d] = 0;
297 pb_phy_disconnect_devices(this);
298 bs_trace_error_line("Opening FIFO from device %i to phy failed\n", d);
299 }
300
301 this->device_connected[d] = true;
302 bs_trace_raw(9,"Connected to device %i\n", d);
303 }
304
305 return 0;
306 }
307
pb_phy_free_one_device(pb_phy_state_t * this,int d)308 void pb_phy_free_one_device(pb_phy_state_t *this, int d) {
309 if (this->ff_dtp[d]) {
310 close(this->ff_dtp[d]);
311 this->ff_dtp[d] = 0;
312 }
313 if (this->ff_path_dtp[d]) {
314 remove(this->ff_path_dtp[d]);
315 free(this->ff_path_dtp[d]);
316 this->ff_path_dtp[d] = NULL;
317 }
318 if (this->ff_ptd[d]) {
319 close(this->ff_ptd[d]);
320 this->ff_ptd[d] = 0;
321 }
322 if (this->ff_path_ptd[d]) {
323 remove(this->ff_path_ptd[d]);
324 free(this->ff_path_ptd[d]);
325 this->ff_path_ptd[d] = NULL;
326 }
327 this->device_connected[d] = false;
328 }
329
330 /**
331 * Disconnect all devices we are still connected to,
332 * free all the memory we used and delete the FIFOs
333 *
334 * It is safe to call this function multiple times
335 */
pb_phy_disconnect_devices(pb_phy_state_t * this)336 void pb_phy_disconnect_devices(pb_phy_state_t *this) {
337
338 if (this->device_connected != NULL) {
339 pc_header_t header = PB_MSG_DISCONNECT;
340 for (int d = 0; d < this->n_devices; d++) {
341 if (this->ff_ptd[d]) {
342 write(this->ff_ptd[d], &header, sizeof(header));
343 }
344 pb_phy_free_one_device(this, d);
345 }
346
347 if (pb_com_path) {
348 rmdir(pb_com_path);
349 free(pb_com_path);
350 pb_com_path = NULL;
351 }
352
353 if (this->device_connected) {
354 free(this->device_connected);
355 this->device_connected = NULL;
356 }
357 if (this->ff_path_dtp) {
358 free(this->ff_path_dtp);
359 this->ff_path_dtp = NULL;
360 }
361 if (this->ff_dtp) {
362 free(this->ff_dtp);
363 this->ff_dtp = NULL;
364 }
365 if (this->ff_path_ptd) {
366 free(this->ff_path_ptd);
367 this->ff_path_ptd = NULL;
368 }
369 if (this->ff_ptd) {
370 free(this->ff_ptd);
371 this->ff_ptd = NULL;
372 }
373 }
374 remove_lock_file(&this->lock_path);
375 }
376
377 /**
378 * Check if we are connected to this device (or any device)
379 * Return 1 if we are
380 */
pb_phy_is_connected_to_device(pb_phy_state_t * this,uint d)381 int pb_phy_is_connected_to_device(pb_phy_state_t *this, uint d){
382 if ((this->device_connected == NULL) || (!this->device_connected[d])) {
383 bs_trace_error_line("Programming error while trying to talk to device %i\n", d);
384 return 0;
385 }
386 return 1;
387 }
388
389 /**
390 * Respond to the device at the end of wait
391 */
pb_phy_resp_wait(pb_phy_state_t * this,uint d)392 void pb_phy_resp_wait(pb_phy_state_t *this, uint d) {
393 if ( pb_phy_is_connected_to_device(this, d) ) {
394 pc_header_t header = PB_MSG_WAIT_END;
395 write(this->ff_ptd[d], &header, sizeof(header));
396 }
397 }
398
399 /**
400 * Get (and return) the next request from this device
401 */
pb_phy_get_next_request(pb_phy_state_t * this,uint d)402 pc_header_t pb_phy_get_next_request(pb_phy_state_t *this, uint d) {
403 pc_header_t header = PB_MSG_DISCONNECT;
404
405 if ( pb_phy_is_connected_to_device(this, d) ) {
406 int n = read(this->ff_dtp[d], &header, sizeof(header));
407 if (n < sizeof(header)) {
408 bs_trace_warning_line("Device %u left the party unsuspectingly.. I treat it as if it disconnected\n", d);
409 }
410
411 if ((header == PB_MSG_DISCONNECT) || (header == PB_MSG_TERMINATE)) {
412 //if the read failed or the device really wants to disconnect
413 pb_phy_free_one_device(this, d);
414 }
415 }
416 return header;
417 }
418
pb_phy_get_wait_s(pb_phy_state_t * this,uint d,pb_wait_t * wait_s)419 void pb_phy_get_wait_s(pb_phy_state_t *this, uint d, pb_wait_t *wait_s) {
420 if ( pb_phy_is_connected_to_device(this, d) ) {
421 read(this->ff_dtp[d], wait_s, sizeof(pb_wait_t));
422 }
423 }
424
425 /**
426 * Initialize the communication interface with the phy
427 *
428 * inputs:
429 * this Pointer to structure where the connection status will be keps.
430 * MUST be initialized with zeroes.
431 * d The device number this device will have in this phy
432 * s String identifying the simulation
433 * p String identifying this phy in this simulation
434 *
435 * returns:
436 * 0 if ok. Any other number on error
437 */
pb_dev_init_com(pb_dev_state_t * this,uint d,const char * s,const char * p)438 int pb_dev_init_com(pb_dev_state_t *this, uint d, const char* s, const char *p) {
439
440 if (this->connected) {
441 bs_trace_warning_line("%s called twice in a simulation\n", __func__);
442 return -1;
443 }
444
445 /* In case we fail, we initialize them to "invalid" content*/
446 this->ff_path_dtp = NULL;
447 this->ff_path_ptd = NULL;
448
449 this->ff_ptd = 0; /*0 == stdin == not one we would have used */
450 this->ff_dtp = 0;
451
452 if ((s == NULL) || (p == NULL)) {
453 bs_trace_error_line("The simulation and phy identification strings need to be provided\n");
454 }
455
456 this->this_dev_nbr = d;
457 pb_com_path_length = pb_create_com_folder(s);
458
459 if ( device_test_and_create_lock_file(this, p, d) ) {
460 bs_trace_error_line("Failed to get lock\n");
461 }
462
463 int flen = pb_com_path_length + strlen(p) + bs_number_strlen(d) + 30;
464 this->ff_path_dtp = (char *) bs_calloc(flen, sizeof(char));
465 this->ff_path_ptd = (char *) bs_calloc(flen, sizeof(char));
466 sprintf(this->ff_path_dtp, "%s/%s.d%i.dtp", pb_com_path, p, d);
467 sprintf(this->ff_path_ptd, "%s/%s.d%i.ptd", pb_com_path, p, d);
468
469 if ((pb_create_fifo_if_not_there(this->ff_path_dtp) != 0)
470 || (pb_create_fifo_if_not_there(this->ff_path_ptd) != 0)) {
471 pb_dev_clean_up(this);
472 bs_trace_error_line("Could not create FIFOs");
473 }
474
475 if (((this->ff_ptd = open(this->ff_path_ptd, O_RDONLY )) == -1)) {
476 this->ff_ptd = 0;
477 pb_dev_clean_up(this);
478 bs_trace_error_line("Opening FIFO from phy to device failed\n");
479 }
480 if (((this->ff_dtp = open(this->ff_path_dtp, O_WRONLY )) == -1)) {
481 this->ff_dtp = 0;
482 pb_dev_clean_up(this);
483 bs_trace_error_line("Opening FIFO from device to phy failed\n");
484 }
485
486 this->connected = true;
487 is_base_com_initialized = true;
488 return 0;
489 }
490
491 /**
492 * Attempt to terminate the simulation and disconnect
493 */
pb_dev_terminate(pb_dev_state_t * this)494 void pb_dev_terminate(pb_dev_state_t *this) {
495 if (this->connected) {
496 pc_header_t header = PB_MSG_TERMINATE;
497
498 write(this->ff_dtp, &header, sizeof(header));
499 pb_dev_clean_up(this);
500 }
501 }
502
503 /**
504 * Disconnect from the phy
505 */
pb_dev_disconnect(pb_dev_state_t * this)506 void pb_dev_disconnect(pb_dev_state_t *this) {
507 if (this->connected) {
508 pc_header_t header = PB_MSG_DISCONNECT;
509
510 write(this->ff_dtp, &header, sizeof(header));
511 pb_dev_clean_up(this);
512 }
513 }
514
515 /*
516 * Try to delete the FIFOs, clear memory and try to delete the directory
517 *
518 * It is safe to call this function (unnecessarily) several times
519 */
pb_dev_clean_up(pb_dev_state_t * this)520 void pb_dev_clean_up(pb_dev_state_t *this) {
521
522 remove_lock_file(&this->lock_path);
523
524 this->connected = false; //we don't want any possible future call to libphycom to attempt to talk with the phy
525
526 if (this->ff_path_dtp) {
527 if (this->ff_dtp) {
528 close(this->ff_dtp);
529 this->ff_dtp = 0;
530 }
531
532 remove(this->ff_path_dtp);
533 free(this->ff_path_dtp);
534 this->ff_path_dtp = NULL;
535 }
536
537 if (this->ff_path_ptd) {
538 if (this->ff_ptd) {
539 close(this->ff_ptd);
540 this->ff_ptd = 0;
541 }
542 remove(this->ff_path_ptd);
543 free(this->ff_path_ptd);
544 this->ff_path_ptd = NULL;
545 }
546
547 if (pb_com_path != NULL) {
548 rmdir(pb_com_path);
549 free(pb_com_path);
550 pb_com_path = NULL;
551 }
552 }
553
554 /**
555 * Read from a FIFO n_bytes
556 * returns -1 on failure (it can't read n_bytes, and cleans up),
557 * otherwise returns n_bytes
558 */
pb_dev_read(pb_dev_state_t * this,void * buf,size_t n_bytes)559 int pb_dev_read(pb_dev_state_t *this, void *buf, size_t n_bytes) {
560 int read_b;
561
562 read_b = read(this->ff_ptd, buf, n_bytes);
563
564 if (n_bytes == read_b) {
565 return read_b;
566 }
567
568 bs_trace_warning_line(COM_FAILED_ERROR " (tried to get %i got %i bytes)\n", n_bytes, read_b);
569 pb_dev_clean_up(this);
570 return -1;
571 }
572
573 /**
574 * Request a non blocking wait to the phy
575 * Note that eventually the caller needs to pick the wait response
576 * from the phy with cb_dev_pick_wait_resp()
577 */
pb_dev_request_wait_nonblock(pb_dev_state_t * this,pb_wait_t * wait_s)578 int pb_dev_request_wait_nonblock(pb_dev_state_t *this, pb_wait_t *wait_s) {
579 CHECK_CONNECTED(this->connected);
580 pb_send_msg(this->ff_dtp, PB_MSG_WAIT, (void *)wait_s, sizeof(pb_wait_t));
581 return 0;
582 }
583
584 /**
585 * Block until getting a wait response from the phy
586 * If everything goes ok, the phy has just reached the
587 * requested end time and 0 is returned
588 * Otherwise, we should disconnect (-1 will be returned)
589 */
pb_dev_pick_wait_resp(pb_dev_state_t * this)590 int pb_dev_pick_wait_resp(pb_dev_state_t *this) {
591 CHECK_CONNECTED(this->connected);
592
593 pc_header_t header = PB_MSG_DISCONNECT;
594
595 if (pb_dev_read(this, &header, sizeof(header)) == -1) {
596 return -1;
597 }
598
599 if (header == PB_MSG_DISCONNECT) {
600 pb_dev_clean_up(this);
601 return -1;
602 } else if (header == PB_MSG_WAIT_END) {
603 return 0;
604 } else {
605 INVALID_RESP(header);
606 return -1;
607 }
608 }
609
610 /**
611 * Request a wait to the phy and block until receiving the response
612 * If everything goes ok 0 is returned
613 *
614 * Otherwise, we should disconnect (-1 will be returned)
615 */
pb_dev_request_wait_block(pb_dev_state_t * this,pb_wait_t * wait_s)616 int pb_dev_request_wait_block(pb_dev_state_t *this, pb_wait_t *wait_s) {
617 CHECK_CONNECTED(this->connected);
618 int ret;
619 ret = pb_dev_request_wait_nonblock(this, wait_s);
620 if (ret)
621 return ret;
622 return pb_dev_pick_wait_resp(this);
623 }
624