| From f850acdbe3bf2e5812f56d303add96ec3dc8f0a8 Mon Sep 17 00:00:00 2001 |
| From: Dumpeti Sathish Kumar <sdumpeti@codeaurora.org> |
| Date: Tue, 20 Apr 2021 19:38:20 +0530 |
| Subject: [PATCH] ODL-support-on-Open-Source-Diag-Router |
| |
| --- |
| tools/diag-log_on_device.c | 643 +++++++++++++++++++++++++++++++++++++++++++++ |
| 1 file changed, 643 insertions(+) |
| create mode 100644 tools/diag-log_on_device.c |
| |
| diff --git a/tools/diag-log_on_device.c b/tools/diag-log_on_device.c |
| new file mode 100644 |
| index 0000000..03a67f8 |
| --- /dev/null |
| +++ b/tools/diag-log_on_device.c |
| @@ -0,0 +1,643 @@ |
| +/* |
| + * Copyright (c) 2021 The Linux Foundation. All rights reserved. |
| + |
| + * Redistribution and use in source and binary forms, with or without |
| + * modification, are permitted provided that the following conditions are met: |
| + * Redistributions of source code must retain the above copyright |
| + notice, this list of conditions and the following disclaimer. |
| + * Redistributions in binary form must reproduce the above |
| + copyright notice, this list of conditions and the following |
| + disclaimer in the documentation and/or other materials provided |
| + with the distribution. |
| + * Neither the name of The Linux Foundation nor the names of its |
| + contributors may be used to endorse or promote products derived |
| + from this software without specific prior written permission. |
| + |
| + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| + */ |
| + |
| +#include <sys/socket.h> |
| +#include <sys/un.h> |
| +#include <netinet/in.h> |
| +#include <arpa/inet.h> |
| +#include <sys/time.h> |
| +#include <pthread.h> |
| +#include <err.h> |
| +#include <errno.h> |
| +#include <fcntl.h> |
| +#include <netdb.h> |
| +#include <stdio.h> |
| +#include <string.h> |
| +#include <stdlib.h> |
| +#include <unistd.h> |
| +#include <time.h> |
| +#include <pthread.h> |
| +#include <stdint.h> |
| +#include <signal.h> |
| +#include <sys/time.h> |
| +#include <sys/types.h> |
| +#include <sys/stat.h> |
| +#include <dirent.h> |
| +#include <sys/types.h> |
| +#include <sys/syscall.h> |
| +#include <sys/select.h> |
| +#include <ctype.h> |
| +#include <limits.h> |
| +#include <stdlib.h> |
| + |
| +#define FILE_LIST_NAME_SIZE 100 |
| +#define MAX_FILES_IN_FILE_LIST 100 |
| +#define std_strlprintf snprintf |
| +#define LOG_FILENAME_PREFIX_LEN 9 |
| +#define READ_BUF_SIZE 100000 |
| +#define DISK_BUF_SIZE 8192 |
| +#define CONTROL_CHAR 0x7E |
| +#define USER_SPACE_DATA_TYPE 0x00000020 |
| +#define USER_SPACE_RAW_DATA_TYPE 0x00000080 |
| +#define FILE_NAME_LEN 500 |
| +#define MASK_FILE_BUF_SIZE 8192 |
| + |
| +struct buffer_pool { |
| + unsigned int bytes_in_buff; |
| + unsigned char buffer_ptr[DISK_BUF_SIZE]; |
| +}; |
| + |
| +/*Static declaration of buffer. */ |
| +struct buffer_pool pools[] = { |
| + [0] = { |
| + .bytes_in_buff = 0, |
| + }, |
| + [1] = { |
| + .bytes_in_buff = 0, |
| + }, |
| + |
| +}; |
| + |
| +typedef uint32_t uint32; |
| +typedef uint8_t uint8; |
| + |
| +pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; |
| +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; |
| + |
| +/* Thread Handlers */ |
| +pthread_t read_thread_hdl; /* Diag Read thread handle */ |
| +pthread_t write_thread_hdl; /* Diag disk write thread handle */ |
| + |
| +/* Global Declarations */ |
| +int curr_read = 0; |
| +int curr_write = 0; |
| +unsigned char read_buffer[READ_BUF_SIZE]; |
| +char output_dir[FILE_NAME_LEN] = {"/tmp"}; |
| +char mask_clear_file[] = "/tmp/diag_mask.cfg"; |
| +int fd_dev = -1; // Socket Descriptor |
| +int fd_device = -1; //FIle Descriptor |
| +char timestamp_buf[30]; |
| +char file_name_curr[FILE_NAME_LEN]; |
| +static char *open_mask_file= NULL; |
| +static int diag_mask_file = 0; |
| +static int diag_timeout; |
| +int num_bytes_read; |
| +unsigned int max_file_num = 10; |
| +static unsigned int file_count = 0; |
| +int file_list_size; |
| +unsigned long max_file_size = 100000000; |
| +unsigned long min_file_size = 80000000; |
| +unsigned static long count_written_bytes; |
| +char* file_list = NULL; |
| +int file_list_index = -1; |
| +char file_name_del[FILE_NAME_LEN]; |
| +int overflow_flag = 0; |
| +int cleanup_mask; |
| + |
| + |
| +/* Function to get timestamp */ |
| +void get_time_string(char *buffer, int len) |
| +{ |
| + struct timeval tv; |
| + struct tm *tm; |
| + unsigned long long milliseconds = 0; |
| + char timestamp_buf[30]; |
| + |
| + if (!buffer || len <= 0) |
| + return; |
| + |
| + gettimeofday(&tv, NULL); |
| + tm = localtime(&tv.tv_sec); |
| + if (!tm) |
| + return; |
| + |
| + milliseconds = (tv.tv_sec * 1000LL) + (tv.tv_usec / 1000); |
| + strftime(timestamp_buf, 30, "%Y%m%d_%H%M%S", tm); |
| + (void)snprintf(buffer, len, "%s%lld", |
| + timestamp_buf, milliseconds); |
| +} |
| + |
| +static void usage(void) |
| +{ |
| + fprintf(stderr, |
| + "User space application for diag interface\n" |
| + "\n" |
| + "usage: diag [-of]\n" |
| + "\n" |
| + "options:\n" |
| + "-o <output directory>\n" |
| + "-f <Diag Configuration file>\n" |
| + "-t <Log Timeout>\n" |
| + "-n <number of log files>\n" |
| + "-s <Size in MB>\n" |
| + "-c <Cleanup Modem Mask>\n" |
| + ); |
| + exit(1); |
| +} |
| + |
| + |
| +int send_empty_mask(int type) |
| +{ |
| + int fd_mask, ch, ret; |
| + const uint8 size = 40; |
| + unsigned char mask_buf[size]; |
| + int count_mask_bytes = 4; |
| + *(int *)mask_buf = USER_SPACE_RAW_DATA_TYPE; |
| + FILE *mask_fp = NULL; |
| + |
| + if ((mask_fp = fopen(mask_clear_file, "rb")) == NULL) { |
| + printf("can't open mask clear file: %s, errno: %d\n", mask_clear_file,errno); |
| + return -1; |
| + } |
| + while(1) |
| + { |
| + ch = fgetc(mask_fp); |
| + if(ch == EOF) |
| + break; |
| + |
| + mask_buf[count_mask_bytes] = ch; |
| + if (mask_buf[count_mask_bytes] == CONTROL_CHAR) { |
| + ret = write(fd_dev, mask_buf, count_mask_bytes-1); |
| + if(ret < 0) { |
| + printf("Write Error on the Socket: errno = %d\n", errno); |
| + return -1; |
| + } |
| + *(int *)mask_buf = USER_SPACE_DATA_TYPE; |
| + count_mask_bytes = 4; |
| + } else { |
| + count_mask_bytes++; |
| + } |
| + } |
| + fclose(mask_fp); |
| + |
| + return 0; |
| +} |
| + |
| + |
| +static void log_timeout(int sig, siginfo_t *siginfo, void *context) |
| +{ |
| + int ret; |
| + printf ("Sending PID: %ld, UID: %ld\n", |
| + (long)siginfo->si_pid, (long)siginfo->si_uid); |
| + ret = send_empty_mask(0); |
| + if(ret < 0) |
| + printf("Couldn't open Mask clear file.\n"); |
| + |
| + close(fd_device); |
| + close(fd_dev); |
| + pthread_cancel(read_thread_hdl); |
| + pthread_cancel(write_thread_hdl); |
| +} |
| +int close_logging_file() |
| +{ |
| + int status; |
| + char timestamp_buf[30]; |
| + char new_filename[FILE_NAME_LEN]; |
| + |
| + close(fd_device); |
| + /* check whether number of files in /tmp. Delete if we exceed the limit */ |
| + if(file_count > max_file_num) { |
| + printf("Delete Oldest File.\n"); |
| + if (!delete_log()) { |
| + file_count--; |
| + } |
| + } |
| + get_time_string(timestamp_buf, sizeof(timestamp_buf)); |
| + snprintf(new_filename, |
| + FILE_NAME_LEN, "%s%s%s%s", |
| + output_dir,"/diag_log_", |
| + timestamp_buf, ".qmdl"); |
| + |
| + fd_device = open(new_filename, |
| + O_CREAT | O_RDWR | O_SYNC | O_TRUNC, |
| + 0644); |
| + if(fd_device < 0) { |
| + printf(" File open error: %d\n", errno); |
| + return -1; |
| + } |
| + else { |
| + file_count++; |
| + } |
| + strncpy(file_name_curr, new_filename, FILE_NAME_LEN); |
| + return 0; |
| +} |
| + |
| +static void *WriteThread(void *data) |
| +{ |
| + unsigned int chunks, last_chunk; |
| + struct stat finfo; |
| + int ret; |
| + while(1) { |
| + pthread_mutex_lock(&lock); |
| + pthread_cond_wait(&cond1, &lock); |
| + ret = write(fd_device,pools[curr_write].buffer_ptr,DISK_BUF_SIZE); |
| + if(ret > 0) { |
| + pools[curr_write].bytes_in_buff = 0; |
| + } else if (ret < 0) { |
| + goto fail; |
| + } |
| + /* Check whether we reached to MAX size of the File */ |
| + if(fstat(fd_device, &finfo) == 0) { |
| + if(finfo.st_size > min_file_size) { |
| + ret = close_logging_file(); |
| + if(ret < 0) |
| + goto fail; |
| + count_written_bytes = 0; |
| + } |
| + } |
| + else { |
| + goto fail; |
| + } |
| + pthread_mutex_unlock(&lock); |
| + curr_write =! curr_write; |
| + } |
| +fail: |
| + pthread_cancel(read_thread_hdl); |
| + close(fd_dev); |
| + close(fd_device); |
| +} |
| +/* Read Thread */ |
| +static void *CreateWaitThread(void* data) |
| +{ |
| + int read_bytes = 0,type,num_data_fields; |
| + uint32 count_received_bytes; |
| + unsigned char* ptr; |
| + |
| + while(1) { |
| + num_bytes_read = 0; |
| + memset(read_buffer, 0, READ_BUF_SIZE); |
| + num_bytes_read = read(fd_dev, (void*)read_buffer,READ_BUF_SIZE); |
| + if(!num_bytes_read || (num_bytes_read < 0)) |
| + goto read_failure; |
| + |
| + type = *(int *)read_buffer; |
| + ptr = read_buffer+4; |
| + num_data_fields = *(int *)ptr; |
| + ptr += 4; |
| + count_received_bytes = *(uint32*)ptr; |
| + ptr += sizeof(uint32); |
| + count_written_bytes += num_bytes_read; |
| + if(count_received_bytes >= (DISK_BUF_SIZE - pools[curr_read].bytes_in_buff)) { |
| + /* Trigger Write Thread */ |
| + curr_read =! curr_read; |
| + pthread_cond_signal(&cond1); |
| + } |
| + |
| + if(count_received_bytes > 0) {/* Buffer space is available */ |
| + memcpy(pools[curr_read].buffer_ptr + pools[curr_read].bytes_in_buff, ptr, count_received_bytes); |
| + pools[curr_read].bytes_in_buff += count_received_bytes; |
| + } |
| + } |
| + |
| +read_failure: |
| + close(fd_dev); |
| + close(fd_device); |
| + pthread_cancel(write_thread_hdl); |
| +} |
| + |
| +static int create_oldest_file_list(char *oldest_dir) |
| +{ |
| + |
| + int status = 1; |
| + struct dirent **dirent_list; |
| + int i,n,type=0; |
| + int num_entries = 0; |
| + int num_entries_capped = 0; |
| + char *name_ptr; |
| + int num_bytes = 0; |
| + |
| + num_entries = scandir(oldest_dir, &dirent_list, 0,(int(*)(const struct dirent **, const struct dirent **))alphasort); |
| + if(!dirent_list) { |
| + printf("In %s, couldn't get the dirent_list, errno: %d, directory: %s\n", |
| + __func__, errno, oldest_dir); |
| + return 0; |
| + } else if (num_entries < 0) { |
| + printf("In %s, error determining directory entries, errno: %d, directory: %s\n", |
| + __func__, errno, oldest_dir); |
| + return 0; |
| + } |
| + |
| + /* Limit the size of the list so we aren't working with too many files */ |
| + num_entries_capped = (num_entries > MAX_FILES_IN_FILE_LIST) ? |
| + MAX_FILES_IN_FILE_LIST : num_entries; |
| + |
| + if (num_entries_capped - 2 > 0) { |
| + file_list_size = num_entries_capped - 2; |
| + num_bytes = FILE_LIST_NAME_SIZE * file_list_size; |
| + file_list = malloc(num_bytes); |
| + } |
| + |
| + if (file_list) { |
| + file_list_index = 0; |
| + for (i = 0; i < num_entries_capped; i++) |
| + { |
| + if ((strncmp(dirent_list[i]->d_name, "diag_log_",LOG_FILENAME_PREFIX_LEN) != 0)) |
| + continue; |
| + if (file_list_index < file_list_size) |
| + { |
| + name_ptr = file_list + |
| + (file_list_index * FILE_LIST_NAME_SIZE); |
| + strncpy(name_ptr, dirent_list[i]->d_name, FILE_LIST_NAME_SIZE); |
| + *(name_ptr + (FILE_LIST_NAME_SIZE - 1)) = 0; |
| + file_list_index++; |
| + } |
| + if (file_list_index > 0) { |
| + if (file_list_index < file_list_size) { |
| + int new_size = FILE_LIST_NAME_SIZE *file_list_index; |
| + char *temp_ptr = realloc(file_list, new_size); |
| + if (temp_ptr) |
| + file_list = temp_ptr; |
| + } |
| + file_list_size = file_list_index; |
| + } |
| + } |
| + } |
| + else if (num_bytes > 0) { |
| + printf("Memory Allocation error.\n"); |
| + status = 0; |
| + } |
| + |
| + i = num_entries; |
| + while (i--) { |
| + free(dirent_list[i]); |
| + } |
| + |
| + free(dirent_list); |
| + return status; |
| +} |
| + |
| +static int get_oldest_file(char* oldest_file, char *oldest_dir) |
| +{ |
| + int status = 0; |
| + status = create_oldest_file_list(oldest_dir); |
| + |
| + if (file_list) { |
| + if (oldest_file) { |
| + strncpy(oldest_file, file_list, |
| + FILE_LIST_NAME_SIZE); |
| + file_list_index++; |
| + status = 1; |
| + } else { |
| + printf("In %s, oldest_file is NULL\n", __func__); |
| + } |
| + |
| + }else { |
| + status =0; |
| + printf("No Log files in the dicrectory.\n"); |
| + } |
| + |
| + return status; |
| +} |
| +/* Number of Log file in /tmp directory */ |
| +static int get_file_count(char *oldest_dir) |
| +{ |
| + struct dirent **dirent_list; |
| + int i,num_entries = 0; |
| + int num_entries_capped = 0; |
| + |
| + num_entries = scandir(oldest_dir, &dirent_list, 0,(int(*)(const struct dirent **, const struct dirent **))alphasort); |
| + if(!dirent_list) { |
| + printf("In %s, couldn't get the dirent_list, errno: %d, directory: %s\n", |
| + __func__, errno, oldest_dir); |
| + return 0; |
| + } else if (num_entries < 0) { |
| + printf("In %s, error determining directory entries, errno: %d, directory: %s\n", |
| + __func__, errno, oldest_dir); |
| + return 0; |
| + } |
| + |
| + for (i = 0; i < num_entries; i++) { |
| + if ((strncmp(dirent_list[i]->d_name, "diag_log_",LOG_FILENAME_PREFIX_LEN) != 0)) |
| + continue; |
| + file_count++; |
| + } |
| + return file_count; |
| +} |
| + |
| +int delete_log() |
| +{ |
| + int status; |
| + char oldest_file[FILE_LIST_NAME_SIZE] = ""; |
| + struct stat file_stat; |
| + |
| + status = get_oldest_file(oldest_file, |
| + output_dir); |
| + if (0 == status) { |
| + printf("diag: In %s, Unable to determine oldest file for deletion\n", |
| + __func__); |
| + return -1; |
| + } |
| + std_strlprintf(file_name_del, |
| + FILE_NAME_LEN, "%s%s%s", |
| + output_dir, "/", oldest_file); |
| + if (!strncmp(file_name_curr, file_name_del, FILE_NAME_LEN)) { |
| + printf("diag: In %s, Cannot delete file, file %s is in use \n", |
| + __func__, file_name_curr); |
| + return -1; |
| + } |
| + stat(file_name_del, &file_stat); |
| + /* Convert size to KB */ |
| + file_stat.st_size /= 1024; |
| + if (unlink(file_name_del)) { |
| + printf("In %s, Unable to delete file: %s, errno: %d\n", |
| + __func__, file_name_del, errno); |
| + return -1; |
| + } else { |
| + printf("In %s, Deleting logfile %s of size %lld KB\n", |
| + __func__, file_name_del, |
| + (long long int) file_stat.st_size); |
| + free(file_list); |
| + } |
| + return 0; |
| +} |
| + |
| + |
| +int main(int argc, char **argv) |
| +{ |
| + int ret; |
| + int c,ch,found_cmd; |
| + struct sockaddr_un addr; |
| + struct timeval tv = {20, 0}; |
| + struct sigaction act; |
| + int count_mask_bytes = 0; |
| + unsigned char mask_buf[MASK_FILE_BUF_SIZE]; |
| + FILE *read_mask_fp; |
| + *(int *)mask_buf = USER_SPACE_DATA_TYPE; |
| + |
| + count_mask_bytes = 4; |
| + memset (&act, '\0', sizeof(act)); |
| + act.sa_sigaction = &log_timeout; |
| + act.sa_flags = SA_SIGINFO; |
| + if (sigaction(SIGALRM, &act, NULL) < 0) { |
| + perror ("sigaction"); |
| + return 1; |
| + } |
| + |
| + for (;;) { |
| + c = getopt(argc, argv, "hf:o:t:s"); |
| + if (c < 0) |
| + break; |
| + |
| + switch (c) { |
| + case 'c': |
| + cleanup_mask = 1; |
| + break; |
| + |
| + case 's': |
| + max_file_size = atol(optarg); |
| + if ((long)max_file_size <= 0) |
| + max_file_size = 100000000; |
| + else { |
| + max_file_size *= 1024 * 1024; |
| + if (max_file_size >= 0 && max_file_size < 1024 * 1024) |
| + max_file_size = 100000000; |
| + } |
| + min_file_size = ((max_file_size / 100) * 80); |
| + break; |
| + |
| + case 'o': |
| + file_count = get_file_count(output_dir); |
| + printf("QMDL File Count in /tmp Directory = %d\n", file_count); |
| + |
| + if(max_file_num > 1 && (file_count >= max_file_num)) { /* Check file_count before creating the Log File. */ |
| + printf("In %s, File count reached max file num %u so deleting oldest file\n",__func__, max_file_num); |
| + |
| + while(file_count > max_file_num) |
| + if (!delete_log()) { |
| + file_count--; |
| + } |
| + } |
| + |
| + get_time_string(timestamp_buf, sizeof(timestamp_buf)); |
| + snprintf(file_name_curr, |
| + FILE_NAME_LEN, "%s%s%s%s", |
| + output_dir,"/diag_log_", |
| + timestamp_buf, ".qmdl"); |
| + fd_device = open(file_name_curr, |
| + O_CREAT | O_RDWR | O_SYNC | O_TRUNC, |
| + 0644); |
| + if(fd_device < 0) { |
| + printf(" File open error: %d\n", errno); |
| + return -1; |
| + } |
| + else { |
| + file_count++; |
| + } |
| + break; |
| + |
| + case 'f': |
| + open_mask_file = strdup(optarg); |
| + diag_mask_file = 1; |
| + break; |
| + case 't': |
| + printf("Timeout for QMDL Logging\n"); |
| + diag_timeout = atoi(optarg); |
| + printf("Time out value = %d\n", diag_timeout); |
| + alarm(diag_timeout); |
| + break; |
| + default: |
| + case 'h': |
| + usage(); |
| + break; |
| + } |
| + } |
| + |
| + fd_dev = socket(AF_UNIX, SOCK_SEQPACKET, 0); |
| + if (fd_dev < 0) |
| + goto failure_case3; |
| + |
| + memset(&addr, 0, sizeof(addr)); |
| + addr.sun_family = AF_UNIX; |
| + strncpy(addr.sun_path, "\0diag", sizeof(addr.sun_path)-1); |
| + |
| + ret = connect(fd_dev, (struct sockaddr*)&addr, sizeof(addr)); |
| + if (ret < 0) |
| + goto failure_case2; |
| + |
| + pthread_create(&read_thread_hdl, NULL, CreateWaitThread, NULL); |
| + if(read_thread_hdl == 0) { |
| + printf("Failed to create Read Thread.\n"); |
| + goto failure_case2; |
| + } |
| + pthread_create(&write_thread_hdl, NULL, WriteThread, NULL); |
| + if(write_thread_hdl == 0) { |
| + printf("Failed to create Write Thread.\n"); |
| + if(read_thread_hdl == 0) |
| + pthread_cancel(read_thread_hdl); |
| + goto failure_case2; |
| + } |
| + |
| + if ((read_mask_fp = fopen(open_mask_file, "rb")) == NULL) { |
| + printf("can't open mask file: %s, errno: %d\n", open_mask_file,errno); |
| + goto failure_case1; |
| + } |
| + |
| + if(diag_mask_file){ |
| + while(1){ |
| + ch = fgetc(read_mask_fp); |
| + if (ch == EOF) |
| + break; |
| + mask_buf[count_mask_bytes] = ch; |
| + if (mask_buf[count_mask_bytes] == CONTROL_CHAR) { |
| + |
| + if (!found_cmd) |
| + found_cmd = 1; |
| + ret = write(fd_dev, mask_buf, count_mask_bytes+1); |
| + if(ret < 0) { |
| + printf("Write Error on the Socket: errno = %d\n", errno); |
| + goto failure_case0; |
| + } |
| + |
| + *(int *)mask_buf = USER_SPACE_DATA_TYPE; |
| + count_mask_bytes = 4; |
| + } else { |
| + count_mask_bytes++; |
| + } |
| + } |
| + if(!found_cmd){ |
| + printf("No command found:\n"); |
| + } |
| + } |
| + while(1) |
| + sleep(3600); |
| + |
| +failure_case0: |
| + fclose(read_mask_fp); |
| + |
| +failure_case1: |
| + pthread_cancel(read_thread_hdl); |
| + pthread_cancel(write_thread_hdl); |
| + |
| +failure_case2: |
| + close(fd_dev); |
| + |
| +failure_case3: |
| + close(fd_device); |
| + |
| + return 0; |
| +} |
| -- |
| 2.7.4 |
| |