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   }
303 
304   return 0;
305 }
306 
pb_phy_free_one_device(pb_phy_state_t * this,int d)307 void pb_phy_free_one_device(pb_phy_state_t *this, int d) {
308   if (this->ff_dtp[d]) {
309     close(this->ff_dtp[d]);
310     this->ff_dtp[d] = 0;
311   }
312   if (this->ff_path_dtp[d]) {
313     remove(this->ff_path_dtp[d]);
314     free(this->ff_path_dtp[d]);
315     this->ff_path_dtp[d] = NULL;
316   }
317   if (this->ff_ptd[d]) {
318     close(this->ff_ptd[d]);
319     this->ff_ptd[d] = 0;
320   }
321   if (this->ff_path_ptd[d]) {
322     remove(this->ff_path_ptd[d]);
323     free(this->ff_path_ptd[d]);
324     this->ff_path_ptd[d] = NULL;
325   }
326   this->device_connected[d] = false;
327 }
328 
329 /**
330  * Disconnect all devices we are still connected to,
331  * free all the memory we used and delete the FIFOs
332  *
333  * It is safe to call this function multiple times
334  */
pb_phy_disconnect_devices(pb_phy_state_t * this)335 void pb_phy_disconnect_devices(pb_phy_state_t *this) {
336 
337   if (this->device_connected != NULL) {
338     pc_header_t header = PB_MSG_DISCONNECT;
339     for (int d = 0; d < this->n_devices; d++) {
340       if (this->ff_ptd[d]) {
341         write(this->ff_ptd[d], &header, sizeof(header));
342       }
343       pb_phy_free_one_device(this, d);
344     }
345 
346     if (pb_com_path) {
347       rmdir(pb_com_path);
348       free(pb_com_path);
349       pb_com_path = NULL;
350     }
351 
352     if (this->device_connected) {
353       free(this->device_connected);
354       this->device_connected = NULL;
355     }
356     if (this->ff_path_dtp) {
357       free(this->ff_path_dtp);
358       this->ff_path_dtp = NULL;
359     }
360     if (this->ff_dtp) {
361       free(this->ff_dtp);
362       this->ff_dtp = NULL;
363     }
364     if (this->ff_path_ptd) {
365       free(this->ff_path_ptd);
366       this->ff_path_ptd = NULL;
367     }
368     if (this->ff_ptd) {
369       free(this->ff_ptd);
370       this->ff_ptd = NULL;
371     }
372   }
373   remove_lock_file(&this->lock_path);
374 }
375 
376 /**
377  * Check if we are connected to this device (or any device)
378  * Return 1 if we are
379  */
pb_phy_is_connected_to_device(pb_phy_state_t * this,uint d)380 int pb_phy_is_connected_to_device(pb_phy_state_t *this, uint d){
381   if ((this->device_connected == NULL) || (!this->device_connected[d])) {
382     bs_trace_error_line("Programming error while trying to talk to device %i\n", d);
383     return 0;
384   }
385   return 1;
386 }
387 
388 /**
389  * Respond to the device at the end of wait
390  */
pb_phy_resp_wait(pb_phy_state_t * this,uint d)391 void pb_phy_resp_wait(pb_phy_state_t *this, uint d) {
392   if ( pb_phy_is_connected_to_device(this, d) ) {
393     pc_header_t header = PB_MSG_WAIT_END;
394     write(this->ff_ptd[d], &header, sizeof(header));
395   }
396 }
397 
398 /**
399  * Get (and return) the next request from this device
400  */
pb_phy_get_next_request(pb_phy_state_t * this,uint d)401 pc_header_t pb_phy_get_next_request(pb_phy_state_t *this, uint d) {
402   pc_header_t header = PB_MSG_DISCONNECT;
403 
404   if ( pb_phy_is_connected_to_device(this, d) ) {
405     int n = read(this->ff_dtp[d], &header, sizeof(header));
406     if (n < sizeof(header)) {
407       bs_trace_warning_line("Device %u left the party unsuspectingly.. I treat it as if it disconnected\n", d);
408     }
409 
410     if ((header == PB_MSG_DISCONNECT) || (header == PB_MSG_TERMINATE)) {
411       //if the read failed or the device really wants to disconnect
412       pb_phy_free_one_device(this, d);
413     }
414   }
415   return header;
416 }
417 
pb_phy_get_wait_s(pb_phy_state_t * this,uint d,pb_wait_t * wait_s)418 void pb_phy_get_wait_s(pb_phy_state_t *this, uint d, pb_wait_t *wait_s) {
419   if ( pb_phy_is_connected_to_device(this, d) ) {
420     read(this->ff_dtp[d], wait_s, sizeof(pb_wait_t));
421   }
422 }
423 
424 /**
425  * Initialize the communication interface with the phy
426  *
427  * inputs:
428  *  this  Pointer to structure where the connection status will be keps.
429  *         MUST be initialized with zeroes.
430  *  d     The device number this device will have in this phy
431  *  s     String identifying the simulation
432  *  p     String identifying this phy in this simulation
433  *
434  * returns:
435  *   0 if ok. Any other number on error
436  */
pb_dev_init_com(pb_dev_state_t * this,uint d,const char * s,const char * p)437 int pb_dev_init_com(pb_dev_state_t *this, uint d, const char* s, const char *p) {
438 
439   if (this->connected) {
440     bs_trace_warning_line("%s called twice in a simulation\n", __func__);
441     return -1;
442   }
443 
444   /* In case we fail, we initialize them to "invalid" content*/
445   this->ff_path_dtp = NULL;
446   this->ff_path_ptd = NULL;
447 
448   this->ff_ptd = 0; /*0 == stdin == not one we would have used */
449   this->ff_dtp = 0;
450 
451   if ((s == NULL) || (p  == NULL)) {
452     bs_trace_error_line("The simulation and phy identification strings need to be provided\n");
453   }
454 
455   this->this_dev_nbr = d;
456   pb_com_path_length = pb_create_com_folder(s);
457 
458   if ( device_test_and_create_lock_file(this, p, d) ) {
459     bs_trace_error_line("Failed to get lock\n");
460   }
461 
462   int flen = pb_com_path_length + strlen(p) + bs_number_strlen(d) + 30;
463   this->ff_path_dtp = (char *) bs_calloc(flen, sizeof(char));
464   this->ff_path_ptd = (char *) bs_calloc(flen, sizeof(char));
465   sprintf(this->ff_path_dtp, "%s/%s.d%i.dtp", pb_com_path, p, d);
466   sprintf(this->ff_path_ptd, "%s/%s.d%i.ptd", pb_com_path, p, d);
467 
468   if ((pb_create_fifo_if_not_there(this->ff_path_dtp) != 0)
469       || (pb_create_fifo_if_not_there(this->ff_path_ptd) != 0)) {
470     pb_dev_clean_up(this);
471     bs_trace_error_line("Could not create FIFOs");
472   }
473 
474   if (((this->ff_ptd = open(this->ff_path_ptd, O_RDONLY )) == -1)) {
475     this->ff_ptd = 0;
476     pb_dev_clean_up(this);
477     bs_trace_error_line("Opening FIFO from phy to device failed\n");
478   }
479   if (((this->ff_dtp = open(this->ff_path_dtp, O_WRONLY )) == -1)) {
480     this->ff_dtp = 0;
481     pb_dev_clean_up(this);
482     bs_trace_error_line("Opening FIFO from device to phy failed\n");
483   }
484 
485   this->connected = true;
486   is_base_com_initialized = true;
487   return 0;
488 }
489 
490 /**
491  * Attempt to terminate the simulation and disconnect
492  */
pb_dev_terminate(pb_dev_state_t * this)493 void pb_dev_terminate(pb_dev_state_t *this) {
494   if (this->connected) {
495     pc_header_t header = PB_MSG_TERMINATE;
496 
497     write(this->ff_dtp, &header, sizeof(header));
498     pb_dev_clean_up(this);
499   }
500 }
501 
502 /**
503  * Disconnect from the phy
504  */
pb_dev_disconnect(pb_dev_state_t * this)505 void pb_dev_disconnect(pb_dev_state_t *this) {
506   if (this->connected) {
507     pc_header_t header = PB_MSG_DISCONNECT;
508 
509     write(this->ff_dtp, &header, sizeof(header));
510     pb_dev_clean_up(this);
511   }
512 }
513 
514 /*
515  * Try to delete the FIFOs, clear memory and try to delete the directory
516  *
517  * It is safe to call this function (unnecessarily) several times
518  */
pb_dev_clean_up(pb_dev_state_t * this)519 void pb_dev_clean_up(pb_dev_state_t *this) {
520 
521   remove_lock_file(&this->lock_path);
522 
523   this->connected = false; //we don't want any possible future call to libphycom to attempt to talk with the phy
524 
525   if (this->ff_path_dtp) {
526     if (this->ff_dtp) {
527       close(this->ff_dtp);
528       this->ff_dtp = 0;
529     }
530 
531     remove(this->ff_path_dtp);
532     free(this->ff_path_dtp);
533     this->ff_path_dtp = NULL;
534   }
535 
536   if (this->ff_path_ptd) {
537     if (this->ff_ptd) {
538       close(this->ff_ptd);
539       this->ff_ptd = 0;
540     }
541     remove(this->ff_path_ptd);
542     free(this->ff_path_ptd);
543     this->ff_path_ptd = NULL;
544   }
545 
546   if (pb_com_path != NULL) {
547     rmdir(pb_com_path);
548     free(pb_com_path);
549     pb_com_path = NULL;
550   }
551 }
552 
553 /**
554  * Read from a FIFO n_bytes
555  * returns -1 on failure (it can't read n_bytes, and cleans up),
556  * otherwise returns n_bytes
557  */
pb_dev_read(pb_dev_state_t * this,void * buf,size_t n_bytes)558 int pb_dev_read(pb_dev_state_t *this, void *buf, size_t n_bytes) {
559   int read_b;
560 
561   read_b = read(this->ff_ptd, buf, n_bytes);
562 
563   if (n_bytes == read_b) {
564     return read_b;
565   }
566 
567   bs_trace_warning_line(COM_FAILED_ERROR " (tried to get %i got %i bytes)\n", n_bytes, read_b);
568   pb_dev_clean_up(this);
569   return -1;
570 }
571 
572 /**
573  * Request a non blocking wait to the phy
574  * Note that eventually the caller needs to pick the wait response
575  * from the phy with cb_dev_pick_wait_resp()
576  */
pb_dev_request_wait_nonblock(pb_dev_state_t * this,pb_wait_t * wait_s)577 int pb_dev_request_wait_nonblock(pb_dev_state_t *this, pb_wait_t *wait_s) {
578   CHECK_CONNECTED(this->connected);
579   pb_send_msg(this->ff_dtp, PB_MSG_WAIT, (void *)wait_s, sizeof(pb_wait_t));
580   return 0;
581 }
582 
583 /**
584  * Block until getting a wait response from the phy
585  * If everything goes ok, the phy has just reached the
586  * requested end time and 0 is returned
587  * Otherwise, we should disconnect (-1 will be returned)
588  */
pb_dev_pick_wait_resp(pb_dev_state_t * this)589 int pb_dev_pick_wait_resp(pb_dev_state_t *this) {
590   CHECK_CONNECTED(this->connected);
591 
592   pc_header_t header = PB_MSG_DISCONNECT;
593 
594   if (pb_dev_read(this, &header, sizeof(header)) == -1) {
595     return -1;
596   }
597 
598   if (header == PB_MSG_DISCONNECT) {
599     pb_dev_clean_up(this);
600     return -1;
601   } else if (header == PB_MSG_WAIT_END) {
602     return 0;
603   } else {
604     INVALID_RESP(header);
605     return -1;
606   }
607 }
608 
609 /**
610  * Request a wait to the phy and block until receiving the response
611  * If everything goes ok 0 is returned
612  *
613  * Otherwise, we should disconnect (-1 will be returned)
614  */
pb_dev_request_wait_block(pb_dev_state_t * this,pb_wait_t * wait_s)615 int pb_dev_request_wait_block(pb_dev_state_t *this, pb_wait_t *wait_s) {
616   CHECK_CONNECTED(this->connected);
617   int ret;
618   ret = pb_dev_request_wait_nonblock(this, wait_s);
619   if (ret)
620     return ret;
621   return pb_dev_pick_wait_resp(this);
622 }
623