I tried a new way of protecting my code through some binary flags.
I think binary flags are very handy in such situations because it is easy to check for conditions, for example the FAILURE flag below that tells if a failure occured or not is very easy to code.
Issue is it is easy to get tangled up with 1-digit difference flags.
# define EXIT_FLAG 0b10000000000000000000000000000000
# define FAILURE 0b00000000000000000001111111111111
# define FAILED_FORK 0b00000000000000000000000000000001
# define FAILED_WAITPID 0b00000000000000000000000000000010
# define FAILED_SEM_OPEN 0b00000000000000000000000000000100
# define FAILED_SEM_CLOSE 0b00000000000000000000000000001000
# define FAILED_SEM_POST 0b00000000000000000000000000010000
# define FAILED_SEM_WAIT 0b00000000000000000000000000100000
# define FAILED_SEM_UNLINK 0b00000000000000000000000001000000
# define FAILED_CREAT_TH 0b00000000000000000000000010000000
# define FAILED_JOIN_TH 0b00000000000000000000000100000000
# define FAILED_KILL 0b00000000000000000000001000000000
# define FAILED_GET_TIME 0b00000000000000000000010000000000
# define FAILED_USLEEP 0b00000000000000000000100000000000
# define FAILED_WRITE 0b00000000000000000001000000000000
# define USERGUIDE 0b00000000000000000010000000000000
# define USERGUIDE_MSG "usage:\n\t./philo {nb_philos} {die_tm} \
{eat_tm} {sleep_tm} (max_eat)\n \
\tinputs in ms is capped to 60,000 ms\n"
int ft_putstr_fd(char *s, int fd)
{
if (s)
{
while (*s)
{
if (write(fd, s , 1) == -1)
{
write(fd, "Failed write\n", 13);
return (0);
}
}
}
return (1);
}
int ft_putstr_error(char *s)
{
return (ft_putstr_fd(s, STDERR_FILENO));
}
void *ft_puterror(int flag, void *args)
{
if (flag & FAILED_FORK)
ft_putstr_error("Failed fork: ");
else if (flag & FAILED_WAITPID)
ft_putstr_error("Failed waitpid: ");
else if (flag & FAILED_SEM_OPEN)
ft_putstr_error("Failed sem_open: ");
else if (flag & FAILED_SEM_CLOSE)
ft_putstr_error("Failed sem_close: ");
else if (flag & FAILED_SEM_POST)
ft_putstr_error("Failed sem_post: ");
else if (flag & FAILED_SEM_WAIT)
ft_putstr_error("Failed sem_wait: ");
else if (flag & FAILED_SEM_UNLINK)
ft_putstr_error("Failed sem_unlink: ");
else if (flag & FAILED_CREAT_TH)
ft_putstr_error("Failed create thread: ");
else if (flag & FAILED_JOIN_TH)
ft_putstr_error("Failed joining thread: ");
else if (flag & FAILED_KILL)
ft_putstr_error("Failed kill: ");
else if (flag & FAILED_GET_TIME)
ft_putstr_error("Failed get_time: ");
else if (flag & FAILED_USLEEP)
ft_putstr_error("Failed usleep: ");
else if (flag & FAILED_WRITE)
ft_putstr_error("Failed write: ");
if (flag & FAILURE)
{
ft_putstr_error((char *)args);
ft_putstr_error("\n");
}
if (flag & USERGUIDE)
ft_putstr_error(USERGUIDE_MSG);
return (NULL);
}
Would you recommend to use this method to handle such errors or is there a nicer way, like a best practice ?
CodePudding user response:
It is much less error-prone if you use a sequence of numbers for the failure codes. Whenever you need a bitmask, simply shift one bit N times to the left, e.g.
//specify failure code type
typedef uint64_t failure_t;
//create a bitmask of the specified failure code
#define FAILURE_MASK(FCODE) (UINT64_C(1) << (FCODE))
//list of failure constants
#define NO_FAILURE UINT64_C(0) //add a no failure flag
#define FAILED_FORK UINT64_C(1)
#define FAILED_WAITPID UINT64_C(2)
#define FAILED_SEM_OPEN UINT64_C(3)
#define FAILED_SEM_CLOSE UINT64_C(4)
#define FAILED_SEM_POST UINT64_C(5)
#define FAILED_SEM_WAIT UINT64_C(6)
#define FAILED_SEM_UNLINK UINT64_C(7)
#define FAILED_CREAT_TH UINT64_C(8)
#define FAILED_JOIN_TH UINT64_C(9)
#define FAILED_KILL UINT64_C(10)
#define FAILED_GET_TIME UINT64_C(11)
#define FAILED_USLEEP UINT64_C(12)
#define FAILED_WRITE UINT64_C(13)
//... extend as you see fit
#define LAST_FAILURE FAILED_WRITE //useful for iteration
//list of failure strings
static const char *failure_strings[] = {
"No failure",
"Failed fork",
"...",
"Failed write"
};
//extern function to retrieve the failure string
const char* get_failure_string(failure_t failure)
{
return failure_strings[failure];
}
//test if the failure set contains the specified failure
#define is_failure_set(FSET, FCODE) FSET & FAILURE_MASK(FCODE)
//set the failure code in the failure set
#define set_failure(FSET, FCODE) FSET |= FAILURE_MASK(FCODE)
/**/
void *ft_puterror(failure_t failure_set, void *args)
{
if (failure_set == NO_FAILURE) //no failure set, nothing to do ...
return NULL; //unless no failure should be reported too
//for each failure that is set in failure_set
for (failure_t n=1; n <= LAST_FAILURE; n, failure_set >>= 1) {
if (failure_set & 1) { //failure n is set, report it
ft_putstr_error(get_failure_string(n));
ft_putstr_error((char *)args);
ft_putstr_error("\n");
}
}
//...
return NULL;
}
Adding a new failure code requires only 3 steps:
- add a new const, e.g.
#define FAILED_NEW 14
- set the constant
LAST_FAILURE
to the appropriate value, e.g.#define LAST_FAILURE FAILED_NEW
- add a new string to the
failure_strings
table, e.g."Failed new"