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  */
pb_test_and_create_lock_file(const char * filename)136 int pb_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 
pb_remove_lock_file(char ** file_path)203 void pb_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 = pb_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 
pb_device_test_and_create_lock_file(pb_dev_state_t * this,const char * phy_id,unsigned int dev_nbr)224 int pb_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 = pb_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   pb_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 ( pb_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   pb_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