add gpsd program and booster
 
	
	
	
	
	
	
	
	
	
	
	
	
	
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #include "logger.h" | 
 |  |  | #include "at-esp32.h" | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|    Baisc AT command    | | 
 |  |  |  *+------------------------+*/ | 
 |  |  |  | 
 |  |  | static inline int check_at_ready(comport_t *comport) | 
 |  |  | { | 
 |  |  |     int             times = 6; | 
 |  |  |     int             ready = 0; | 
 |  |  |  | 
 |  |  |     while( times-- ) | 
 |  |  |     { | 
 |  |  |         if( 0 == send_atcmd_check_ok(comport, "AT", 500) ) | 
 |  |  |         { | 
 |  |  |             ready = 1; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return ready; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+RST */ | 
 |  |  | int esp32_reset(comport_t *comport) | 
 |  |  | { | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     rv = send_atcmd(comport, "AT+RST", 5000, "ready", AT_ERRSTR, NULL, 0); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("send AT command to reset ESP32 failed, rv=%d\n", rv); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( check_at_ready(comport) ) | 
 |  |  |     { | 
 |  |  |         log_info("send AT to reset ESP32 and AT command ready\n"); | 
 |  |  |         return 0; | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         log_info("send AT to reset ESP32 but AT command not ready\n"); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+RESTORE */ | 
 |  |  | int esp32_restore(comport_t *comport) | 
 |  |  | { | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     //rv = send_atcmd_check_ok(comport, "AT+RESTORE", 5000); | 
 |  |  |     rv = send_atcmd(comport, "AT+RESTORE", 5000, "ready", AT_ERRSTR, NULL, 0); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("send AT command to restore ESP32 failed, rv=%d\n", rv); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( check_at_ready(comport) ) | 
 |  |  |     { | 
 |  |  |         log_info("send AT command to resstore ESP32 ready\n"); | 
 |  |  |         return 0; | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         log_error("send AT command to restore ESP32 but AT not ready\n"); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: ATE1 or ATE0 */ | 
 |  |  | int esp32_set_echo(comport_t *comport, int enable) | 
 |  |  | { | 
 |  |  |     char           *at = enable? "ATE1" : "ATE0"; | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+GMR */ | 
 |  |  | int esp32_get_firmware(comport_t *comport, char *version, int size) | 
 |  |  | { | 
 |  |  |     if( !version || size<=0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_value(comport, "AT+GMR", 2000, version, size); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+SYSSTORE */ | 
 |  |  | int esp32_set_sysstore(comport_t *comport, int enable) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+SYSSTORE=%d", enable?1:0); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1000); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|    WiFi AT command     | | 
 |  |  |  *+------------------------+*/ | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWMODE */ | 
 |  |  | int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CWMODE=%d,%d", mode, autoconn?1:0); | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTAMAC/CIPAPMAC */ | 
 |  |  | int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac) | 
 |  |  | { | 
 |  |  |     if( !mac ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     if( MODE_SOFTAP == mode ) | 
 |  |  |         return send_atcmd_check_request(comport, "AT+CIPAPMAC?", 3000, mac, MAC_LEN); | 
 |  |  |     else | 
 |  |  |         return send_atcmd_check_request(comport, "AT+CIPSTAMAC?", 3000, mac, MAC_LEN); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTA/AT+CIPAP  */ | 
 |  |  | int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( !ip || !gateway ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     if( MODE_SOFTAP == mode ) | 
 |  |  |         snprintf(at, sizeof(at), "AT+CIPAP=\"%s\",\"%s\"", ip, gateway); | 
 |  |  |     else | 
 |  |  |         snprintf(at, sizeof(at), "AT+CIPSTA=\"%s\",\"%s\"", ip, gateway); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 2000); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTA/AT+CIPAP  */ | 
 |  |  | int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway) | 
 |  |  | { | 
 |  |  |     char           *at = MODE_SOFTAP==mode? "AT+CIPAP?" : "AT+CIPSTA?"; | 
 |  |  |     char            buf[ATCMD_REPLY_LEN]; | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     if( !ip || !gateway ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     rv = send_atcmd_check_value(comport, at, 3000, buf, sizeof(buf)); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("AT command query IP address failed, rv=%d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv = parser_request_value(buf, "ip", ip, IP_LEN); | 
 |  |  |     if( rv < 0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Parser query IP address failed, rv=%d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv = parser_request_value(buf, "gateway", gateway, IP_LEN); | 
 |  |  |     if( rv < 0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Parser query gateway address failed, rv=%d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWDHCP */ | 
 |  |  | int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CWDHCP=%d,%d", enable?1:0, mode); | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWAUTOCONN */ | 
 |  |  | int esp32_set_autoconn(comport_t *comport, int enable) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CWAUTOCONN=%d", enable?1:0); | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWLAP */ | 
 |  |  | int esp32_list_ap(comport_t *comport, char *buf, int size) | 
 |  |  | { | 
 |  |  |     if( !buf || size<=0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_value(comport, "AT+CWLAP", 8000, buf, size); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWJAP */ | 
 |  |  | int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( !ssid || !pwd ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CWJAP=\"%s\",\"%s\"", ssid, pwd); | 
 |  |  |     return send_atcmd_check_ok(comport, at, 15000); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWQAP */ | 
 |  |  | int esp32_disconn_ap(comport_t *comport) | 
 |  |  | { | 
 |  |  |     return send_atcmd_check_ok(comport, "AT+CWQAP", 5000); | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWSTATE?  status value: | 
 |  |  |  * - 0: ESP32 station has not started any Wi-Fi connection. | 
 |  |  |  * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet. | 
 |  |  |  * - 2: ESP32 station has connected to an AP, and got an IPv4 address. | 
 |  |  |  * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state. | 
 |  |  |  * - 4: ESP32 station is in Wi-Fi disconnected state | 
 |  |  |  */ | 
 |  |  | int esp32_join_status(comport_t *comport, int *status, char *ssid) | 
 |  |  | { | 
 |  |  |     char            buf[ATCMD_REPLY_LEN]; | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     if( !status || !ssid ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     rv = send_atcmd_check_request(comport, "AT+CWSTATE?", 3000, buf, sizeof(buf)); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("AT command query join status failed, rv=%d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     sscanf(buf, "%d,%s", status, ssid); | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command:  AT+PING */ | 
 |  |  | int esp32_ping(comport_t *comport, char *host, int timeout) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( !host ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+PING=\"%s\"", host); | 
 |  |  |     return send_atcmd_check_ok(comport, at, timeout); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command:  AT+CWSAP */ | 
 |  |  | int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( !ssid || !pwd ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CWSAP=\"%s\",\"%s\",%d,%d", ssid, pwd, channel, encmode); | 
 |  |  |     return send_atcmd_check_ok(comport, at, 5000); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWLIF */ | 
 |  |  | int esp32_list_client(comport_t *comport, char *buf, int size) | 
 |  |  | { | 
 |  |  |     if( !buf || size<=0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_value(comport, "AT+CWLIF", 8000, buf, size); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|   Socket AT command    | | 
 |  |  |  *+------------------------+*/ | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPMUX */ | 
 |  |  | int esp32_set_socket_mux(comport_t *comport, int enable) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CIPMUX=%d", enable?1:0); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVERMAXCONN */ | 
 |  |  | int esp32_set_socket_clients(comport_t *comport, int max) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( max <= 0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CIPSERVERMAXCONN=%d", max); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTO, timeout unit second */ | 
 |  |  | int esp32_set_socket_timeout(comport_t *comport, int timeout) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( timeout <= 0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CIPSTO=%d", timeout); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVER */ | 
 |  |  | int esp32_set_tcp_server(comport_t *comport, int port) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( port <= 0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CIPSERVER=1,%d,\"TCP\"", port); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVER */ | 
 |  |  | int esp32_del_tcp_server(comport_t *comport, int port) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |  | 
 |  |  |     if( port <= 0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     snprintf(at, sizeof(at), "AT+CIPSERVER=0,%d,\"TCP\"", port); | 
 |  |  |  | 
 |  |  |     return send_atcmd_check_ok(comport, at, 1500); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTART */ | 
 |  |  | int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port) | 
 |  |  | { | 
 |  |  |     char            at[ATCMD_LEN]={'\0'}; | 
 |  |  |     char            buf[ATCMD_REPLY_LEN]={'\0'}; | 
 |  |  |     int             keepalive = 60; /* unit second */ | 
 |  |  |     int             rv,linkid = 0; | 
 |  |  |  | 
 |  |  |     if( !host || port <= 0 ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_socket_mux(comport, mux); | 
 |  |  |     if(rv < 0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     if( mux ) | 
 |  |  |         snprintf(at, sizeof(at), "AT+CIPSTART=1,\"TCP\",\"%s\",%d,%d", host, port, keepalive); | 
 |  |  |     else | 
 |  |  |         snprintf(at, sizeof(at), "AT+CIPSTART=\"TCP\",\"%s\",%d,%d", host, port, keepalive); | 
 |  |  |  | 
 |  |  |     rv = send_atcmd_check_value(comport, at, 1500, buf, sizeof(buf)); | 
 |  |  |     if(rv < 0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     sscanf(buf, "%d,", &linkid); | 
 |  |  |  | 
 |  |  |     return linkid; | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #ifndef  _AT_ESP32_H_ | 
 |  |  | #define  _AT_ESP32_H_ | 
 |  |  |  | 
 |  |  | #include "atcmd.h" | 
 |  |  |  | 
 |  |  | enum | 
 |  |  | { | 
 |  |  |     DISABLE = 0,    /* common disable */ | 
 |  |  |     ENABLE,         /* common enable  */ | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | #define MAC_LEN     18 /* 11:22:33:44:55:66 */ | 
 |  |  | #define IP_LEN      16 /* 255.255.255.255 */ | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|    Baisc AT command    | | 
 |  |  |  *+------------------------+*/ | 
 |  |  |  | 
 |  |  | /* AT command: AT+RST */ | 
 |  |  | extern int esp32_reset(comport_t *comport); | 
 |  |  |  | 
 |  |  | /* AT command: AT+RESTORE */ | 
 |  |  | extern int esp32_restore(comport_t *comport); | 
 |  |  |  | 
 |  |  | /* AT command: ATE1 or ATE0 */ | 
 |  |  | extern int esp32_set_echo(comport_t *comport, int enable); | 
 |  |  |  | 
 |  |  | /* AT command: AT+GMR */ | 
 |  |  | extern int esp32_get_firmware(comport_t *comport, char *version, int size); | 
 |  |  |  | 
 |  |  | /* AT command: AT+SYSSTORE */ | 
 |  |  | extern int esp32_set_sysstore(comport_t *comport, int enable); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|    WiFi AT command     | | 
 |  |  |  *+------------------------+*/ | 
 |  |  | typedef enum | 
 |  |  | { | 
 |  |  |     MODE_DISABLE=0, /* Wi-Fi RF will be disabled */ | 
 |  |  |     MODE_STATION,   /* Station mode */ | 
 |  |  |     MODE_SOFTAP,    /* SoftAP mode */ | 
 |  |  |     MODE_WDS,       /* Wireless Distribution System: Both Station & SoftAP mode */ | 
 |  |  | } workmode_t; | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWMODE */ | 
 |  |  | extern int esp32_set_wmode(comport_t *comport, workmode_t mode, int autoconn); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWDHCP */ | 
 |  |  | extern int esp32_set_dhcp(comport_t *comport, workmode_t mode, int enable); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWAUTOCONN */ | 
 |  |  | extern int esp32_set_autoconn(comport_t *comport, int enable); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTAMAC/CIPAPMAC */ | 
 |  |  | extern int esp32_get_macaddr(comport_t *comport, workmode_t mode, char *mac); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTA/CIPAP */ | 
 |  |  | extern int esp32_get_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTA/AT+CIPAP  */ | 
 |  |  | extern int esp32_set_ipaddr(comport_t *comport, workmode_t mode, char *ip, char *gateway); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWLAP */ | 
 |  |  | extern int esp32_list_ap(comport_t *comport, char *buf, int size); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWJAP */ | 
 |  |  | extern int esp32_connect_ap(comport_t *comport, char *ssid, char *pwd); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWQAP */ | 
 |  |  | extern int esp32_disconn_ap(comport_t *comport); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CWSTATE? , status value: | 
 |  |  |  * - 0: ESP32 station has not started any Wi-Fi connection. | 
 |  |  |  * - 1: ESP32 station has connected to an AP, but does not get an IPv4 address yet. | 
 |  |  |  * - 2: ESP32 station has connected to an AP, and got an IPv4 address. | 
 |  |  |  * - 3: ESP32 station is in Wi-Fi connecting or reconnecting state. | 
 |  |  |  * - 4: ESP32 station is in Wi-Fi disconnected state | 
 |  |  |  */ | 
 |  |  | extern int esp32_join_status(comport_t *comport, int *status, char *ssid); | 
 |  |  |  | 
 |  |  | /* AT command:  AT+PING */ | 
 |  |  | extern int esp32_ping(comport_t *comport, char *host, int timeout); | 
 |  |  |  | 
 |  |  | /* AT command:  AT+CWSAP */ | 
 |  |  | typedef enum | 
 |  |  | { | 
 |  |  |     MODE_OPEN, | 
 |  |  |     /* WEP not support */ | 
 |  |  |     MODE_WPAPSK = 2, | 
 |  |  |     MODE_WPA2PSK, | 
 |  |  |     MODE_WPA_WPA2PSK, | 
 |  |  | } encmode_t; | 
 |  |  | extern int esp32_set_softap(comport_t *comport, char *ssid, char *pwd, int channel, encmode_t encmode); | 
 |  |  |  | 
 |  |  | /* AT command:  AT+CWLIF */ | 
 |  |  | extern int esp32_list_client(comport_t *comport, char *buf, int size); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|   Socket AT command    | | 
 |  |  |  *+------------------------+*/ | 
 |  |  |  | 
 |  |  | /* AT command: CIPMUX */ | 
 |  |  | extern int esp32_set_socket_mux(comport_t *comport, int enable); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVERMAXCONN */ | 
 |  |  | extern int esp32_set_socket_clients(comport_t *comport, int max); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTO, timeout unit second */ | 
 |  |  | extern int esp32_set_socket_timeout(comport_t *comport, int timeout); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVER */ | 
 |  |  | extern int esp32_set_tcp_server(comport_t *comport, int port); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSERVER */ | 
 |  |  | extern int esp32_del_tcp_server(comport_t *comport, int port); | 
 |  |  |  | 
 |  |  | /* AT command: AT+CIPSTART */ | 
 |  |  | extern int esp32_set_tcp_client(comport_t *comport, int mux, char *host, int port); | 
 |  |  |  | 
 |  |  | /*+------------------------+ | 
 |  |  |  *|     BLE AT command     | | 
 |  |  |  *+------------------------+*/ | 
 |  |  | // RFU | 
 |  |  |  | 
 |  |  | #endif   /* ----- #ifndef _AT_ESP32_H_  ----- */ | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2023 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #include <ctype.h> | 
 |  |  |  | 
 |  |  | #include "logger.h" | 
 |  |  | #include "atcmd.h" | 
 |  |  |  | 
 |  |  | /*  Description: this function used to send an AT command from serial port and wait for reply message from | 
 |  |  |  *               the communication module, and it will return once get expet/error string or timeout. | 
 |  |  |  * | 
 |  |  |  *    Arugments: | 
 |  |  |  *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa... | 
 |  |  |  *              $at: the AT command need to be sent, without "\r\n" | 
 |  |  |  *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms) | 
 |  |  |  *          $exepct: the expect string reply from communication module | 
 |  |  |  *           $error: the error string reply from communication module | 
 |  |  |  *           $reply: the module reply message output buffer | 
 |  |  |  *            $size: the output buffer ($reply) size | 
 |  |  |  * | 
 |  |  |  * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout | 
 |  |  |  * | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size) | 
 |  |  | { | 
 |  |  |     int                    i, rv = 0; | 
 |  |  |     int                    res = ATRES_TIMEOUT; | 
 |  |  |     int                    bytes = 0; | 
 |  |  |     char                   buf[ATCMD_REPLY_LEN] = {'\0'}; | 
 |  |  |     char                   atcmd[ATCMD_LEN] = {'\0'}; | 
 |  |  |  | 
 |  |  |     if( !comport || !at ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( comport->fd <= 0 ) | 
 |  |  |     { | 
 |  |  |         log_error("comport[%s] not opened\n"); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* flushes both data received but not read, and data written but not transmitted in serial port */ | 
 |  |  |     tcflush(comport->fd, TCIOFLUSH); | 
 |  |  |  | 
 |  |  |     snprintf(atcmd, sizeof(atcmd), "%s%s", at, AT_SUFFIX); | 
 |  |  |     rv=comport_send( comport, atcmd, strlen(atcmd) ); | 
 |  |  |     if(rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("send AT command \"%s\" to \"%s\" failed, rv=%d\n", at, comport->devname, rv); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     res = ATRES_TIMEOUT; | 
 |  |  |     memset( buf, 0, sizeof(buf) ); | 
 |  |  |  | 
 |  |  |     for(i=0; i<timeout/10; i++) | 
 |  |  |     { | 
 |  |  |         if( bytes >= sizeof(buf) ) | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         rv=comport_recv( comport, buf+bytes, sizeof(buf)-bytes, 10); | 
 |  |  |         if(rv < 0) | 
 |  |  |         { | 
 |  |  |             log_error("send AT command \'%s\' to \'%s\' failed, rv=%d\n", at, comport->devname, rv); | 
 |  |  |             return -3; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         bytes += rv; | 
 |  |  |  | 
 |  |  |         if( expect && strstr(buf, expect) ) | 
 |  |  |         { | 
 |  |  |             log_debug("send AT command \"%s\" and got reply \"OK\"\n", at); | 
 |  |  |             res = ATRES_EXPECT; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         if( error && strstr(buf, error) ) | 
 |  |  |         { | 
 |  |  |             log_debug("send AT command \"%s\" and got reply \"ERROR\"\n", at); | 
 |  |  |             res = ATRES_ERROR; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( bytes > 0 ) | 
 |  |  |         log_trace("AT command reply:%s", buf); | 
 |  |  |  | 
 |  |  |     if( reply && size>0 ) | 
 |  |  |     { | 
 |  |  |         bytes = strlen(buf)>size ? size : strlen(buf); | 
 |  |  |         memset(reply, 0, size); | 
 |  |  |         strncpy(reply, buf, bytes); | 
 |  |  |  | 
 |  |  |         log_debug("copy out AT command \"%s\" reply message: \n%s", at, reply); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return res; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT: | 
 |  |  |  *                 Reply:   \r\nOK\r\n | 
 |  |  |  * Return Value: 0: OK     -X: Failure | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout) | 
 |  |  | { | 
 |  |  |     int                     rv; | 
 |  |  |  | 
 |  |  |     if( !comport || !at ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv=send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, NULL, 0); | 
 |  |  |     if( ATRES_EXPECT == rv ) | 
 |  |  |     { | 
 |  |  |         return 0; | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM: | 
 |  |  |  *                  Reply:   \r\nEC20F\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value: 0: OK     -X: Failure | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size) | 
 |  |  | { | 
 |  |  |     int                     rv, len; | 
 |  |  |     char                    buf[ATCMD_REPLY_LEN]; | 
 |  |  |     char                   *ptr_start = buf; | 
 |  |  |     char                   *ptr_end; | 
 |  |  |  | 
 |  |  |     if( !comport || !at || !reply || size<=0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN); | 
 |  |  |     if( rv <= 0 ) | 
 |  |  |     { | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Skip the echo back command line */ | 
 |  |  |     if( !strncmp(buf, at, strlen(at)) ) | 
 |  |  |     { | 
 |  |  |         ptr_start=strchr(buf, '\n'); | 
 |  |  |         if( !ptr_start ) | 
 |  |  |         { | 
 |  |  |             log_error("reply message got wrong\n"); | 
 |  |  |             return -3; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         ptr_start++;   /* skip '\n'  */ | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* find end reply string "\r\nOK\r\n"  */ | 
 |  |  |     ptr_end = strstr(ptr_start, AT_OKSTR); | 
 |  |  |     if( ptr_end ) | 
 |  |  |     { | 
 |  |  |         len = ptr_end - ptr_start; | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         len = strlen(buf) - (ptr_start-buf); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     memset(reply, 0, size); | 
 |  |  |  | 
 |  |  |     len = len>size ? size : len; | 
 |  |  |     memcpy(reply, ptr_start, len); | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ: | 
 |  |  |  *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value: 0: OK     -X: Failure | 
 |  |  |  */ | 
 |  |  | int parser_request_value(char *buf, char *key, char *value, int size) | 
 |  |  | { | 
 |  |  |     char                   *ptr; | 
 |  |  |     int                    i = 0; | 
 |  |  |  | 
 |  |  |     if( !buf || !key || !value || size<=0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     ptr = strstr(buf, key); | 
 |  |  |     if( !ptr ) | 
 |  |  |     { | 
 |  |  |         log_debug("Not found key \"%s\" in %s\n", key, buf); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     ptr=strchr(ptr, ':');  /* found ':' before the value */ | 
 |  |  |     if( !ptr ) | 
 |  |  |     { | 
 |  |  |         log_debug("Not found ':' before value\n"); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  |     ptr++;   /* skip ':'  */ | 
 |  |  |  | 
 |  |  |     if( *ptr == '\"' ) /* skip " */ | 
 |  |  |         ptr++; | 
 |  |  |  | 
 |  |  |     memset(value, 0, size); | 
 |  |  |     while(*ptr!='\r' && i<size-1) | 
 |  |  |     { | 
 |  |  |         if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */ | 
 |  |  |             value[i++] = *ptr; | 
 |  |  |         ptr ++; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     ptr++; /* skip  */ | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ: | 
 |  |  |  *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value: 0: OK     -X: Failure | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *reply, int size) | 
 |  |  | { | 
 |  |  |     int                     i = 0; | 
 |  |  |     int                     rv; | 
 |  |  |     char                    buf[ATCMD_REPLY_LEN]; | 
 |  |  |     char                   *ptr; | 
 |  |  |  | 
 |  |  |     if( !comport || !at || !reply || size<=0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv = send_atcmd(comport, at, timeout, AT_OKSTR, AT_ERRSTR, buf, ATCMD_REPLY_LEN); | 
 |  |  |     if( rv <= 0 ) | 
 |  |  |     { | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     ptr=strchr(buf, '+');  /* found '+' before the value */ | 
 |  |  |     if( !ptr ) | 
 |  |  |     { | 
 |  |  |         log_error("reply message got wrong\n"); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  |     ptr++;   /* skip '+'  */ | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     ptr=strchr(buf, ':');  /* found ':' before the value */ | 
 |  |  |     if( !ptr ) | 
 |  |  |     { | 
 |  |  |         log_error("reply message got wrong\n"); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  |     ptr++;   /* skip ':'  */ | 
 |  |  |  | 
 |  |  |     if( *ptr == '\"' ) /* skip " */ | 
 |  |  |         ptr++; | 
 |  |  |  | 
 |  |  |     memset(reply, 0, size); | 
 |  |  |     while(*ptr!='\r' && i<size-1) | 
 |  |  |     { | 
 |  |  |         if( !isspace(*ptr) && *ptr!='\"') /* skip space,\r,\n ...  */ | 
 |  |  |             reply[i++] = *ptr; | 
 |  |  |         ptr ++; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #ifndef  _ATCMD_H_ | 
 |  |  | #define  _ATCMD_H_ | 
 |  |  |  | 
 |  |  | #include "comport.h" | 
 |  |  |  | 
 |  |  | /* AT command common reply message max length  */ | 
 |  |  | #define ATCMD_REPLY_LEN          1024 /* Max AT command reply message length */ | 
 |  |  | #define ATCMD_LEN                256 /* Max AT command length */ | 
 |  |  |  | 
 |  |  | /* AT command reply message got expect or error string */ | 
 |  |  | #define AT_OKSTR                 "\r\nOK\r\n"     /* expect string always be OK */ | 
 |  |  | #define AT_ERRSTR                "\r\nERROR\r\n"  /* error string always be ERROR */ | 
 |  |  |  | 
 |  |  | /* AT command should be end by $AT_SUFFIX */ | 
 |  |  | #define AT_SUFFIX                "\r\n" | 
 |  |  |  | 
 |  |  | /* send_atcmd)( return value status */ | 
 |  |  | enum | 
 |  |  | { | 
 |  |  |     ATRES_ERROR,    /* AT command reply got error string, such as "ERROR\r\n" */ | 
 |  |  |     ATRES_EXPECT,   /* AT command reply got expect string, such as "OK\r\n" */ | 
 |  |  |     ATRES_TIMEOUT,  /* AT command not get error/expect string, receive util timeout */ | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | /*  Description: this function used to send an AT command from serial port and wait for reply message from | 
 |  |  |  *               the communication module, and it will return once get expet/error string or timeout. | 
 |  |  |  * | 
 |  |  |  *    Arugments: | 
 |  |  |  *         $comport: the serial port which connected to GPRS/3G/4G/NB-IoT/WiFi/BLE/Zigbee/LoRa... | 
 |  |  |  *              $at: the AT command need to be sent, without "\r\n" | 
 |  |  |  *         $timeout: wait for module reply for AT command timeout value, unit micro seconds(ms) | 
 |  |  |  *          $exepct: the expect string reply from communication module | 
 |  |  |  *           $error: the error string reply from communication module | 
 |  |  |  *           $reply: the module reply message output buffer | 
 |  |  |  *            $size: the output buffer ($reply) size | 
 |  |  |  * | 
 |  |  |  * Return value: <0: Function error   0: Got error string   1: Got expect string 2: Receive util timeout | 
 |  |  |  * | 
 |  |  |  */ | 
 |  |  | int send_atcmd(comport_t *comport, char *at, unsigned long timeout, char *expect, char *error, char *reply, int size); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will only reply by "OK" or "ERROR", such as AT: | 
 |  |  |  *                  Reply:   \r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value:  0: OK     -X: ERROR | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_ok(comport_t *comport, char *at, unsigned long timeout); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will reply by a value directly in a single line, such as AT+CGMM: | 
 |  |  |  *                  Reply:   \r\nEC20F\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value:  0: OK     -X: ERROR | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_value(comport_t *comport, char *at, unsigned long timeout, char *reply, int size); | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Parser the $value from $key like "xxx: " line, such as AT+CSQ: | 
 |  |  |  *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value: 0: OK     -X: Failure | 
 |  |  |  */ | 
 |  |  | int parser_request_value(char *buf, char *key, char *value, int size); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  Description: Send AT command which will reply by the value  with a prefix "+CMD: " line, such as AT+CSQ: | 
 |  |  |  *                  Reply:   \r\n+CSQ: 26,99\r\nOK\r\n | 
 |  |  |  * | 
 |  |  |  * Return Value:  0: OK     -X: ERROR | 
 |  |  |  */ | 
 |  |  | int send_atcmd_check_request(comport_t *comport, char *at, unsigned long timeout, char *repy, int size); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | #endif   /* ----- #ifndef _ATCMD_H_  ----- */ | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #include "comport.h" | 
 |  |  |  | 
 |  |  | #define CONFIG_PRINT_LOGGER | 
 |  |  | //#define CONFIG_PRINT_STDOUT | 
 |  |  |  | 
 |  |  | #if ( defined CONFIG_PRINT_LOGGER ) | 
 |  |  | #include "logger.h" | 
 |  |  | #define dbg_print(format,args...) log_error(format, ##args) | 
 |  |  |  | 
 |  |  | #elif ( defined CONFIG_PRINT_STDOUT ) | 
 |  |  | #define dbg_print(format,args...) printf(format, ##args) | 
 |  |  |  | 
 |  |  | #else | 
 |  |  | #define dbg_print(format,args...) do{} while(0); | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  |  | 
 |  |  | static inline void set_settings(comport_t * comport, const char *settings); | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: Open the serial port | 
 |  |  |  * | 
 |  |  |  *   input args: $comport:  corresponding comport point | 
 |  |  |  *               $dev_name:  The comport device name path, such as '/dev/ttyS3' | 
 |  |  |  *               $baudrate:  The baudrate, such as 115200 | 
 |  |  |  *               $settings:  The databit,parity,stopbit,flowctrl settings, such as '8N1N' | 
 |  |  |  * | 
 |  |  |  * return value: The comport opened file description, <0 means failure | 
 |  |  |  */ | 
 |  |  | int comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings) | 
 |  |  | { | 
 |  |  |     int                         rv = -1; | 
 |  |  |     struct termios              old_cfg, new_cfg; | 
 |  |  |     int                         old_flags; | 
 |  |  |     long                        tmp; | 
 |  |  |  | 
 |  |  |     if( !comport || !devname ) | 
 |  |  |     { | 
 |  |  |         dbg_print("invalid input arugments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /*+-----------------------+ | 
 |  |  |      *| open the serial port  | | 
 |  |  |      *+-----------------------+*/ | 
 |  |  |  | 
 |  |  |     memset(comport, 0, sizeof(*comport)); | 
 |  |  |     strncpy(comport->devname, devname, sizeof(comport->devname)); | 
 |  |  |     comport->baudrate = baudrate; | 
 |  |  |     comport->fd = -1; | 
 |  |  |     comport->fragsize = CONFIG_DEF_FRAGSIZE; | 
 |  |  |     set_settings(comport, settings); | 
 |  |  |  | 
 |  |  |     if( !strstr(comport->devname, "tty") ) | 
 |  |  |     { | 
 |  |  |         dbg_print("comport device \"%s\" is not tty device\n", comport->devname); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     comport->fd = open(comport->devname, O_RDWR | O_NOCTTY | O_NONBLOCK); | 
 |  |  |     if( comport->fd<0 ) | 
 |  |  |     { | 
 |  |  |         dbg_print("comport open \"%s\" failed:%s\n", comport->devname, strerror(errno)); | 
 |  |  |         return -3; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if(   (-1 != (old_flags = fcntl(comport->fd, F_GETFL, 0))) | 
 |  |  |        && (-1 != fcntl(comport->fd, F_SETFL, old_flags & ~O_NONBLOCK)) ) | 
 |  |  |     { | 
 |  |  |         /* Flush input and output */ | 
 |  |  |         tcflush(comport->fd, TCIOFLUSH); | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         rv = -4; | 
 |  |  |         goto CleanUp; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if (0 != tcgetattr(comport->fd, &old_cfg)) | 
 |  |  |     { | 
 |  |  |         rv = -5; | 
 |  |  |         goto CleanUp; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     /*+-----------------------+ | 
 |  |  |      *| configure serial port | | 
 |  |  |      *+-----------------------+*/ | 
 |  |  |  | 
 |  |  |     memset(&new_cfg, 0, sizeof(new_cfg)); | 
 |  |  |     new_cfg.c_cflag &= ~CSIZE; | 
 |  |  |     new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); | 
 |  |  |     new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); | 
 |  |  |     new_cfg.c_oflag &= ~(OPOST); | 
 |  |  |  | 
 |  |  |     /* Set the data bit */ | 
 |  |  |     switch (comport->databit) | 
 |  |  |     { | 
 |  |  |         case 0x07: | 
 |  |  |             new_cfg.c_cflag |= CS7; | 
 |  |  |             break; | 
 |  |  |         case 0x06: | 
 |  |  |             new_cfg.c_cflag |= CS6; | 
 |  |  |             break; | 
 |  |  |         case 0x05: | 
 |  |  |             new_cfg.c_cflag |= CS5; | 
 |  |  |             break; | 
 |  |  |         default: | 
 |  |  |             new_cfg.c_cflag |= CS8; | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Set the parity */ | 
 |  |  |     switch (comport->parity) | 
 |  |  |     { | 
 |  |  |         case 0x01:    /* Odd */ | 
 |  |  |             new_cfg.c_cflag |= (PARENB | PARODD); | 
 |  |  |             new_cfg.c_cflag |= (INPCK | ISTRIP); | 
 |  |  |             break; | 
 |  |  |         case 0x02:    /* Even */ | 
 |  |  |             new_cfg.c_cflag |= PARENB; | 
 |  |  |             new_cfg.c_cflag &= ~PARODD;; | 
 |  |  |             new_cfg.c_cflag |= (INPCK | ISTRIP); | 
 |  |  |             break; | 
 |  |  |         case 0x03: | 
 |  |  |             new_cfg.c_cflag &= ~PARENB; | 
 |  |  |             new_cfg.c_cflag &= ~CSTOPB; | 
 |  |  |             break; | 
 |  |  |         default: | 
 |  |  |             new_cfg.c_cflag &= ~PARENB; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Set Stop bit */ | 
 |  |  |     if (0x01 != comport->stopbit) | 
 |  |  |     { | 
 |  |  |         new_cfg.c_cflag |= CSTOPB; | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         new_cfg.c_cflag &= ~CSTOPB; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Set flow control */ | 
 |  |  |     switch (comport->flowctrl) | 
 |  |  |     { | 
 |  |  |         case 1:       /* Software control */ | 
 |  |  |         case 3: | 
 |  |  |             new_cfg.c_cflag &= ~(CRTSCTS); | 
 |  |  |             new_cfg.c_iflag |= (IXON | IXOFF); | 
 |  |  |             break; | 
 |  |  |         case 2:       /* Hardware control */ | 
 |  |  |             new_cfg.c_cflag |= CRTSCTS; | 
 |  |  |             new_cfg.c_iflag &= ~(IXON | IXOFF); | 
 |  |  |             break; | 
 |  |  |         default:      /* NONE */ | 
 |  |  |             new_cfg.c_cflag &= ~(CRTSCTS); | 
 |  |  |             new_cfg.c_iflag &= ~(IXON | IXOFF); | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Set baudrate */ | 
 |  |  |     switch (comport->baudrate) | 
 |  |  |     { | 
 |  |  |         /* Upper is not POSIX(bits/termios-baud.h) */ | 
 |  |  |         case 4000000: | 
 |  |  |             tmp = B4000000; | 
 |  |  |             break; | 
 |  |  |         case 3500000: | 
 |  |  |             tmp = B3500000; | 
 |  |  |             break; | 
 |  |  |         case 3000000: | 
 |  |  |             tmp = B3000000; | 
 |  |  |             break; | 
 |  |  |         case 2500000: | 
 |  |  |             tmp = B2500000; | 
 |  |  |             break; | 
 |  |  |         case 2000000: | 
 |  |  |             tmp = B2000000; | 
 |  |  |             break; | 
 |  |  |         case 1500000: | 
 |  |  |             tmp = B1500000; | 
 |  |  |             break; | 
 |  |  |         case 1152000: | 
 |  |  |             tmp = B1152000; | 
 |  |  |             break; | 
 |  |  |         case 1000000: | 
 |  |  |             tmp = B1000000; | 
 |  |  |             break; | 
 |  |  |         case 921600: | 
 |  |  |             tmp = B921600; | 
 |  |  |             break; | 
 |  |  |         case 576000: | 
 |  |  |             tmp = B576000; | 
 |  |  |             break; | 
 |  |  |         case 500000: | 
 |  |  |             tmp = B500000; | 
 |  |  |             break; | 
 |  |  |         case 460800: | 
 |  |  |             tmp = B460800; | 
 |  |  |             break; | 
 |  |  |         case 230400: | 
 |  |  |             tmp = B230400; | 
 |  |  |             break; | 
 |  |  |         case 115200: | 
 |  |  |             tmp = B115200; | 
 |  |  |             break; | 
 |  |  |         case 57600: | 
 |  |  |             tmp = B57600; | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         /* Below is POSIX(bits/termios.h) */ | 
 |  |  |         case 38400: | 
 |  |  |             tmp = B38400; | 
 |  |  |             break; | 
 |  |  |         case 19200: | 
 |  |  |             tmp = B19200; | 
 |  |  |             break; | 
 |  |  |         case 9600: | 
 |  |  |             tmp = B9600; | 
 |  |  |             break; | 
 |  |  |         case 4800: | 
 |  |  |             tmp = B4800; | 
 |  |  |             break; | 
 |  |  |         case 2400: | 
 |  |  |             tmp = B2400; | 
 |  |  |             break; | 
 |  |  |         case 1800: | 
 |  |  |             tmp = B1800; | 
 |  |  |             break; | 
 |  |  |         case 1200: | 
 |  |  |             tmp = B1200; | 
 |  |  |             break; | 
 |  |  |         case 600: | 
 |  |  |             tmp = B600; | 
 |  |  |             break; | 
 |  |  |         case 300: | 
 |  |  |             tmp = B300; | 
 |  |  |             break; | 
 |  |  |         case 200: | 
 |  |  |             tmp = B200; | 
 |  |  |             break; | 
 |  |  |         case 150: | 
 |  |  |             tmp = B150; | 
 |  |  |             break; | 
 |  |  |         case 134: | 
 |  |  |             tmp = B134; | 
 |  |  |             break; | 
 |  |  |         case 110: | 
 |  |  |             tmp = B110; | 
 |  |  |             break; | 
 |  |  |         case 75: | 
 |  |  |             tmp = B75; | 
 |  |  |             break; | 
 |  |  |         case 50: | 
 |  |  |             tmp = B50; | 
 |  |  |             break; | 
 |  |  |         default: | 
 |  |  |             tmp = B115200; | 
 |  |  |     } | 
 |  |  |     cfsetispeed(&new_cfg, tmp); | 
 |  |  |     cfsetispeed(&new_cfg, tmp); | 
 |  |  |  | 
 |  |  |     /* Set the Com port timeout settings */ | 
 |  |  |     new_cfg.c_cc[VMIN] = 0; | 
 |  |  |     new_cfg.c_cc[VTIME] = 0; | 
 |  |  |  | 
 |  |  |     tcflush(comport->fd, TCIFLUSH); | 
 |  |  |     if (0 != tcsetattr(comport->fd, TCSANOW, &new_cfg)) | 
 |  |  |     { | 
 |  |  |         rv = -6;          // Failed to set device com port settings | 
 |  |  |         goto CleanUp; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     rv = comport->fd; | 
 |  |  |  | 
 |  |  | CleanUp: | 
 |  |  |     return rv; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: close comport | 
 |  |  |  *   input args: $comport:  corresponding comport point | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | void comport_close(comport_t *comport) | 
 |  |  | { | 
 |  |  |     if( !comport ) | 
 |  |  |     { | 
 |  |  |         dbg_print("invalid input arugments\n"); | 
 |  |  |         return ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if ( comport->fd >= 0 ) | 
 |  |  |     { | 
 |  |  |         close(comport->fd); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     comport->fd = -1; | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: write $data_bytes $data to $comport | 
 |  |  |  * return value: 0: write ok  <0: write failure | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | int comport_send(comport_t *comport, char *data, int data_bytes) | 
 |  |  | { | 
 |  |  |     char                       *ptr; | 
 |  |  |     int                         left, bytes = 0; | 
 |  |  |     int                         rv = 0; | 
 |  |  |  | 
 |  |  |     if( !comport || !data || data_bytes<=0 ) | 
 |  |  |     { | 
 |  |  |         dbg_print("invalid input arugments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( comport->fd < 0 ) | 
 |  |  |     { | 
 |  |  |         dbg_print("Serail port not opened\n"); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     ptr = data; | 
 |  |  |     left = data_bytes; | 
 |  |  |  | 
 |  |  |     while( left > 0 ) | 
 |  |  |     { | 
 |  |  |         /* Large data, then slice them to frag and send */ | 
 |  |  |         bytes = left>comport->fragsize ? comport->fragsize : left; | 
 |  |  |  | 
 |  |  |         rv = write(comport->fd, ptr, bytes); | 
 |  |  |         if( rv<0 ) | 
 |  |  |         { | 
 |  |  |             rv = -3; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         left -= rv; | 
 |  |  |         ptr += rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return rv; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes | 
 |  |  |  * return value: the actual read data bytes, <0: read failure | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | int comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout) | 
 |  |  | { | 
 |  |  |     fd_set                      rdfds, exfds; | 
 |  |  |     struct timeval              to, *to_ptr = NULL; | 
 |  |  |     int                         ret, rv = 0; | 
 |  |  |     int                         bytes = 0; | 
 |  |  |  | 
 |  |  |     if ( !comport || !buf || buf_size<=0 ) | 
 |  |  |     { | 
 |  |  |         dbg_print("invalid input arugments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if ( comport->fd < 0 ) | 
 |  |  |     { | 
 |  |  |         dbg_print("Serail port not opened\n"); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     memset(buf, 0, buf_size); | 
 |  |  |  | 
 |  |  |     FD_ZERO(&rdfds); | 
 |  |  |     FD_ZERO(&exfds); | 
 |  |  |     FD_SET(comport->fd, &rdfds); | 
 |  |  |     FD_SET(comport->fd, &exfds); | 
 |  |  |  | 
 |  |  |     if( TIMEOUT_NONE != timeout ) | 
 |  |  |     { | 
 |  |  |         to.tv_sec = (time_t) (timeout / 1000); | 
 |  |  |         to.tv_usec = (long)(1000 * (timeout % 1000)); | 
 |  |  |         to_ptr = &to; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     while( 1 ) | 
 |  |  |     { | 
 |  |  |         /* check got data arrive or not */ | 
 |  |  |         ret = select(comport->fd+1, &rdfds, 0, &exfds, to_ptr); | 
 |  |  |         if( ret<0 ) | 
 |  |  |         { | 
 |  |  |             /* EINTR means catch interrupt signal */ | 
 |  |  |             dbg_print("comport select() failed: %s\n", strerror(errno)); | 
 |  |  |             rv = EINTR==errno ? 0 : -3; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |         else if( 0 == ret ) /* timeout */ | 
 |  |  |         { | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         /* read data from comport */ | 
 |  |  |         ret = read(comport->fd, buf+bytes, buf_size-bytes); | 
 |  |  |         if(ret <= 0) | 
 |  |  |         { | 
 |  |  |             dbg_print("comport read() failed: %s\n", strerror(errno)); | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         bytes += ret; | 
 |  |  |         if( bytes >= buf_size ) | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         /* try to read data in 1ms again, if no data arrive it will break */ | 
 |  |  |         to.tv_sec = 0; | 
 |  |  |         to.tv_usec = 10000; | 
 |  |  |         to_ptr = &to; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( !rv ) | 
 |  |  |         rv = bytes; | 
 |  |  |  | 
 |  |  |     return rv; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /************************************************************************************** | 
 |  |  |  *  Description: Set the comport databit,parity,stopbit,flowctrl into the comport structure | 
 |  |  |  *   Input Args: comport: the comport_t pointer | 
 |  |  |  *               settings: The databit/parity/stopbit/flowctrl settings as like "8N1N" | 
 |  |  |  *  Output Args: NONE | 
 |  |  |  * Return Value: NONE | 
 |  |  |  *************************************************************************************/ | 
 |  |  | static inline void set_settings(comport_t * comport, const char *settings) | 
 |  |  | { | 
 |  |  |     if( !settings || !comport ) | 
 |  |  |     { | 
 |  |  |         dbg_print("invalid input arugments\n"); | 
 |  |  |         return ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     switch (settings[0])        /* data bit */ | 
 |  |  |     { | 
 |  |  |         case '7': | 
 |  |  |             comport->databit = 7; | 
 |  |  |             break; | 
 |  |  |         case '8': | 
 |  |  |         default: | 
 |  |  |             comport->databit = 8; | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     switch (settings[1])        /* parity */ | 
 |  |  |     { | 
 |  |  |         case 'O': | 
 |  |  |         case 'o': | 
 |  |  |             comport->parity = 1; | 
 |  |  |             break; | 
 |  |  |         case 'E': | 
 |  |  |         case 'e': | 
 |  |  |             comport->parity = 2; | 
 |  |  |             break; | 
 |  |  |         case 'S': | 
 |  |  |         case 's': | 
 |  |  |             comport->parity = 3; | 
 |  |  |             break; | 
 |  |  |         case 'N': | 
 |  |  |         case 'n': | 
 |  |  |         default: | 
 |  |  |             comport->parity = 0; | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     switch (settings[2])        /* stop bit */ | 
 |  |  |     { | 
 |  |  |         case '0': | 
 |  |  |             comport->stopbit = 0; | 
 |  |  |             break; | 
 |  |  |         case '1': | 
 |  |  |         default: | 
 |  |  |             comport->stopbit = 1; | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     switch (settings[3])        /* flow control */ | 
 |  |  |     { | 
 |  |  |         case 'S': | 
 |  |  |         case 's': | 
 |  |  |             comport->flowctrl = 1; | 
 |  |  |             break; | 
 |  |  |         case 'H': | 
 |  |  |         case 'h': | 
 |  |  |             comport->flowctrl = 2; | 
 |  |  |             break; | 
 |  |  |         case 'B': | 
 |  |  |         case 'b': | 
 |  |  |             comport->flowctrl = 3; | 
 |  |  |             break; | 
 |  |  |         case 'N': | 
 |  |  |         case 'n': | 
 |  |  |         default: | 
 |  |  |             comport->flowctrl = 0; | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #ifndef  _COMPORT_H_ | 
 |  |  | #define  _COMPORT_H_ | 
 |  |  |  | 
 |  |  | #include  <stdio.h> | 
 |  |  | #include  <stdlib.h> | 
 |  |  | #include  <unistd.h> | 
 |  |  | #include  <string.h> | 
 |  |  | #include  <getopt.h> | 
 |  |  | #include  <fcntl.h> | 
 |  |  | #include  <errno.h> | 
 |  |  | #include  <termios.h> | 
 |  |  | #include  <sys/stat.h> | 
 |  |  | #include  <sys/wait.h> | 
 |  |  | #include  <sys/types.h> | 
 |  |  | #include  <sys/stat.h> | 
 |  |  | #include  <sys/select.h> | 
 |  |  |  | 
 |  |  | #define CONFIG_DEF_FRAGSIZE    128 | 
 |  |  | typedef struct comport_s | 
 |  |  | { | 
 |  |  |     char           devname[32]; | 
 |  |  |     unsigned char  databit, parity, stopbit, flowctrl; | 
 |  |  |     long           baudrate; | 
 |  |  |  | 
 |  |  |     int            fd; | 
 |  |  |     int            fragsize; /* frag size when do large data send */ | 
 |  |  | } comport_t; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: Open the comport and returned by $comport | 
 |  |  |  * | 
 |  |  |  *   input args: $comport:  corresponding comport handler | 
 |  |  |  *               $devname:  The comport device name path, such as '/dev/ttyS3' | 
 |  |  |  *               $baudrate: The baudrate, such as 115200 | 
 |  |  |  *               $settings: The databit,parity,stopbit,flowctrl settings, such as '8N1N' | 
 |  |  |  * | 
 |  |  |  * return value: The comport opened file description, <0 means failure | 
 |  |  |  */ | 
 |  |  | extern int  comport_open(comport_t *comport, const char *devname, long baudrate, const char *settings); | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: close comport | 
 |  |  |  *   input args: $comport:  corresponding comport handler | 
 |  |  |  */ | 
 |  |  | extern void comport_close(comport_t *comport); | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: write $bytes $data to $comport | 
 |  |  |  * return value: 0: write ok  <0: write failure | 
 |  |  |  */ | 
 |  |  | extern int  comport_send(comport_t *comport, char *data, int data_bytes); | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  *  description: read data from $comport in $timeout <ms> to $buf no more than $buf_size bytes | 
 |  |  |  * return value: the actual read data bytes, <0: read failure | 
 |  |  |  */ | 
 |  |  | #define TIMEOUT_NONE           0 | 
 |  |  | extern int  comport_recv(comport_t *comport, char *buf, int buf_size, unsigned long timeout); | 
 |  |  |  | 
 |  |  | #endif | 
 
| New file | 
 |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |    @file    dictionary.c | 
 |  |  |    @author  N. Devillard | 
 |  |  |    @brief   Implements a dictionary for string variables. | 
 |  |  |    @url     https://github.com/ndevilla/iniparser | 
 |  |  |  | 
 |  |  |    This module implements a simple dictionary object, i.e. a list | 
 |  |  |    of string/string associations. This object is useful to store e.g. | 
 |  |  |    informations retrieved from a configuration file (ini files). | 
 |  |  | */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                                 Includes | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  | #include "dictionary.h" | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <stdlib.h> | 
 |  |  | #include <string.h> | 
 |  |  | #include <unistd.h> | 
 |  |  |  | 
 |  |  | /** Maximum value size for integers and doubles. */ | 
 |  |  | #define MAXVALSZ    1024 | 
 |  |  |  | 
 |  |  | /** Minimal allocated number of entries in a dictionary */ | 
 |  |  | #define DICTMINSZ   128 | 
 |  |  |  | 
 |  |  | /** Invalid key token */ | 
 |  |  | #define DICT_INVALID_KEY    ((char*)-1) | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                             Private functions | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Duplicate a string | 
 |  |  |   @param    s String to duplicate | 
 |  |  |   @return   Pointer to a newly allocated string, to be freed with free() | 
 |  |  |  | 
 |  |  |   This is a replacement for strdup(). This implementation is provided | 
 |  |  |   for systems that do not have it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static char * xstrdup(const char * s) | 
 |  |  | { | 
 |  |  |     char * t ; | 
 |  |  |     size_t len ; | 
 |  |  |     if (!s) | 
 |  |  |         return NULL ; | 
 |  |  |  | 
 |  |  |     len = strlen(s) + 1 ; | 
 |  |  |     t = (char*) malloc(len) ; | 
 |  |  |     if (t) { | 
 |  |  |         memcpy(t, s, len) ; | 
 |  |  |     } | 
 |  |  |     return t ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Double the size of the dictionary | 
 |  |  |   @param    d Dictionary to grow | 
 |  |  |   @return   This function returns non-zero in case of failure | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static int dictionary_grow(dictionary * d) | 
 |  |  | { | 
 |  |  |     char        ** new_val ; | 
 |  |  |     char        ** new_key ; | 
 |  |  |     unsigned     * new_hash ; | 
 |  |  |  | 
 |  |  |     new_val  = (char**) calloc(d->size * 2, sizeof *d->val); | 
 |  |  |     new_key  = (char**) calloc(d->size * 2, sizeof *d->key); | 
 |  |  |     new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash); | 
 |  |  |     if (!new_val || !new_key || !new_hash) { | 
 |  |  |         /* An allocation failed, leave the dictionary unchanged */ | 
 |  |  |         if (new_val) | 
 |  |  |             free(new_val); | 
 |  |  |         if (new_key) | 
 |  |  |             free(new_key); | 
 |  |  |         if (new_hash) | 
 |  |  |             free(new_hash); | 
 |  |  |         return -1 ; | 
 |  |  |     } | 
 |  |  |     /* Initialize the newly allocated space */ | 
 |  |  |     memcpy(new_val, d->val, d->size * sizeof(char *)); | 
 |  |  |     memcpy(new_key, d->key, d->size * sizeof(char *)); | 
 |  |  |     memcpy(new_hash, d->hash, d->size * sizeof(unsigned)); | 
 |  |  |     /* Delete previous data */ | 
 |  |  |     free(d->val); | 
 |  |  |     free(d->key); | 
 |  |  |     free(d->hash); | 
 |  |  |     /* Actually update the dictionary */ | 
 |  |  |     d->size *= 2 ; | 
 |  |  |     d->val = new_val; | 
 |  |  |     d->key = new_key; | 
 |  |  |     d->hash = new_hash; | 
 |  |  |     return 0 ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                             Function codes | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Compute the hash key for a string. | 
 |  |  |   @param    key     Character string to use for key. | 
 |  |  |   @return   1 unsigned int on at least 32 bits. | 
 |  |  |  | 
 |  |  |   This hash function has been taken from an Article in Dr Dobbs Journal. | 
 |  |  |   This is normally a collision-free function, distributing keys evenly. | 
 |  |  |   The key is stored anyway in the struct so that collision can be avoided | 
 |  |  |   by comparing the key itself in last resort. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | unsigned dictionary_hash(const char * key) | 
 |  |  | { | 
 |  |  |     size_t      len ; | 
 |  |  |     unsigned    hash ; | 
 |  |  |     size_t      i ; | 
 |  |  |  | 
 |  |  |     if (!key) | 
 |  |  |         return 0 ; | 
 |  |  |  | 
 |  |  |     len = strlen(key); | 
 |  |  |     for (hash=0, i=0 ; i<len ; i++) { | 
 |  |  |         hash += (unsigned)key[i] ; | 
 |  |  |         hash += (hash<<10); | 
 |  |  |         hash ^= (hash>>6) ; | 
 |  |  |     } | 
 |  |  |     hash += (hash <<3); | 
 |  |  |     hash ^= (hash >>11); | 
 |  |  |     hash += (hash <<15); | 
 |  |  |     return hash ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Create a new dictionary object. | 
 |  |  |   @param    size    Optional initial size of the dictionary. | 
 |  |  |   @return   1 newly allocated dictionary object. | 
 |  |  |  | 
 |  |  |   This function allocates a new dictionary object of given size and returns | 
 |  |  |   it. If you do not know in advance (roughly) the number of entries in the | 
 |  |  |   dictionary, give size=0. | 
 |  |  |  */ | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | dictionary * dictionary_new(size_t size) | 
 |  |  | { | 
 |  |  |     dictionary  *   d ; | 
 |  |  |  | 
 |  |  |     /* If no size was specified, allocate space for DICTMINSZ */ | 
 |  |  |     if (size<DICTMINSZ) size=DICTMINSZ ; | 
 |  |  |  | 
 |  |  |     d = (dictionary*) calloc(1, sizeof *d) ; | 
 |  |  |  | 
 |  |  |     if (d) { | 
 |  |  |         d->size = size ; | 
 |  |  |         d->val  = (char**) calloc(size, sizeof *d->val); | 
 |  |  |         d->key  = (char**) calloc(size, sizeof *d->key); | 
 |  |  |         d->hash = (unsigned*) calloc(size, sizeof *d->hash); | 
 |  |  |     } | 
 |  |  |     return d ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete a dictionary object | 
 |  |  |   @param    d   dictionary object to deallocate. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Deallocate a dictionary object and all memory associated to it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_del(dictionary * d) | 
 |  |  | { | 
 |  |  |     ssize_t  i ; | 
 |  |  |  | 
 |  |  |     if (d==NULL) return ; | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]!=NULL) | 
 |  |  |             free(d->key[i]); | 
 |  |  |         if (d->val[i]!=NULL) | 
 |  |  |             free(d->val[i]); | 
 |  |  |     } | 
 |  |  |     free(d->val); | 
 |  |  |     free(d->key); | 
 |  |  |     free(d->hash); | 
 |  |  |     free(d); | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get a value from a dictionary. | 
 |  |  |   @param    d       dictionary object to search. | 
 |  |  |   @param    key     Key to look for in the dictionary. | 
 |  |  |   @param    def     Default value to return if key not found. | 
 |  |  |   @return   1 pointer to internally allocated character string. | 
 |  |  |  | 
 |  |  |   This function locates a key in a dictionary and returns a pointer to its | 
 |  |  |   value, or the passed 'def' pointer if no such key can be found in | 
 |  |  |   dictionary. The returned character pointer points to data internal to the | 
 |  |  |   dictionary object, you should not try to free it or modify it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char * dictionary_get(const dictionary * d, const char * key, const char * def) | 
 |  |  | { | 
 |  |  |     unsigned    hash ; | 
 |  |  |     ssize_t      i ; | 
 |  |  |  | 
 |  |  |     hash = dictionary_hash(key); | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         /* Compare hash */ | 
 |  |  |         if (hash==d->hash[i]) { | 
 |  |  |             /* Compare string, to avoid hash collisions */ | 
 |  |  |             if (!strcmp(key, d->key[i])) { | 
 |  |  |                 return d->val[i] ; | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     return def ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Set a value in a dictionary. | 
 |  |  |   @param    d       dictionary object to modify. | 
 |  |  |   @param    key     Key to modify or add. | 
 |  |  |   @param    val     Value to add. | 
 |  |  |   @return   int     0 if Ok, anything else otherwise | 
 |  |  |  | 
 |  |  |   If the given key is found in the dictionary, the associated value is | 
 |  |  |   replaced by the provided one. If the key cannot be found in the | 
 |  |  |   dictionary, it is added to it. | 
 |  |  |  | 
 |  |  |   It is Ok to provide a NULL value for val, but NULL values for the dictionary | 
 |  |  |   or the key are considered as errors: the function will return immediately | 
 |  |  |   in such a case. | 
 |  |  |  | 
 |  |  |   Notice that if you dictionary_set a variable to NULL, a call to | 
 |  |  |   dictionary_get will return a NULL value: the variable will be found, and | 
 |  |  |   its value (NULL) is returned. In other words, setting the variable | 
 |  |  |   content to NULL is equivalent to deleting the variable from the | 
 |  |  |   dictionary. It is not possible (in this implementation) to have a key in | 
 |  |  |   the dictionary without value. | 
 |  |  |  | 
 |  |  |   This function returns non-zero in case of failure. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int dictionary_set(dictionary * d, const char * key, const char * val) | 
 |  |  | { | 
 |  |  |     ssize_t         i ; | 
 |  |  |     unsigned       hash ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || key==NULL) return -1 ; | 
 |  |  |  | 
 |  |  |     /* Compute hash for this key */ | 
 |  |  |     hash = dictionary_hash(key) ; | 
 |  |  |     /* Find if value is already in dictionary */ | 
 |  |  |     if (d->n>0) { | 
 |  |  |         for (i=0 ; i<d->size ; i++) { | 
 |  |  |             if (d->key[i]==NULL) | 
 |  |  |                 continue ; | 
 |  |  |             if (hash==d->hash[i]) { /* Same hash value */ | 
 |  |  |                 if (!strcmp(key, d->key[i])) {   /* Same key */ | 
 |  |  |                     /* Found a value: modify and return */ | 
 |  |  |                     if (d->val[i]!=NULL) | 
 |  |  |                         free(d->val[i]); | 
 |  |  |                     d->val[i] = (val ? xstrdup(val) : NULL); | 
 |  |  |                     /* Value has been modified: return */ | 
 |  |  |                     return 0 ; | 
 |  |  |                 } | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     /* Add a new value */ | 
 |  |  |     /* See if dictionary needs to grow */ | 
 |  |  |     if (d->n==d->size) { | 
 |  |  |         /* Reached maximum size: reallocate dictionary */ | 
 |  |  |         if (dictionary_grow(d) != 0) | 
 |  |  |             return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Insert key in the first empty slot. Start at d->n and wrap at | 
 |  |  |        d->size. Because d->n < d->size this will necessarily | 
 |  |  |        terminate. */ | 
 |  |  |     for (i=d->n ; d->key[i] ; ) { | 
 |  |  |         if(++i == d->size) i = 0; | 
 |  |  |     } | 
 |  |  |     /* Copy key */ | 
 |  |  |     d->key[i]  = xstrdup(key); | 
 |  |  |     d->val[i]  = (val ? xstrdup(val) : NULL) ; | 
 |  |  |     d->hash[i] = hash; | 
 |  |  |     d->n ++ ; | 
 |  |  |     return 0 ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete a key in a dictionary | 
 |  |  |   @param    d       dictionary object to modify. | 
 |  |  |   @param    key     Key to remove. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function deletes a key in a dictionary. Nothing is done if the | 
 |  |  |   key cannot be found. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_unset(dictionary * d, const char * key) | 
 |  |  | { | 
 |  |  |     unsigned    hash ; | 
 |  |  |     ssize_t      i ; | 
 |  |  |  | 
 |  |  |     if (key == NULL || d == NULL) { | 
 |  |  |         return; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     hash = dictionary_hash(key); | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         /* Compare hash */ | 
 |  |  |         if (hash==d->hash[i]) { | 
 |  |  |             /* Compare string, to avoid hash collisions */ | 
 |  |  |             if (!strcmp(key, d->key[i])) { | 
 |  |  |                 /* Found key */ | 
 |  |  |                 break ; | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     if (i>=d->size) | 
 |  |  |         /* Key not found */ | 
 |  |  |         return ; | 
 |  |  |  | 
 |  |  |     free(d->key[i]); | 
 |  |  |     d->key[i] = NULL ; | 
 |  |  |     if (d->val[i]!=NULL) { | 
 |  |  |         free(d->val[i]); | 
 |  |  |         d->val[i] = NULL ; | 
 |  |  |     } | 
 |  |  |     d->hash[i] = 0 ; | 
 |  |  |     d->n -- ; | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Dump a dictionary to an opened file pointer. | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    f   Opened file pointer. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Dumps a dictionary onto an opened file pointer. Key pairs are printed out | 
 |  |  |   as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as | 
 |  |  |   output file pointers. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_dump(const dictionary * d, FILE * out) | 
 |  |  | { | 
 |  |  |     ssize_t  i ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || out==NULL) return ; | 
 |  |  |     if (d->n<1) { | 
 |  |  |         fprintf(out, "empty dictionary\n"); | 
 |  |  |         return ; | 
 |  |  |     } | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]) { | 
 |  |  |             fprintf(out, "%20s\t[%s]\n", | 
 |  |  |                     d->key[i], | 
 |  |  |                     d->val[i] ? d->val[i] : "UNDEF"); | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     return ; | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |    @file    dictionary.h | 
 |  |  |    @author  N. Devillard | 
 |  |  |    @brief   Implements a dictionary for string variables. | 
 |  |  |    @url     https://github.com/ndevilla/iniparser | 
 |  |  |  | 
 |  |  |    This module implements a simple dictionary object, i.e. a list | 
 |  |  |    of string/string associations. This object is useful to store e.g. | 
 |  |  |    informations retrieved from a configuration file (ini files). | 
 |  |  | */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | #ifndef _DICTIONARY_H_ | 
 |  |  | #define _DICTIONARY_H_ | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                                 Includes | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <stdlib.h> | 
 |  |  | #include <string.h> | 
 |  |  | #include <unistd.h> | 
 |  |  |  | 
 |  |  | #ifdef __cplusplus | 
 |  |  | extern "C" { | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                                 New types | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Dictionary object | 
 |  |  |  | 
 |  |  |   This object contains a list of string/string associations. Each | 
 |  |  |   association is identified by a unique string key. Looking up values | 
 |  |  |   in the dictionary is speeded up by the use of a (hopefully collision-free) | 
 |  |  |   hash function. | 
 |  |  |  */ | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | typedef struct _dictionary_ { | 
 |  |  |     int             n ;     /** Number of entries in dictionary */ | 
 |  |  |     ssize_t         size ;  /** Storage size */ | 
 |  |  |     char        **  val ;   /** List of string values */ | 
 |  |  |     char        **  key ;   /** List of string keys */ | 
 |  |  |     unsigned     *  hash ;  /** List of hash values for keys */ | 
 |  |  | } dictionary ; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                             Function prototypes | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Compute the hash key for a string. | 
 |  |  |   @param    key     Character string to use for key. | 
 |  |  |   @return   1 unsigned int on at least 32 bits. | 
 |  |  |  | 
 |  |  |   This hash function has been taken from an Article in Dr Dobbs Journal. | 
 |  |  |   This is normally a collision-free function, distributing keys evenly. | 
 |  |  |   The key is stored anyway in the struct so that collision can be avoided | 
 |  |  |   by comparing the key itself in last resort. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | unsigned dictionary_hash(const char * key); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Create a new dictionary object. | 
 |  |  |   @param    size    Optional initial size of the dictionary. | 
 |  |  |   @return   1 newly allocated dictionary object. | 
 |  |  |  | 
 |  |  |   This function allocates a new dictionary object of given size and returns | 
 |  |  |   it. If you do not know in advance (roughly) the number of entries in the | 
 |  |  |   dictionary, give size=0. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | dictionary * dictionary_new(size_t size); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete a dictionary object | 
 |  |  |   @param    d   dictionary object to deallocate. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Deallocate a dictionary object and all memory associated to it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_del(dictionary * vd); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get a value from a dictionary. | 
 |  |  |   @param    d       dictionary object to search. | 
 |  |  |   @param    key     Key to look for in the dictionary. | 
 |  |  |   @param    def     Default value to return if key not found. | 
 |  |  |   @return   1 pointer to internally allocated character string. | 
 |  |  |  | 
 |  |  |   This function locates a key in a dictionary and returns a pointer to its | 
 |  |  |   value, or the passed 'def' pointer if no such key can be found in | 
 |  |  |   dictionary. The returned character pointer points to data internal to the | 
 |  |  |   dictionary object, you should not try to free it or modify it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char * dictionary_get(const dictionary * d, const char * key, const char * def); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Set a value in a dictionary. | 
 |  |  |   @param    d       dictionary object to modify. | 
 |  |  |   @param    key     Key to modify or add. | 
 |  |  |   @param    val     Value to add. | 
 |  |  |   @return   int     0 if Ok, anything else otherwise | 
 |  |  |  | 
 |  |  |   If the given key is found in the dictionary, the associated value is | 
 |  |  |   replaced by the provided one. If the key cannot be found in the | 
 |  |  |   dictionary, it is added to it. | 
 |  |  |  | 
 |  |  |   It is Ok to provide a NULL value for val, but NULL values for the dictionary | 
 |  |  |   or the key are considered as errors: the function will return immediately | 
 |  |  |   in such a case. | 
 |  |  |  | 
 |  |  |   Notice that if you dictionary_set a variable to NULL, a call to | 
 |  |  |   dictionary_get will return a NULL value: the variable will be found, and | 
 |  |  |   its value (NULL) is returned. In other words, setting the variable | 
 |  |  |   content to NULL is equivalent to deleting the variable from the | 
 |  |  |   dictionary. It is not possible (in this implementation) to have a key in | 
 |  |  |   the dictionary without value. | 
 |  |  |  | 
 |  |  |   This function returns non-zero in case of failure. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int dictionary_set(dictionary * vd, const char * key, const char * val); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete a key in a dictionary | 
 |  |  |   @param    d       dictionary object to modify. | 
 |  |  |   @param    key     Key to remove. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function deletes a key in a dictionary. Nothing is done if the | 
 |  |  |   key cannot be found. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_unset(dictionary * d, const char * key); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Dump a dictionary to an opened file pointer. | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    f   Opened file pointer. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Dumps a dictionary onto an opened file pointer. Key pairs are printed out | 
 |  |  |   as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as | 
 |  |  |   output file pointers. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void dictionary_dump(const dictionary * d, FILE * out); | 
 |  |  |  | 
 |  |  | #ifdef __cplusplus | 
 |  |  | } | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | #endif | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #include "logger.h" | 
 |  |  | #include "esp32.h" | 
 |  |  |  | 
 |  |  | int esp32_init_module(comport_t *comport) | 
 |  |  | { | 
 |  |  |     int             rv; | 
 |  |  |     char            version[256] = {0}; | 
 |  |  |  | 
 |  |  |     if( !comport ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     rv = esp32_reset(comport); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("Reset ESP32 WiFi module failed: %d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     esp32_set_echo(comport, DISABLE); | 
 |  |  |  | 
 |  |  |     esp32_set_sysstore(comport, ENABLE); | 
 |  |  |  | 
 |  |  |     rv = esp32_get_firmware(comport, version, sizeof(version)); | 
 |  |  |     if( rv < 0) | 
 |  |  |     { | 
 |  |  |         log_error("Query ESP32 firmware version failed: %d\n", rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     log_info("ESP32 firmware version:\n%s\n", version); | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd) | 
 |  |  | { | 
 |  |  |     esp32_set_wmode(comport, MODE_SOFTAP, ENABLE); | 
 |  |  |  | 
 |  |  |     esp32_set_ipaddr(comport, MODE_SOFTAP, DEF_SOFTAP_IPADDR, DEF_SOFTAP_IPADDR); | 
 |  |  |  | 
 |  |  |     esp32_set_dhcp(comport, MODE_SOFTAP, ENABLE); | 
 |  |  |  | 
 |  |  |     esp32_set_softap(comport, ssid, pwd, 11, MODE_WPA2PSK); | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int esp32_join_network(comport_t *comport, char *ssid, char *pwd) | 
 |  |  | { | 
 |  |  |     int             rv, status = 0; | 
 |  |  |     int             times=10; | 
 |  |  |     char            buf[128] = {0}; | 
 |  |  |  | 
 |  |  |     if( !comport ) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     esp32_join_status(comport, &status, buf); | 
 |  |  |     if( 2==status && !strcmp(buf, ssid) ) | 
 |  |  |     { | 
 |  |  |         log_info("ESP32 connected to \"%s\" already\n", ssid); | 
 |  |  |         return 0; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     esp32_set_wmode(comport, MODE_STATION, ENABLE); | 
 |  |  |  | 
 |  |  |     esp32_set_dhcp(comport, MODE_STATION, ENABLE); | 
 |  |  |  | 
 |  |  |     rv = esp32_connect_ap(comport, ssid, pwd); | 
 |  |  |     if( rv < 0 ) | 
 |  |  |     { | 
 |  |  |         log_error("connect to AP \"%s\" failed, rv=%d", ssid, rv); | 
 |  |  |         return rv; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     while(times--) | 
 |  |  |     { | 
 |  |  |         rv = esp32_join_status(comport, &status, buf); | 
 |  |  |         if( 2 == status ) | 
 |  |  |         { | 
 |  |  |             log_info("ESP32 connected to \"%s\" already\n", ssid); | 
 |  |  |             return 0; | 
 |  |  |         } | 
 |  |  |         rv = -3; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return rv; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | int esp32_check_network(comport_t *comport) | 
 |  |  | { | 
 |  |  |     int             rv, times=5; | 
 |  |  |     char            ip[IP_LEN] = {0}; | 
 |  |  |     char            gateway[IP_LEN] = {0}; | 
 |  |  |  | 
 |  |  |     memset(ip, 0, sizeof(ip)); | 
 |  |  |     memset(gateway, 0, sizeof(gateway)); | 
 |  |  |     rv = esp32_get_ipaddr(comport, MODE_STATION, ip, gateway); | 
 |  |  |     if( rv<0 ) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     if( !strlen(ip) || !strlen(gateway) ) | 
 |  |  |         return -3; | 
 |  |  |  | 
 |  |  |     log_info("IP address: %s, netmask: %s\n", ip, gateway); | 
 |  |  |  | 
 |  |  |     while( times-- ) | 
 |  |  |     { | 
 |  |  |         if( !(rv=esp32_ping(comport, gateway, 5000)) ) | 
 |  |  |         { | 
 |  |  |             rv = 0; | 
 |  |  |             break; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int esp32_setup_tcp_server(comport_t *comport, int port) | 
 |  |  | { | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_socket_mux(comport, ENABLE); | 
 |  |  |     if(rv<0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_socket_clients(comport, 2); | 
 |  |  |     if(rv<0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_tcp_server(comport, port); | 
 |  |  |     if(rv<0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_socket_timeout(comport, 600); | 
 |  |  |     if(rv<0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int esp32_setup_tcp_client(comport_t *comport, char *host, int port) | 
 |  |  | { | 
 |  |  |     int             rv; | 
 |  |  |  | 
 |  |  |     rv = esp32_set_tcp_client(comport, DISABLE, host, port); | 
 |  |  |     if(rv<0) | 
 |  |  |         return rv; | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #ifndef  _ESP32_H_ | 
 |  |  | #define  _ESP32_H_ | 
 |  |  |  | 
 |  |  | #include "at-esp32.h" | 
 |  |  |  | 
 |  |  | #define DEF_SOFTAP_IPADDR    "192.168.8.1" | 
 |  |  | #define DEF_SOFTAP_SSID      "Router_ESP32" | 
 |  |  | #define DEF_SOFTAP_PWD       "12345678" | 
 |  |  |  | 
 |  |  | extern int esp32_init_module(comport_t *comport); | 
 |  |  |  | 
 |  |  | extern int esp32_setup_softap(comport_t *comport, char *ssid, char *pwd); | 
 |  |  |  | 
 |  |  | extern int esp32_join_network(comport_t *comport, char *ssid, char *pwd); | 
 |  |  |  | 
 |  |  | extern int esp32_check_network(comport_t *comport); | 
 |  |  |  | 
 |  |  | extern int esp32_setup_tcp_server(comport_t *comport, int port); | 
 |  |  |  | 
 |  |  | extern int esp32_setup_tcp_client(comport_t *comport, char *host, int port); | 
 |  |  |  | 
 |  |  | #endif   /* ----- #ifndef _ESP32_H_  ----- */ | 
 
| New file | 
 |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |    @file    iniparser.c | 
 |  |  |    @author  N. Devillard | 
 |  |  |    @brief   Parser for ini files. | 
 |  |  |    @url     https://github.com/ndevilla/iniparser | 
 |  |  | */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | /*---------------------------- Includes ------------------------------------*/ | 
 |  |  | #include <ctype.h> | 
 |  |  | #include <stdarg.h> | 
 |  |  | #include "iniparser.h" | 
 |  |  |  | 
 |  |  | /*---------------------------- Defines -------------------------------------*/ | 
 |  |  | #define ASCIILINESZ         (1024) | 
 |  |  | #define INI_INVALID_KEY     ((char*)-1) | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                         Private to this module | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |  * This enum stores the status for each parsed line (internal use only). | 
 |  |  |  */ | 
 |  |  | typedef enum _line_status_ { | 
 |  |  |     LINE_UNPROCESSED, | 
 |  |  |     LINE_ERROR, | 
 |  |  |     LINE_EMPTY, | 
 |  |  |     LINE_COMMENT, | 
 |  |  |     LINE_SECTION, | 
 |  |  |     LINE_VALUE | 
 |  |  | } line_status ; | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Convert a string to lowercase. | 
 |  |  |   @param    in   String to convert. | 
 |  |  |   @param    out Output buffer. | 
 |  |  |   @param    len Size of the out buffer. | 
 |  |  |   @return   ptr to the out buffer or NULL if an error occured. | 
 |  |  |  | 
 |  |  |   This function convert a string into lowercase. | 
 |  |  |   At most len - 1 elements of the input string will be converted. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static const char * strlwc(const char * in, char *out, unsigned len) | 
 |  |  | { | 
 |  |  |     unsigned i ; | 
 |  |  |  | 
 |  |  |     if (in==NULL || out == NULL || len==0) return NULL ; | 
 |  |  |     i=0 ; | 
 |  |  |     while (in[i] != '\0' && i < len-1) { | 
 |  |  |         out[i] = (char)tolower((int)in[i]); | 
 |  |  |         i++ ; | 
 |  |  |     } | 
 |  |  |     out[i] = '\0'; | 
 |  |  |     return out ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Duplicate a string | 
 |  |  |   @param    s String to duplicate | 
 |  |  |   @return   Pointer to a newly allocated string, to be freed with free() | 
 |  |  |  | 
 |  |  |   This is a replacement for strdup(). This implementation is provided | 
 |  |  |   for systems that do not have it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static char * xstrdup(const char * s) | 
 |  |  | { | 
 |  |  |     char * t ; | 
 |  |  |     size_t len ; | 
 |  |  |     if (!s) | 
 |  |  |         return NULL ; | 
 |  |  |  | 
 |  |  |     len = strlen(s) + 1 ; | 
 |  |  |     t = (char*) malloc(len) ; | 
 |  |  |     if (t) { | 
 |  |  |         memcpy(t, s, len) ; | 
 |  |  |     } | 
 |  |  |     return t ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Remove blanks at the beginning and the end of a string. | 
 |  |  |   @param    str  String to parse and alter. | 
 |  |  |   @return   unsigned New size of the string. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static unsigned strstrip(char * s) | 
 |  |  | { | 
 |  |  |     char *last = NULL ; | 
 |  |  |     char *dest = s; | 
 |  |  |  | 
 |  |  |     if (s==NULL) return 0; | 
 |  |  |  | 
 |  |  |     last = s + strlen(s); | 
 |  |  |     while (isspace((int)*s) && *s) s++; | 
 |  |  |     while (last > s) { | 
 |  |  |         if (!isspace((int)*(last-1))) | 
 |  |  |             break ; | 
 |  |  |         last -- ; | 
 |  |  |     } | 
 |  |  |     *last = (char)0; | 
 |  |  |  | 
 |  |  |     memmove(dest,s,last - s + 1); | 
 |  |  |     return last - s; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Default error callback for iniparser: wraps `fprintf(stderr, ...)`. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static int default_error_callback(const char *format, ...) | 
 |  |  | { | 
 |  |  |   int ret; | 
 |  |  |   va_list argptr; | 
 |  |  |   va_start(argptr, format); | 
 |  |  |   ret = vfprintf(stderr, format, argptr); | 
 |  |  |   va_end(argptr); | 
 |  |  |   return ret; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static int (*iniparser_error_callback)(const char*, ...) = default_error_callback; | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Configure a function to receive the error messages. | 
 |  |  |   @param    errback  Function to call. | 
 |  |  |  | 
 |  |  |   By default, the error will be printed on stderr. If a null pointer is passed | 
 |  |  |   as errback the error callback will be switched back to default. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_set_error_callback(int (*errback)(const char *, ...)) | 
 |  |  | { | 
 |  |  |   if (errback) { | 
 |  |  |     iniparser_error_callback = errback; | 
 |  |  |   } else { | 
 |  |  |     iniparser_error_callback = default_error_callback; | 
 |  |  |   } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get number of sections in a dictionary | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @return   int Number of sections found in dictionary | 
 |  |  |  | 
 |  |  |   This function returns the number of sections found in a dictionary. | 
 |  |  |   The test to recognize sections is done on the string stored in the | 
 |  |  |   dictionary: a section name is given as "section" whereas a key is | 
 |  |  |   stored as "section:key", thus the test looks for entries that do not | 
 |  |  |   contain a colon. | 
 |  |  |  | 
 |  |  |   This clearly fails in the case a section name contains a colon, but | 
 |  |  |   this should simply be avoided. | 
 |  |  |  | 
 |  |  |   This function returns -1 in case of error. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getnsec(const dictionary * d) | 
 |  |  | { | 
 |  |  |     int i ; | 
 |  |  |     int nsec ; | 
 |  |  |  | 
 |  |  |     if (d==NULL) return -1 ; | 
 |  |  |     nsec=0 ; | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (strchr(d->key[i], ':')==NULL) { | 
 |  |  |             nsec ++ ; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     return nsec ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get name for section n in a dictionary. | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @param    n   Section number (from 0 to nsec-1). | 
 |  |  |   @return   Pointer to char string | 
 |  |  |  | 
 |  |  |   This function locates the n-th section in a dictionary and returns | 
 |  |  |   its name as a pointer to a string statically allocated inside the | 
 |  |  |   dictionary. Do not free or modify the returned string! | 
 |  |  |  | 
 |  |  |   This function returns NULL in case of error. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char * iniparser_getsecname(const dictionary * d, int n) | 
 |  |  | { | 
 |  |  |     int i ; | 
 |  |  |     int foundsec ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || n<0) return NULL ; | 
 |  |  |     foundsec=0 ; | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (strchr(d->key[i], ':')==NULL) { | 
 |  |  |             foundsec++ ; | 
 |  |  |             if (foundsec>n) | 
 |  |  |                 break ; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     if (foundsec<=n) { | 
 |  |  |         return NULL ; | 
 |  |  |     } | 
 |  |  |     return d->key[i] ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Dump a dictionary to an opened file pointer. | 
 |  |  |   @param    d   Dictionary to dump. | 
 |  |  |   @param    f   Opened file pointer to dump to. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function prints out the contents of a dictionary, one element by | 
 |  |  |   line, onto the provided file pointer. It is OK to specify @c stderr | 
 |  |  |   or @c stdout as output files. This function is meant for debugging | 
 |  |  |   purposes mostly. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_dump(const dictionary * d, FILE * f) | 
 |  |  | { | 
 |  |  |     int     i ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || f==NULL) return ; | 
 |  |  |     for (i=0 ; i<d->size ; i++) { | 
 |  |  |         if (d->key[i]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (d->val[i]!=NULL) { | 
 |  |  |             fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); | 
 |  |  |         } else { | 
 |  |  |             fprintf(f, "[%s]=UNDEF\n", d->key[i]); | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Save a dictionary to a loadable ini file | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    f   Opened file pointer to dump to | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function dumps a given dictionary into a loadable ini file. | 
 |  |  |   It is Ok to specify @c stderr or @c stdout as output files. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_dump_ini(const dictionary * d, FILE * f) | 
 |  |  | { | 
 |  |  |     int          i ; | 
 |  |  |     int          nsec ; | 
 |  |  |     const char * secname ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || f==NULL) return ; | 
 |  |  |  | 
 |  |  |     nsec = iniparser_getnsec(d); | 
 |  |  |     if (nsec<1) { | 
 |  |  |         /* No section in file: dump all keys as they are */ | 
 |  |  |         for (i=0 ; i<d->size ; i++) { | 
 |  |  |             if (d->key[i]==NULL) | 
 |  |  |                 continue ; | 
 |  |  |             fprintf(f, "%s = %s\n", d->key[i], d->val[i]); | 
 |  |  |         } | 
 |  |  |         return ; | 
 |  |  |     } | 
 |  |  |     for (i=0 ; i<nsec ; i++) { | 
 |  |  |         secname = iniparser_getsecname(d, i) ; | 
 |  |  |         iniparser_dumpsection_ini(d, secname, f); | 
 |  |  |     } | 
 |  |  |     fprintf(f, "\n"); | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Save a dictionary section to a loadable ini file | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    s   Section name of dictionary to dump | 
 |  |  |   @param    f   Opened file pointer to dump to | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function dumps a given section of a given dictionary into a loadable ini | 
 |  |  |   file.  It is Ok to specify @c stderr or @c stdout as output files. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f) | 
 |  |  | { | 
 |  |  |     int     j ; | 
 |  |  |     char    keym[ASCIILINESZ+1]; | 
 |  |  |     int     seclen ; | 
 |  |  |  | 
 |  |  |     if (d==NULL || f==NULL) return ; | 
 |  |  |     if (! iniparser_find_entry(d, s)) return ; | 
 |  |  |  | 
 |  |  |     seclen  = (int)strlen(s); | 
 |  |  |     fprintf(f, "\n[%s]\n", s); | 
 |  |  |     sprintf(keym, "%s:", s); | 
 |  |  |     for (j=0 ; j<d->size ; j++) { | 
 |  |  |         if (d->key[j]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (!strncmp(d->key[j], keym, seclen+1)) { | 
 |  |  |             fprintf(f, | 
 |  |  |                     "%-30s = %s\n", | 
 |  |  |                     d->key[j]+seclen+1, | 
 |  |  |                     d->val[j] ? d->val[j] : ""); | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     fprintf(f, "\n"); | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the number of keys in a section of a dictionary. | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @param    s   Section name of dictionary to examine | 
 |  |  |   @return   Number of keys in section | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getsecnkeys(const dictionary * d, const char * s) | 
 |  |  | { | 
 |  |  |     int     seclen, nkeys ; | 
 |  |  |     char    keym[ASCIILINESZ+1]; | 
 |  |  |     int j ; | 
 |  |  |  | 
 |  |  |     nkeys = 0; | 
 |  |  |  | 
 |  |  |     if (d==NULL) return nkeys; | 
 |  |  |     if (! iniparser_find_entry(d, s)) return nkeys; | 
 |  |  |  | 
 |  |  |     seclen  = (int)strlen(s); | 
 |  |  |     strlwc(s, keym, sizeof(keym)); | 
 |  |  |     keym[seclen] = ':'; | 
 |  |  |  | 
 |  |  |     for (j=0 ; j<d->size ; j++) { | 
 |  |  |         if (d->key[j]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (!strncmp(d->key[j], keym, seclen+1)) | 
 |  |  |             nkeys++; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return nkeys; | 
 |  |  |  | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the number of keys in a section of a dictionary. | 
 |  |  |   @param    d    Dictionary to examine | 
 |  |  |   @param    s    Section name of dictionary to examine | 
 |  |  |   @param    keys Already allocated array to store the keys in | 
 |  |  |   @return   The pointer passed as `keys` argument or NULL in case of error | 
 |  |  |  | 
 |  |  |   This function queries a dictionary and finds all keys in a given section. | 
 |  |  |   The keys argument should be an array of pointers which size has been | 
 |  |  |   determined by calling `iniparser_getsecnkeys` function prior to this one. | 
 |  |  |  | 
 |  |  |   Each pointer in the returned char pointer-to-pointer is pointing to | 
 |  |  |   a string allocated in the dictionary; do not free or modify them. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys) | 
 |  |  | { | 
 |  |  |     int i, j, seclen ; | 
 |  |  |     char keym[ASCIILINESZ+1]; | 
 |  |  |  | 
 |  |  |     if (d==NULL || keys==NULL) return NULL; | 
 |  |  |     if (! iniparser_find_entry(d, s)) return NULL; | 
 |  |  |  | 
 |  |  |     seclen  = (int)strlen(s); | 
 |  |  |     strlwc(s, keym, sizeof(keym)); | 
 |  |  |     keym[seclen] = ':'; | 
 |  |  |  | 
 |  |  |     i = 0; | 
 |  |  |  | 
 |  |  |     for (j=0 ; j<d->size ; j++) { | 
 |  |  |         if (d->key[j]==NULL) | 
 |  |  |             continue ; | 
 |  |  |         if (!strncmp(d->key[j], keym, seclen+1)) { | 
 |  |  |             keys[i] = d->key[j]; | 
 |  |  |             i++; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return keys; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key | 
 |  |  |   @param    d       Dictionary to search | 
 |  |  |   @param    key     Key string to look for | 
 |  |  |   @param    def     Default value to return if key not found. | 
 |  |  |   @return   pointer to statically allocated character string | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the pointer passed as 'def' is returned. | 
 |  |  |   The returned char pointer is pointing to a string allocated in | 
 |  |  |   the dictionary, do not free or modify it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char * iniparser_getstring(const dictionary * d, const char * key, const char * def) | 
 |  |  | { | 
 |  |  |     const char * lc_key ; | 
 |  |  |     const char * sval ; | 
 |  |  |     char tmp_str[ASCIILINESZ+1]; | 
 |  |  |  | 
 |  |  |     if (d==NULL || key==NULL) | 
 |  |  |         return def ; | 
 |  |  |  | 
 |  |  |     lc_key = strlwc(key, tmp_str, sizeof(tmp_str)); | 
 |  |  |     sval = dictionary_get(d, lc_key, def); | 
 |  |  |     return sval ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to an long int | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   long integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   Supported values for integers include the usual C notation | 
 |  |  |   so decimal, octal (starting with 0) and hexadecimal (starting with 0x) | 
 |  |  |   are supported. Examples: | 
 |  |  |  | 
 |  |  |   "42"      ->  42 | 
 |  |  |   "042"     ->  34 (octal -> decimal) | 
 |  |  |   "0x42"    ->  66 (hexa  -> decimal) | 
 |  |  |  | 
 |  |  |   Warning: the conversion may overflow in various ways. Conversion is | 
 |  |  |   totally outsourced to strtol(), see the associated man page for overflow | 
 |  |  |   handling. | 
 |  |  |  | 
 |  |  |   Credits: Thanks to A. Becker for suggesting strtol() | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound) | 
 |  |  | { | 
 |  |  |     const char * str ; | 
 |  |  |  | 
 |  |  |     str = iniparser_getstring(d, key, INI_INVALID_KEY); | 
 |  |  |     if (str==INI_INVALID_KEY) return notfound ; | 
 |  |  |     return strtol(str, NULL, 0); | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to an int | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   Supported values for integers include the usual C notation | 
 |  |  |   so decimal, octal (starting with 0) and hexadecimal (starting with 0x) | 
 |  |  |   are supported. Examples: | 
 |  |  |  | 
 |  |  |   "42"      ->  42 | 
 |  |  |   "042"     ->  34 (octal -> decimal) | 
 |  |  |   "0x42"    ->  66 (hexa  -> decimal) | 
 |  |  |  | 
 |  |  |   Warning: the conversion may overflow in various ways. Conversion is | 
 |  |  |   totally outsourced to strtol(), see the associated man page for overflow | 
 |  |  |   handling. | 
 |  |  |  | 
 |  |  |   Credits: Thanks to A. Becker for suggesting strtol() | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getint(const dictionary * d, const char * key, int notfound) | 
 |  |  | { | 
 |  |  |     return (int)iniparser_getlongint(d, key, notfound); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to a double | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   double | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | double iniparser_getdouble(const dictionary * d, const char * key, double notfound) | 
 |  |  | { | 
 |  |  |     const char * str ; | 
 |  |  |  | 
 |  |  |     str = iniparser_getstring(d, key, INI_INVALID_KEY); | 
 |  |  |     if (str==INI_INVALID_KEY) return notfound ; | 
 |  |  |     return atof(str); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to a boolean | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   A true boolean is found if one of the following is matched: | 
 |  |  |  | 
 |  |  |   - A string starting with 'y' | 
 |  |  |   - A string starting with 'Y' | 
 |  |  |   - A string starting with 't' | 
 |  |  |   - A string starting with 'T' | 
 |  |  |   - A string starting with '1' | 
 |  |  |  | 
 |  |  |   A false boolean is found if one of the following is matched: | 
 |  |  |  | 
 |  |  |   - A string starting with 'n' | 
 |  |  |   - A string starting with 'N' | 
 |  |  |   - A string starting with 'f' | 
 |  |  |   - A string starting with 'F' | 
 |  |  |   - A string starting with '0' | 
 |  |  |  | 
 |  |  |   The notfound value returned if no boolean is identified, does not | 
 |  |  |   necessarily have to be 0 or 1. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getboolean(const dictionary * d, const char * key, int notfound) | 
 |  |  | { | 
 |  |  |     int          ret ; | 
 |  |  |     const char * c ; | 
 |  |  |  | 
 |  |  |     c = iniparser_getstring(d, key, INI_INVALID_KEY); | 
 |  |  |     if (c==INI_INVALID_KEY) return notfound ; | 
 |  |  |     if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { | 
 |  |  |         ret = 1 ; | 
 |  |  |     } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { | 
 |  |  |         ret = 0 ; | 
 |  |  |     } else { | 
 |  |  |         ret = notfound ; | 
 |  |  |     } | 
 |  |  |     return ret; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Finds out if a given entry exists in a dictionary | 
 |  |  |   @param    ini     Dictionary to search | 
 |  |  |   @param    entry   Name of the entry to look for | 
 |  |  |   @return   integer 1 if entry exists, 0 otherwise | 
 |  |  |  | 
 |  |  |   Finds out if a given entry exists in the dictionary. Since sections | 
 |  |  |   are stored as keys with NULL associated values, this is the only way | 
 |  |  |   of querying for the presence of sections in a dictionary. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_find_entry(const dictionary * ini, const char * entry) | 
 |  |  | { | 
 |  |  |     int found=0 ; | 
 |  |  |     if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { | 
 |  |  |         found = 1 ; | 
 |  |  |     } | 
 |  |  |     return found ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Set an entry in a dictionary. | 
 |  |  |   @param    ini     Dictionary to modify. | 
 |  |  |   @param    entry   Entry to modify (entry name) | 
 |  |  |   @param    val     New value to associate to the entry. | 
 |  |  |   @return   int 0 if Ok, -1 otherwise. | 
 |  |  |  | 
 |  |  |   If the given entry can be found in the dictionary, it is modified to | 
 |  |  |   contain the provided value. If it cannot be found, the entry is created. | 
 |  |  |   It is Ok to set val to NULL. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_set(dictionary * ini, const char * entry, const char * val) | 
 |  |  | { | 
 |  |  |     char tmp_str[ASCIILINESZ+1]; | 
 |  |  |     return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete an entry in a dictionary | 
 |  |  |   @param    ini     Dictionary to modify | 
 |  |  |   @param    entry   Entry to delete (entry name) | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   If the given entry can be found, it is deleted from the dictionary. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_unset(dictionary * ini, const char * entry) | 
 |  |  | { | 
 |  |  |     char tmp_str[ASCIILINESZ+1]; | 
 |  |  |     dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str))); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Load a single line from an INI file | 
 |  |  |   @param    input_line  Input line, may be concatenated multi-line input | 
 |  |  |   @param    section     Output space to store section | 
 |  |  |   @param    key         Output space to store key | 
 |  |  |   @param    value       Output space to store value | 
 |  |  |   @return   line_status value | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | static line_status iniparser_line( | 
 |  |  |     const char * input_line, | 
 |  |  |     char * section, | 
 |  |  |     char * key, | 
 |  |  |     char * value) | 
 |  |  | { | 
 |  |  |     line_status sta ; | 
 |  |  |     char * line = NULL; | 
 |  |  |     size_t      len ; | 
 |  |  |  | 
 |  |  |     line = xstrdup(input_line); | 
 |  |  |     len = strstrip(line); | 
 |  |  |  | 
 |  |  |     sta = LINE_UNPROCESSED ; | 
 |  |  |     if (len<1) { | 
 |  |  |         /* Empty line */ | 
 |  |  |         sta = LINE_EMPTY ; | 
 |  |  |     } else if (line[0]=='#' || line[0]==';') { | 
 |  |  |         /* Comment line */ | 
 |  |  |         sta = LINE_COMMENT ; | 
 |  |  |     } else if (line[0]=='[' && line[len-1]==']') { | 
 |  |  |         /* Section name */ | 
 |  |  |         sscanf(line, "[%[^]]", section); | 
 |  |  |         strstrip(section); | 
 |  |  |         strlwc(section, section, len); | 
 |  |  |         sta = LINE_SECTION ; | 
 |  |  |     } else if (sscanf (line, "%[^=] = \"%[^\"]\"", key, value) == 2 | 
 |  |  |            ||  sscanf (line, "%[^=] = '%[^\']'",   key, value) == 2) { | 
 |  |  |         /* Usual key=value with quotes, with or without comments */ | 
 |  |  |         strstrip(key); | 
 |  |  |         strlwc(key, key, len); | 
 |  |  |         /* Don't strip spaces from values surrounded with quotes */ | 
 |  |  |         sta = LINE_VALUE ; | 
 |  |  |     } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { | 
 |  |  |         /* Usual key=value without quotes, with or without comments */ | 
 |  |  |         strstrip(key); | 
 |  |  |         strlwc(key, key, len); | 
 |  |  |         strstrip(value); | 
 |  |  |         /* | 
 |  |  |          * sscanf cannot handle '' or "" as empty values | 
 |  |  |          * this is done here | 
 |  |  |          */ | 
 |  |  |         if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { | 
 |  |  |             value[0]=0 ; | 
 |  |  |         } | 
 |  |  |         sta = LINE_VALUE ; | 
 |  |  |     } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 | 
 |  |  |            ||  sscanf(line, "%[^=] %[=]", key, value) == 2) { | 
 |  |  |         /* | 
 |  |  |          * Special cases: | 
 |  |  |          * key= | 
 |  |  |          * key=; | 
 |  |  |          * key=# | 
 |  |  |          */ | 
 |  |  |         strstrip(key); | 
 |  |  |         strlwc(key, key, len); | 
 |  |  |         value[0]=0 ; | 
 |  |  |         sta = LINE_VALUE ; | 
 |  |  |     } else { | 
 |  |  |         /* Generate syntax error */ | 
 |  |  |         sta = LINE_ERROR ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     free(line); | 
 |  |  |     return sta ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Parse an ini file and return an allocated dictionary object | 
 |  |  |   @param    ininame Name of the ini file to read. | 
 |  |  |   @return   Pointer to newly allocated dictionary | 
 |  |  |  | 
 |  |  |   This is the parser for ini files. This function is called, providing | 
 |  |  |   the name of the file to be read. It returns a dictionary object that | 
 |  |  |   should not be accessed directly, but through accessor functions | 
 |  |  |   instead. | 
 |  |  |  | 
 |  |  |   The returned dictionary must be freed using iniparser_freedict(). | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | dictionary * iniparser_load(const char * ininame) | 
 |  |  | { | 
 |  |  |     FILE * in ; | 
 |  |  |  | 
 |  |  |     char line    [ASCIILINESZ+1] ; | 
 |  |  |     char section [ASCIILINESZ+1] ; | 
 |  |  |     char key     [ASCIILINESZ+1] ; | 
 |  |  |     char tmp     [(ASCIILINESZ * 2) + 2] ; | 
 |  |  |     char val     [ASCIILINESZ+1] ; | 
 |  |  |  | 
 |  |  |     int  last=0 ; | 
 |  |  |     int  len ; | 
 |  |  |     int  lineno=0 ; | 
 |  |  |     int  errs=0; | 
 |  |  |     int  mem_err=0; | 
 |  |  |  | 
 |  |  |     dictionary * dict ; | 
 |  |  |  | 
 |  |  |     if ((in=fopen(ininame, "r"))==NULL) { | 
 |  |  |         iniparser_error_callback("iniparser: cannot open %s\n", ininame); | 
 |  |  |         return NULL ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     dict = dictionary_new(0) ; | 
 |  |  |     if (!dict) { | 
 |  |  |         fclose(in); | 
 |  |  |         return NULL ; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     memset(line,    0, ASCIILINESZ); | 
 |  |  |     memset(section, 0, ASCIILINESZ); | 
 |  |  |     memset(key,     0, ASCIILINESZ); | 
 |  |  |     memset(val,     0, ASCIILINESZ); | 
 |  |  |     last=0 ; | 
 |  |  |  | 
 |  |  |     while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { | 
 |  |  |         lineno++ ; | 
 |  |  |         len = (int)strlen(line)-1; | 
 |  |  |         if (len<=0) | 
 |  |  |             continue; | 
 |  |  |         /* Safety check against buffer overflows */ | 
 |  |  |         if (line[len]!='\n' && !feof(in)) { | 
 |  |  |             iniparser_error_callback( | 
 |  |  |               "iniparser: input line too long in %s (%d)\n", | 
 |  |  |               ininame, | 
 |  |  |               lineno); | 
 |  |  |             dictionary_del(dict); | 
 |  |  |             fclose(in); | 
 |  |  |             return NULL ; | 
 |  |  |         } | 
 |  |  |         /* Get rid of \n and spaces at end of line */ | 
 |  |  |         while ((len>=0) && | 
 |  |  |                 ((line[len]=='\n') || (isspace(line[len])))) { | 
 |  |  |             line[len]=0 ; | 
 |  |  |             len-- ; | 
 |  |  |         } | 
 |  |  |         if (len < 0) { /* Line was entirely \n and/or spaces */ | 
 |  |  |             len = 0; | 
 |  |  |         } | 
 |  |  |         /* Detect multi-line */ | 
 |  |  |         if (line[len]=='\\') { | 
 |  |  |             /* Multi-line value */ | 
 |  |  |             last=len ; | 
 |  |  |             continue ; | 
 |  |  |         } else { | 
 |  |  |             last=0 ; | 
 |  |  |         } | 
 |  |  |         switch (iniparser_line(line, section, key, val)) { | 
 |  |  |             case LINE_EMPTY: | 
 |  |  |             case LINE_COMMENT: | 
 |  |  |             break ; | 
 |  |  |  | 
 |  |  |             case LINE_SECTION: | 
 |  |  |             mem_err = dictionary_set(dict, section, NULL); | 
 |  |  |             break ; | 
 |  |  |  | 
 |  |  |             case LINE_VALUE: | 
 |  |  |             sprintf(tmp, "%s:%s", section, key); | 
 |  |  |             mem_err = dictionary_set(dict, tmp, val); | 
 |  |  |             break ; | 
 |  |  |  | 
 |  |  |             case LINE_ERROR: | 
 |  |  |             iniparser_error_callback( | 
 |  |  |               "iniparser: syntax error in %s (%d):\n-> %s\n", | 
 |  |  |               ininame, | 
 |  |  |               lineno, | 
 |  |  |               line); | 
 |  |  |             errs++ ; | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |             default: | 
 |  |  |             break ; | 
 |  |  |         } | 
 |  |  |         memset(line, 0, ASCIILINESZ); | 
 |  |  |         last=0; | 
 |  |  |         if (mem_err<0) { | 
 |  |  |             iniparser_error_callback("iniparser: memory allocation failure\n"); | 
 |  |  |             break ; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     if (errs) { | 
 |  |  |         dictionary_del(dict); | 
 |  |  |         dict = NULL ; | 
 |  |  |     } | 
 |  |  |     fclose(in); | 
 |  |  |     return dict ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Free all memory associated to an ini dictionary | 
 |  |  |   @param    d Dictionary to free | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Free all memory associated to an ini dictionary. | 
 |  |  |   It is mandatory to call this function before the dictionary object | 
 |  |  |   gets out of the current context. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_freedict(dictionary * d) | 
 |  |  | { | 
 |  |  |     dictionary_del(d); | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |    @file    iniparser.h | 
 |  |  |    @author  N. Devillard | 
 |  |  |    @brief   Parser for ini files.  | 
 |  |  |    @url     https://github.com/ndevilla/iniparser | 
 |  |  | */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | #ifndef _INIPARSER_H_ | 
 |  |  | #define _INIPARSER_H_ | 
 |  |  |  | 
 |  |  | /*--------------------------------------------------------------------------- | 
 |  |  |                                 Includes | 
 |  |  |  ---------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <stdlib.h> | 
 |  |  | #include <string.h> | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * The following #include is necessary on many Unixes but not Linux. | 
 |  |  |  * It is not needed for Windows platforms. | 
 |  |  |  * Uncomment it if needed. | 
 |  |  |  */ | 
 |  |  | /* #include <unistd.h> */ | 
 |  |  |  | 
 |  |  | #include "dictionary.h" | 
 |  |  |  | 
 |  |  | #ifdef __cplusplus | 
 |  |  | extern "C" { | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Configure a function to receive the error messages. | 
 |  |  |   @param    errback  Function to call. | 
 |  |  |  | 
 |  |  |   By default, the error will be printed on stderr. If a null pointer is passed | 
 |  |  |   as errback the error callback will be switched back to default. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | void iniparser_set_error_callback(int (*errback)(const char *, ...)); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get number of sections in a dictionary | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @return   int Number of sections found in dictionary | 
 |  |  |  | 
 |  |  |   This function returns the number of sections found in a dictionary. | 
 |  |  |   The test to recognize sections is done on the string stored in the | 
 |  |  |   dictionary: a section name is given as "section" whereas a key is | 
 |  |  |   stored as "section:key", thus the test looks for entries that do not | 
 |  |  |   contain a colon. | 
 |  |  |  | 
 |  |  |   This clearly fails in the case a section name contains a colon, but | 
 |  |  |   this should simply be avoided. | 
 |  |  |  | 
 |  |  |   This function returns -1 in case of error. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | int iniparser_getnsec(const dictionary * d); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get name for section n in a dictionary. | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @param    n   Section number (from 0 to nsec-1). | 
 |  |  |   @return   Pointer to char string | 
 |  |  |  | 
 |  |  |   This function locates the n-th section in a dictionary and returns | 
 |  |  |   its name as a pointer to a string statically allocated inside the | 
 |  |  |   dictionary. Do not free or modify the returned string! | 
 |  |  |  | 
 |  |  |   This function returns NULL in case of error. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | const char * iniparser_getsecname(const dictionary * d, int n); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Save a dictionary to a loadable ini file | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    f   Opened file pointer to dump to | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function dumps a given dictionary into a loadable ini file. | 
 |  |  |   It is Ok to specify @c stderr or @c stdout as output files. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | void iniparser_dump_ini(const dictionary * d, FILE * f); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Save a dictionary section to a loadable ini file | 
 |  |  |   @param    d   Dictionary to dump | 
 |  |  |   @param    s   Section name of dictionary to dump | 
 |  |  |   @param    f   Opened file pointer to dump to | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function dumps a given section of a given dictionary into a loadable ini | 
 |  |  |   file.  It is Ok to specify @c stderr or @c stdout as output files. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  |  | 
 |  |  | void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Dump a dictionary to an opened file pointer. | 
 |  |  |   @param    d   Dictionary to dump. | 
 |  |  |   @param    f   Opened file pointer to dump to. | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   This function prints out the contents of a dictionary, one element by | 
 |  |  |   line, onto the provided file pointer. It is OK to specify @c stderr | 
 |  |  |   or @c stdout as output files. This function is meant for debugging | 
 |  |  |   purposes mostly. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_dump(const dictionary * d, FILE * f); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the number of keys in a section of a dictionary. | 
 |  |  |   @param    d   Dictionary to examine | 
 |  |  |   @param    s   Section name of dictionary to examine | 
 |  |  |   @return   Number of keys in section | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getsecnkeys(const dictionary * d, const char * s); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the number of keys in a section of a dictionary. | 
 |  |  |   @param    d    Dictionary to examine | 
 |  |  |   @param    s    Section name of dictionary to examine | 
 |  |  |   @param    keys Already allocated array to store the keys in | 
 |  |  |   @return   The pointer passed as `keys` argument or NULL in case of error | 
 |  |  |  | 
 |  |  |   This function queries a dictionary and finds all keys in a given section. | 
 |  |  |   The keys argument should be an array of pointers which size has been | 
 |  |  |   determined by calling `iniparser_getsecnkeys` function prior to this one. | 
 |  |  |  | 
 |  |  |   Each pointer in the returned char pointer-to-pointer is pointing to | 
 |  |  |   a string allocated in the dictionary; do not free or modify them. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key | 
 |  |  |   @param    d       Dictionary to search | 
 |  |  |   @param    key     Key string to look for | 
 |  |  |   @param    def     Default value to return if key not found. | 
 |  |  |   @return   pointer to statically allocated character string | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the pointer passed as 'def' is returned. | 
 |  |  |   The returned char pointer is pointing to a string allocated in | 
 |  |  |   the dictionary, do not free or modify it. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | const char * iniparser_getstring(const dictionary * d, const char * key, const char * def); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to an int | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   Supported values for integers include the usual C notation | 
 |  |  |   so decimal, octal (starting with 0) and hexadecimal (starting with 0x) | 
 |  |  |   are supported. Examples: | 
 |  |  |  | 
 |  |  |   - "42"      ->  42 | 
 |  |  |   - "042"     ->  34 (octal -> decimal) | 
 |  |  |   - "0x42"    ->  66 (hexa  -> decimal) | 
 |  |  |  | 
 |  |  |   Warning: the conversion may overflow in various ways. Conversion is | 
 |  |  |   totally outsourced to strtol(), see the associated man page for overflow | 
 |  |  |   handling. | 
 |  |  |  | 
 |  |  |   Credits: Thanks to A. Becker for suggesting strtol() | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getint(const dictionary * d, const char * key, int notfound); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to an long int | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   Supported values for integers include the usual C notation | 
 |  |  |   so decimal, octal (starting with 0) and hexadecimal (starting with 0x) | 
 |  |  |   are supported. Examples: | 
 |  |  |  | 
 |  |  |   - "42"      ->  42 | 
 |  |  |   - "042"     ->  34 (octal -> decimal) | 
 |  |  |   - "0x42"    ->  66 (hexa  -> decimal) | 
 |  |  |  | 
 |  |  |   Warning: the conversion may overflow in various ways. Conversion is | 
 |  |  |   totally outsourced to strtol(), see the associated man page for overflow | 
 |  |  |   handling. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to a double | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   double | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | double iniparser_getdouble(const dictionary * d, const char * key, double notfound); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Get the string associated to a key, convert to a boolean | 
 |  |  |   @param    d Dictionary to search | 
 |  |  |   @param    key Key string to look for | 
 |  |  |   @param    notfound Value to return in case of error | 
 |  |  |   @return   integer | 
 |  |  |  | 
 |  |  |   This function queries a dictionary for a key. A key as read from an | 
 |  |  |   ini file is given as "section:key". If the key cannot be found, | 
 |  |  |   the notfound value is returned. | 
 |  |  |  | 
 |  |  |   A true boolean is found if one of the following is matched: | 
 |  |  |  | 
 |  |  |   - A string starting with 'y' | 
 |  |  |   - A string starting with 'Y' | 
 |  |  |   - A string starting with 't' | 
 |  |  |   - A string starting with 'T' | 
 |  |  |   - A string starting with '1' | 
 |  |  |  | 
 |  |  |   A false boolean is found if one of the following is matched: | 
 |  |  |  | 
 |  |  |   - A string starting with 'n' | 
 |  |  |   - A string starting with 'N' | 
 |  |  |   - A string starting with 'f' | 
 |  |  |   - A string starting with 'F' | 
 |  |  |   - A string starting with '0' | 
 |  |  |  | 
 |  |  |   The notfound value returned if no boolean is identified, does not | 
 |  |  |   necessarily have to be 0 or 1. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_getboolean(const dictionary * d, const char * key, int notfound); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Set an entry in a dictionary. | 
 |  |  |   @param    ini     Dictionary to modify. | 
 |  |  |   @param    entry   Entry to modify (entry name) | 
 |  |  |   @param    val     New value to associate to the entry. | 
 |  |  |   @return   int     0 if Ok, -1 otherwise. | 
 |  |  |  | 
 |  |  |   If the given entry can be found in the dictionary, it is modified to | 
 |  |  |   contain the provided value. If it cannot be found, the entry is created. | 
 |  |  |   It is Ok to set val to NULL. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_set(dictionary * ini, const char * entry, const char * val); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Delete an entry in a dictionary | 
 |  |  |   @param    ini     Dictionary to modify | 
 |  |  |   @param    entry   Entry to delete (entry name) | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   If the given entry can be found, it is deleted from the dictionary. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_unset(dictionary * ini, const char * entry); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Finds out if a given entry exists in a dictionary | 
 |  |  |   @param    ini     Dictionary to search | 
 |  |  |   @param    entry   Name of the entry to look for | 
 |  |  |   @return   integer 1 if entry exists, 0 otherwise | 
 |  |  |  | 
 |  |  |   Finds out if a given entry exists in the dictionary. Since sections | 
 |  |  |   are stored as keys with NULL associated values, this is the only way | 
 |  |  |   of querying for the presence of sections in a dictionary. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | int iniparser_find_entry(const dictionary * ini, const char * entry) ; | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Parse an ini file and return an allocated dictionary object | 
 |  |  |   @param    ininame Name of the ini file to read. | 
 |  |  |   @return   Pointer to newly allocated dictionary | 
 |  |  |  | 
 |  |  |   This is the parser for ini files. This function is called, providing | 
 |  |  |   the name of the file to be read. It returns a dictionary object that | 
 |  |  |   should not be accessed directly, but through accessor functions | 
 |  |  |   instead. | 
 |  |  |  | 
 |  |  |   The returned dictionary must be freed using iniparser_freedict(). | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | dictionary * iniparser_load(const char * ininame); | 
 |  |  |  | 
 |  |  | /*-------------------------------------------------------------------------*/ | 
 |  |  | /** | 
 |  |  |   @brief    Free all memory associated to an ini dictionary | 
 |  |  |   @param    d Dictionary to free | 
 |  |  |   @return   void | 
 |  |  |  | 
 |  |  |   Free all memory associated to an ini dictionary. | 
 |  |  |   It is mandatory to call this function before the dictionary object | 
 |  |  |   gets out of the current context. | 
 |  |  |  */ | 
 |  |  | /*--------------------------------------------------------------------------*/ | 
 |  |  | void iniparser_freedict(dictionary * d); | 
 |  |  |  | 
 |  |  | #ifdef __cplusplus | 
 |  |  | } | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | #endif | 
 
| New file | 
 |  |  | 
 |  |  | /********************************************************************************* | 
 |  |  |  *      Copyright:  (C) 2020 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  linux_list.h | 
 |  |  |  *    Description:  This file is copied from Linux kernel, which provide link list API. | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(08/09/2020) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "08/09/2020 02:24:34 AM" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  |  | 
 |  |  | #ifndef _LINUX_LIST_H | 
 |  |  | #define _LINUX_LIST_H | 
 |  |  |  | 
 |  |  | #include <linux/stddef.h> | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * container_of - cast a member of a structure out to the containing structure | 
 |  |  |  * @ptr:    the pointer to the member. | 
 |  |  |  * @type:   the type of the container struct this is embedded in. | 
 |  |  |  * @member: the name of the member within the struct. | 
 |  |  |  * | 
 |  |  |  */ | 
 |  |  | #undef offsetof | 
 |  |  | #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) | 
 |  |  | #define container_of(ptr, type, member) ({          \ | 
 |  |  |     const typeof( ((type *)0)->member ) *__mptr = (ptr);    \ | 
 |  |  |     (type *)( (char *)__mptr - offsetof(type,member) );}) | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * Architectures might want to move the poison pointer offset | 
 |  |  |  * into some well-recognized area such as 0xdead000000000000, | 
 |  |  |  * that is also not mappable by user-space exploits: | 
 |  |  |  */ | 
 |  |  | #ifdef CONFIG_ILLEGAL_POINTER_VALUE | 
 |  |  | # define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL) | 
 |  |  | #else | 
 |  |  | # define POISON_POINTER_DELTA 0 | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * These are non-NULL pointers that will result in page faults | 
 |  |  |  * under normal circumstances, used to verify that nobody uses | 
 |  |  |  * non-initialized list entries. | 
 |  |  |  */ | 
 |  |  | #define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA) | 
 |  |  | #define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA) | 
 |  |  |  | 
 |  |  | #ifndef ARCH_HAS_PREFETCH | 
 |  |  | #define ARCH_HAS_PREFETCH | 
 |  |  | static inline void prefetch(const void *x) {;} | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * Simple doubly linked list implementation. | 
 |  |  |  * | 
 |  |  |  * Some of the internal functions ("__xxx") are useful when | 
 |  |  |  * manipulating whole lists rather than single entries, as | 
 |  |  |  * sometimes we already know the next/prev entries and we can | 
 |  |  |  * generate better code by using them directly rather than | 
 |  |  |  * using the generic single-entry routines. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | struct list_head { | 
 |  |  |     struct list_head *next, *prev; | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | #define LIST_HEAD_INIT(name) { &(name), &(name) } | 
 |  |  |  | 
 |  |  | #define LIST_HEAD(name) \ | 
 |  |  |     struct list_head name = LIST_HEAD_INIT(name) | 
 |  |  |  | 
 |  |  | static inline void INIT_LIST_HEAD(struct list_head *list) | 
 |  |  | { | 
 |  |  |     list->next = list; | 
 |  |  |     list->prev = list; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * Insert a new entry between two known consecutive entries. | 
 |  |  |  * | 
 |  |  |  * This is only for internal list manipulation where we know | 
 |  |  |  * the prev/next entries already! | 
 |  |  |  */ | 
 |  |  | static inline void __list_add(struct list_head *new, | 
 |  |  |                   struct list_head *prev, | 
 |  |  |                   struct list_head *next) | 
 |  |  | { | 
 |  |  |     next->prev = new; | 
 |  |  |     new->next = next; | 
 |  |  |     new->prev = prev; | 
 |  |  |     prev->next = new; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_add - add a new entry | 
 |  |  |  * @new: new entry to be added | 
 |  |  |  * @head: list head to add it after | 
 |  |  |  * | 
 |  |  |  * Insert a new entry after the specified head. | 
 |  |  |  * This is good for implementing stacks. | 
 |  |  |  */ | 
 |  |  | static inline void list_add(struct list_head *new, struct list_head *head) | 
 |  |  | { | 
 |  |  |     __list_add(new, head, head->next); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_add_tail - add a new entry | 
 |  |  |  * @new: new entry to be added | 
 |  |  |  * @head: list head to add it before | 
 |  |  |  * | 
 |  |  |  * Insert a new entry before the specified head. | 
 |  |  |  * This is useful for implementing queues. | 
 |  |  |  */ | 
 |  |  | static inline void list_add_tail(struct list_head *new, struct list_head *head) | 
 |  |  | { | 
 |  |  |     __list_add(new, head->prev, head); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * Delete a list entry by making the prev/next entries | 
 |  |  |  * point to each other. | 
 |  |  |  * | 
 |  |  |  * This is only for internal list manipulation where we know | 
 |  |  |  * the prev/next entries already! | 
 |  |  |  */ | 
 |  |  | static inline void __list_del(struct list_head *prev, struct list_head *next) | 
 |  |  | { | 
 |  |  |     next->prev = prev; | 
 |  |  |     prev->next = next; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_del - deletes entry from list. | 
 |  |  |  * @entry: the element to delete from the list. | 
 |  |  |  * Note: list_empty() on entry does not return true after this, the entry is | 
 |  |  |  * in an undefined state. | 
 |  |  |  */ | 
 |  |  | static inline void list_del(struct list_head *entry) | 
 |  |  | { | 
 |  |  |     __list_del(entry->prev, entry->next); | 
 |  |  |     entry->next = LIST_POISON1; | 
 |  |  |     entry->prev = LIST_POISON2; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_replace - replace old entry by new one | 
 |  |  |  * @old : the element to be replaced | 
 |  |  |  * @new : the new element to insert | 
 |  |  |  * | 
 |  |  |  * If @old was empty, it will be overwritten. | 
 |  |  |  */ | 
 |  |  | static inline void list_replace(struct list_head *old, | 
 |  |  |                 struct list_head *new) | 
 |  |  | { | 
 |  |  |     new->next = old->next; | 
 |  |  |     new->next->prev = new; | 
 |  |  |     new->prev = old->prev; | 
 |  |  |     new->prev->next = new; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void list_replace_init(struct list_head *old, | 
 |  |  |                     struct list_head *new) | 
 |  |  | { | 
 |  |  |     list_replace(old, new); | 
 |  |  |     INIT_LIST_HEAD(old); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_del_init - deletes entry from list and reinitialize it. | 
 |  |  |  * @entry: the element to delete from the list. | 
 |  |  |  */ | 
 |  |  | static inline void list_del_init(struct list_head *entry) | 
 |  |  | { | 
 |  |  |     __list_del(entry->prev, entry->next); | 
 |  |  |     INIT_LIST_HEAD(entry); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_move - delete from one list and add as another's head | 
 |  |  |  * @list: the entry to move | 
 |  |  |  * @head: the head that will precede our entry | 
 |  |  |  */ | 
 |  |  | static inline void list_move(struct list_head *list, struct list_head *head) | 
 |  |  | { | 
 |  |  |     __list_del(list->prev, list->next); | 
 |  |  |     list_add(list, head); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_move_tail - delete from one list and add as another's tail | 
 |  |  |  * @list: the entry to move | 
 |  |  |  * @head: the head that will follow our entry | 
 |  |  |  */ | 
 |  |  | static inline void list_move_tail(struct list_head *list, | 
 |  |  |                   struct list_head *head) | 
 |  |  | { | 
 |  |  |     __list_del(list->prev, list->next); | 
 |  |  |     list_add_tail(list, head); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_is_last - tests whether @list is the last entry in list @head | 
 |  |  |  * @list: the entry to test | 
 |  |  |  * @head: the head of the list | 
 |  |  |  */ | 
 |  |  | static inline int list_is_last(const struct list_head *list, | 
 |  |  |                 const struct list_head *head) | 
 |  |  | { | 
 |  |  |     return list->next == head; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_empty - tests whether a list is empty | 
 |  |  |  * @head: the list to test. | 
 |  |  |  */ | 
 |  |  | static inline int list_empty(const struct list_head *head) | 
 |  |  | { | 
 |  |  |     return head->next == head; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_empty_careful - tests whether a list is empty and not being modified | 
 |  |  |  * @head: the list to test | 
 |  |  |  * | 
 |  |  |  * Description: | 
 |  |  |  * tests whether a list is empty _and_ checks that no other CPU might be | 
 |  |  |  * in the process of modifying either member (next or prev) | 
 |  |  |  * | 
 |  |  |  * NOTE: using list_empty_careful() without synchronization | 
 |  |  |  * can only be safe if the only activity that can happen | 
 |  |  |  * to the list entry is list_del_init(). Eg. it cannot be used | 
 |  |  |  * if another CPU could re-list_add() it. | 
 |  |  |  */ | 
 |  |  | static inline int list_empty_careful(const struct list_head *head) | 
 |  |  | { | 
 |  |  |     struct list_head *next = head->next; | 
 |  |  |     return (next == head) && (next == head->prev); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_is_singular - tests whether a list has just one entry. | 
 |  |  |  * @head: the list to test. | 
 |  |  |  */ | 
 |  |  | static inline int list_is_singular(const struct list_head *head) | 
 |  |  | { | 
 |  |  |     return !list_empty(head) && (head->next == head->prev); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void __list_cut_position(struct list_head *list, | 
 |  |  |         struct list_head *head, struct list_head *entry) | 
 |  |  | { | 
 |  |  |     struct list_head *new_first = entry->next; | 
 |  |  |     list->next = head->next; | 
 |  |  |     list->next->prev = list; | 
 |  |  |     list->prev = entry; | 
 |  |  |     entry->next = list; | 
 |  |  |     head->next = new_first; | 
 |  |  |     new_first->prev = head; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_cut_position - cut a list into two | 
 |  |  |  * @list: a new list to add all removed entries | 
 |  |  |  * @head: a list with entries | 
 |  |  |  * @entry: an entry within head, could be the head itself | 
 |  |  |  *    and if so we won't cut the list | 
 |  |  |  * | 
 |  |  |  * This helper moves the initial part of @head, up to and | 
 |  |  |  * including @entry, from @head to @list. You should | 
 |  |  |  * pass on @entry an element you know is on @head. @list | 
 |  |  |  * should be an empty list or a list you do not care about | 
 |  |  |  * losing its data. | 
 |  |  |  * | 
 |  |  |  */ | 
 |  |  | static inline void list_cut_position(struct list_head *list, | 
 |  |  |         struct list_head *head, struct list_head *entry) | 
 |  |  | { | 
 |  |  |     if (list_empty(head)) | 
 |  |  |         return; | 
 |  |  |     if (list_is_singular(head) && | 
 |  |  |         (head->next != entry && head != entry)) | 
 |  |  |         return; | 
 |  |  |     if (entry == head) | 
 |  |  |         INIT_LIST_HEAD(list); | 
 |  |  |     else | 
 |  |  |         __list_cut_position(list, head, entry); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void __list_splice(const struct list_head *list, | 
 |  |  |                  struct list_head *prev, | 
 |  |  |                  struct list_head *next) | 
 |  |  | { | 
 |  |  |     struct list_head *first = list->next; | 
 |  |  |     struct list_head *last = list->prev; | 
 |  |  |  | 
 |  |  |     first->prev = prev; | 
 |  |  |     prev->next = first; | 
 |  |  |  | 
 |  |  |     last->next = next; | 
 |  |  |     next->prev = last; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_splice - join two lists, this is designed for stacks | 
 |  |  |  * @list: the new list to add. | 
 |  |  |  * @head: the place to add it in the first list. | 
 |  |  |  */ | 
 |  |  | static inline void list_splice(const struct list_head *list, | 
 |  |  |                 struct list_head *head) | 
 |  |  | { | 
 |  |  |     if (!list_empty(list)) | 
 |  |  |         __list_splice(list, head, head->next); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_splice_tail - join two lists, each list being a queue | 
 |  |  |  * @list: the new list to add. | 
 |  |  |  * @head: the place to add it in the first list. | 
 |  |  |  */ | 
 |  |  | static inline void list_splice_tail(struct list_head *list, | 
 |  |  |                 struct list_head *head) | 
 |  |  | { | 
 |  |  |     if (!list_empty(list)) | 
 |  |  |         __list_splice(list, head->prev, head); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_splice_init - join two lists and reinitialise the emptied list. | 
 |  |  |  * @list: the new list to add. | 
 |  |  |  * @head: the place to add it in the first list. | 
 |  |  |  * | 
 |  |  |  * The list at @list is reinitialised | 
 |  |  |  */ | 
 |  |  | static inline void list_splice_init(struct list_head *list, | 
 |  |  |                     struct list_head *head) | 
 |  |  | { | 
 |  |  |     if (!list_empty(list)) { | 
 |  |  |         __list_splice(list, head, head->next); | 
 |  |  |         INIT_LIST_HEAD(list); | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_splice_tail_init - join two lists and reinitialise the emptied list | 
 |  |  |  * @list: the new list to add. | 
 |  |  |  * @head: the place to add it in the first list. | 
 |  |  |  * | 
 |  |  |  * Each of the lists is a queue. | 
 |  |  |  * The list at @list is reinitialised | 
 |  |  |  */ | 
 |  |  | static inline void list_splice_tail_init(struct list_head *list, | 
 |  |  |                      struct list_head *head) | 
 |  |  | { | 
 |  |  |     if (!list_empty(list)) { | 
 |  |  |         __list_splice(list, head->prev, head); | 
 |  |  |         INIT_LIST_HEAD(list); | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_entry - get the struct for this entry | 
 |  |  |  * @ptr:    the &struct list_head pointer. | 
 |  |  |  * @type:    the type of the struct this is embedded in. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  */ | 
 |  |  | #define list_entry(ptr, type, member) \ | 
 |  |  |     container_of(ptr, type, member) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_first_entry - get the first element from a list | 
 |  |  |  * @ptr:    the list head to take the element from. | 
 |  |  |  * @type:    the type of the struct this is embedded in. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Note, that list is expected to be not empty. | 
 |  |  |  */ | 
 |  |  | #define list_first_entry(ptr, type, member) \ | 
 |  |  |     list_entry((ptr)->next, type, member) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each    -    iterate over a list | 
 |  |  |  * @pos:    the &struct list_head to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  */ | 
 |  |  | #define list_for_each(pos, head) \ | 
 |  |  |     for (pos = (head)->next; prefetch(pos->next), pos != (head); \ | 
 |  |  |         pos = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * __list_for_each    -    iterate over a list | 
 |  |  |  * @pos:    the &struct list_head to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * | 
 |  |  |  * This variant differs from list_for_each() in that it's the | 
 |  |  |  * simplest possible list iteration code, no prefetching is done. | 
 |  |  |  * Use this for code that knows the list to be very short (empty | 
 |  |  |  * or 1 entry) most of the time. | 
 |  |  |  */ | 
 |  |  | #define __list_for_each(pos, head) \ | 
 |  |  |     for (pos = (head)->next; pos != (head); pos = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_prev    -    iterate over a list backwards | 
 |  |  |  * @pos:    the &struct list_head to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_prev(pos, head) \ | 
 |  |  |     for (pos = (head)->prev; prefetch(pos->prev), pos != (head); \ | 
 |  |  |         pos = pos->prev) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_safe - iterate over a list safe against removal of list entry | 
 |  |  |  * @pos:    the &struct list_head to use as a loop cursor. | 
 |  |  |  * @n:        another &struct list_head to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_safe(pos, n, head) \ | 
 |  |  |     for (pos = (head)->next, n = pos->next; pos != (head); \ | 
 |  |  |         pos = n, n = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry | 
 |  |  |  * @pos:    the &struct list_head to use as a loop cursor. | 
 |  |  |  * @n:        another &struct list_head to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_prev_safe(pos, n, head) \ | 
 |  |  |     for (pos = (head)->prev, n = pos->prev; \ | 
 |  |  |          prefetch(pos->prev), pos != (head); \ | 
 |  |  |          pos = n, n = pos->prev) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry    -    iterate over list of given type | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry(pos, head, member)                \ | 
 |  |  |     for (pos = list_entry((head)->next, typeof(*pos), member);    \ | 
 |  |  |          prefetch(pos->member.next), &pos->member != (head);    \ | 
 |  |  |          pos = list_entry(pos->member.next, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_reverse - iterate backwards over list of given type. | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_reverse(pos, head, member)            \ | 
 |  |  |     for (pos = list_entry((head)->prev, typeof(*pos), member);    \ | 
 |  |  |          prefetch(pos->member.prev), &pos->member != (head);    \ | 
 |  |  |          pos = list_entry(pos->member.prev, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() | 
 |  |  |  * @pos:    the type * to use as a start point | 
 |  |  |  * @head:    the head of the list | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). | 
 |  |  |  */ | 
 |  |  | #define list_prepare_entry(pos, head, member) \ | 
 |  |  |     ((pos) ? : list_entry(head, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_continue - continue iteration over list of given type | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Continue to iterate over list of given type, continuing after | 
 |  |  |  * the current position. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_continue(pos, head, member)         \ | 
 |  |  |     for (pos = list_entry(pos->member.next, typeof(*pos), member);    \ | 
 |  |  |          prefetch(pos->member.next), &pos->member != (head);    \ | 
 |  |  |          pos = list_entry(pos->member.next, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_continue_reverse - iterate backwards from the given point | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Start to iterate over list of given type backwards, continuing after | 
 |  |  |  * the current position. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_continue_reverse(pos, head, member)        \ | 
 |  |  |     for (pos = list_entry(pos->member.prev, typeof(*pos), member);    \ | 
 |  |  |          prefetch(pos->member.prev), &pos->member != (head);    \ | 
 |  |  |          pos = list_entry(pos->member.prev, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_from - iterate over list of given type from the current point | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Iterate over list of given type, continuing from current position. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_from(pos, head, member)            \ | 
 |  |  |     for (; prefetch(pos->member.next), &pos->member != (head);    \ | 
 |  |  |          pos = list_entry(pos->member.next, typeof(*pos), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @n:        another type * to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_safe(pos, n, head, member)            \ | 
 |  |  |     for (pos = list_entry((head)->next, typeof(*pos), member),    \ | 
 |  |  |         n = list_entry(pos->member.next, typeof(*pos), member);    \ | 
 |  |  |          &pos->member != (head);                    \ | 
 |  |  |          pos = n, n = list_entry(n->member.next, typeof(*n), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_safe_continue | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @n:        another type * to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Iterate over list of given type, continuing after current point, | 
 |  |  |  * safe against removal of list entry. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_safe_continue(pos, n, head, member)         \ | 
 |  |  |     for (pos = list_entry(pos->member.next, typeof(*pos), member),        \ | 
 |  |  |         n = list_entry(pos->member.next, typeof(*pos), member);        \ | 
 |  |  |          &pos->member != (head);                        \ | 
 |  |  |          pos = n, n = list_entry(n->member.next, typeof(*n), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_safe_from | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @n:        another type * to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Iterate over list of given type from current point, safe against | 
 |  |  |  * removal of list entry. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_safe_from(pos, n, head, member)            \ | 
 |  |  |     for (n = list_entry(pos->member.next, typeof(*pos), member);        \ | 
 |  |  |          &pos->member != (head);                        \ | 
 |  |  |          pos = n, n = list_entry(n->member.next, typeof(*n), member)) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * list_for_each_entry_safe_reverse | 
 |  |  |  * @pos:    the type * to use as a loop cursor. | 
 |  |  |  * @n:        another type * to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the list_struct within the struct. | 
 |  |  |  * | 
 |  |  |  * Iterate backwards over list of given type, safe against removal | 
 |  |  |  * of list entry. | 
 |  |  |  */ | 
 |  |  | #define list_for_each_entry_safe_reverse(pos, n, head, member)        \ | 
 |  |  |     for (pos = list_entry((head)->prev, typeof(*pos), member),    \ | 
 |  |  |         n = list_entry(pos->member.prev, typeof(*pos), member);    \ | 
 |  |  |          &pos->member != (head);                    \ | 
 |  |  |          pos = n, n = list_entry(n->member.prev, typeof(*n), member)) | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * Double linked lists with a single pointer list head. | 
 |  |  |  * Mostly useful for hash tables where the two pointer list head is | 
 |  |  |  * too wasteful. | 
 |  |  |  * You lose the ability to access the tail in O(1). | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | struct hlist_head { | 
 |  |  |     struct hlist_node *first; | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | struct hlist_node { | 
 |  |  |     struct hlist_node *next, **pprev; | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | #define HLIST_HEAD_INIT { .first = NULL } | 
 |  |  | #define HLIST_HEAD(name) struct hlist_head name = {  .first = NULL } | 
 |  |  | #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) | 
 |  |  | static inline void INIT_HLIST_NODE(struct hlist_node *h) | 
 |  |  | { | 
 |  |  |     h->next = NULL; | 
 |  |  |     h->pprev = NULL; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline int hlist_unhashed(const struct hlist_node *h) | 
 |  |  | { | 
 |  |  |     return !h->pprev; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline int hlist_empty(const struct hlist_head *h) | 
 |  |  | { | 
 |  |  |     return !h->first; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void __hlist_del(struct hlist_node *n) | 
 |  |  | { | 
 |  |  |     struct hlist_node *next = n->next; | 
 |  |  |     struct hlist_node **pprev = n->pprev; | 
 |  |  |     *pprev = next; | 
 |  |  |     if (next) | 
 |  |  |         next->pprev = pprev; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void hlist_del(struct hlist_node *n) | 
 |  |  | { | 
 |  |  |     __hlist_del(n); | 
 |  |  |     n->next = LIST_POISON1; | 
 |  |  |     n->pprev = LIST_POISON2; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void hlist_del_init(struct hlist_node *n) | 
 |  |  | { | 
 |  |  |     if (!hlist_unhashed(n)) { | 
 |  |  |         __hlist_del(n); | 
 |  |  |         INIT_HLIST_NODE(n); | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) | 
 |  |  | { | 
 |  |  |     struct hlist_node *first = h->first; | 
 |  |  |     n->next = first; | 
 |  |  |     if (first) | 
 |  |  |         first->pprev = &n->next; | 
 |  |  |     h->first = n; | 
 |  |  |     n->pprev = &h->first; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* next must be != NULL */ | 
 |  |  | static inline void hlist_add_before(struct hlist_node *n, | 
 |  |  |                     struct hlist_node *next) | 
 |  |  | { | 
 |  |  |     n->pprev = next->pprev; | 
 |  |  |     n->next = next; | 
 |  |  |     next->pprev = &n->next; | 
 |  |  |     *(n->pprev) = n; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static inline void hlist_add_after(struct hlist_node *n, | 
 |  |  |                     struct hlist_node *next) | 
 |  |  | { | 
 |  |  |     next->next = n->next; | 
 |  |  |     n->next = next; | 
 |  |  |     next->pprev = &n->next; | 
 |  |  |  | 
 |  |  |     if(next->next) | 
 |  |  |         next->next->pprev  = &next->next; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | #define hlist_entry(ptr, type, member) container_of(ptr,type,member) | 
 |  |  |  | 
 |  |  | #define hlist_for_each(pos, head) \ | 
 |  |  |     for (pos = (head)->first; pos && ({ prefetch(pos->next); 1; }); \ | 
 |  |  |          pos = pos->next) | 
 |  |  |  | 
 |  |  | #define hlist_for_each_safe(pos, n, head) \ | 
 |  |  |     for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ | 
 |  |  |          pos = n) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * hlist_for_each_entry    - iterate over list of given type | 
 |  |  |  * @tpos:    the type * to use as a loop cursor. | 
 |  |  |  * @pos:    the &struct hlist_node to use as a loop cursor. | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the hlist_node within the struct. | 
 |  |  |  */ | 
 |  |  | #define hlist_for_each_entry(tpos, pos, head, member)             \ | 
 |  |  |     for (pos = (head)->first;                     \ | 
 |  |  |          pos && ({ prefetch(pos->next); 1;}) &&             \ | 
 |  |  |         ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | 
 |  |  |          pos = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * hlist_for_each_entry_continue - iterate over a hlist continuing after current point | 
 |  |  |  * @tpos:    the type * to use as a loop cursor. | 
 |  |  |  * @pos:    the &struct hlist_node to use as a loop cursor. | 
 |  |  |  * @member:    the name of the hlist_node within the struct. | 
 |  |  |  */ | 
 |  |  | #define hlist_for_each_entry_continue(tpos, pos, member)         \ | 
 |  |  |     for (pos = (pos)->next;                         \ | 
 |  |  |          pos && ({ prefetch(pos->next); 1;}) &&             \ | 
 |  |  |         ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | 
 |  |  |          pos = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * hlist_for_each_entry_from - iterate over a hlist continuing from current point | 
 |  |  |  * @tpos:    the type * to use as a loop cursor. | 
 |  |  |  * @pos:    the &struct hlist_node to use as a loop cursor. | 
 |  |  |  * @member:    the name of the hlist_node within the struct. | 
 |  |  |  */ | 
 |  |  | #define hlist_for_each_entry_from(tpos, pos, member)             \ | 
 |  |  |     for (; pos && ({ prefetch(pos->next); 1;}) &&             \ | 
 |  |  |         ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | 
 |  |  |          pos = pos->next) | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry | 
 |  |  |  * @tpos:    the type * to use as a loop cursor. | 
 |  |  |  * @pos:    the &struct hlist_node to use as a loop cursor. | 
 |  |  |  * @n:        another &struct hlist_node to use as temporary storage | 
 |  |  |  * @head:    the head for your list. | 
 |  |  |  * @member:    the name of the hlist_node within the struct. | 
 |  |  |  */ | 
 |  |  | #define hlist_for_each_entry_safe(tpos, pos, n, head, member)         \ | 
 |  |  |     for (pos = (head)->first;                     \ | 
 |  |  |          pos && ({ n = pos->next; 1; }) &&                 \ | 
 |  |  |         ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ | 
 |  |  |          pos = n) | 
 |  |  |  | 
 |  |  |  | 
 |  |  | #endif | 
 |  |  |  | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <errno.h> | 
 |  |  | #include <stdlib.h> | 
 |  |  | #include <stdarg.h> | 
 |  |  | #include <string.h> | 
 |  |  | #include <time.h> | 
 |  |  | #include <unistd.h> | 
 |  |  | #include <sys/types.h> | 
 |  |  | #include <sys/time.h> | 
 |  |  | #include <pthread.h> | 
 |  |  |  | 
 |  |  | #include "logger.h" | 
 |  |  |  | 
 |  |  | typedef void (*log_LockFn)(void *udata, int lock); | 
 |  |  |  | 
 |  |  | static struct { | 
 |  |  |     char        file[32]; /* logger file name */ | 
 |  |  |     FILE       *fp;       /* logger file pointer */ | 
 |  |  |     long        size;     /* logger file max size */ | 
 |  |  |     int         level;    /* logger level */ | 
 |  |  |     log_LockFn  lockfn;   /* lock function */ | 
 |  |  |     void       *udata;    /* lock data */ | 
 |  |  | } L; | 
 |  |  |  | 
 |  |  | static const char *level_names[] = { | 
 |  |  |     "FATAL", | 
 |  |  |     "ERROR", | 
 |  |  |     "WARN", | 
 |  |  |     "INFO", | 
 |  |  |     "DEBUG", | 
 |  |  |     "TRACE" | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | static const char *level_colors[] = { | 
 |  |  |     "\x1b[35m", | 
 |  |  |     "\x1b[31m", | 
 |  |  |     "\x1b[33m", | 
 |  |  |     "\x1b[32m", | 
 |  |  |     "\x1b[36m", | 
 |  |  |     "\x1b[94m" | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | static inline void time_to_str(char *buf) | 
 |  |  | { | 
 |  |  |     struct timeval   tv; | 
 |  |  |     struct tm       *tm; | 
 |  |  |     int              len; | 
 |  |  |  | 
 |  |  |     gettimeofday(&tv, NULL); | 
 |  |  |     tm = localtime(&tv.tv_sec); | 
 |  |  |  | 
 |  |  |     len = sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d.%06d ", | 
 |  |  |             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, | 
 |  |  |             tm->tm_hour, tm->tm_min, tm->tm_sec, (int)tv.tv_usec); | 
 |  |  |  | 
 |  |  |     buf[len] = '\0'; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static void mutex_lock(void *udata, int lock) | 
 |  |  | { | 
 |  |  |     int              err; | 
 |  |  |     pthread_mutex_t *l = (pthread_mutex_t *) udata; | 
 |  |  |  | 
 |  |  |     if (lock) | 
 |  |  |     { | 
 |  |  |         if ( (err = pthread_mutex_lock(l)) != 0 ) | 
 |  |  |             log_error("Unable to lock log lock: %s", strerror(err)); | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         if ( (err = pthread_mutex_unlock(l) != 0) ) | 
 |  |  |             log_error("Unable to unlock log lock: %s", strerror(err)); | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int log_open(char *fname, int level, int size, int lock) | 
 |  |  | { | 
 |  |  |     FILE            *fp; | 
 |  |  |  | 
 |  |  |     L.level = level; | 
 |  |  |     L.size = size*1024; | 
 |  |  |  | 
 |  |  |     if( !fname || !strcmp(fname, "console") || !strcmp(fname, "stderr") ) | 
 |  |  |     { | 
 |  |  |         strcpy(L.file, "console"); | 
 |  |  |         L.fp = stderr; | 
 |  |  |         L.size = 0; /* console don't need rollback */ | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         if ( !(fp = fopen(fname, "a+")) ) | 
 |  |  |         { | 
 |  |  |             fprintf(stderr, "%s() failed: %s\n", __func__, strerror(errno)); | 
 |  |  |             return -2; | 
 |  |  |         } | 
 |  |  |         L.fp = fp; | 
 |  |  |         strncpy(L.file, fname, sizeof(L.file)); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     if( lock ) | 
 |  |  |     { | 
 |  |  |         static pthread_mutex_t     log_lock; | 
 |  |  |  | 
 |  |  |         pthread_mutex_init(&log_lock, NULL); | 
 |  |  |         L.udata = (void *)&log_lock; | 
 |  |  |         L.lockfn = mutex_lock; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     fprintf(L.fp, "\n"); | 
 |  |  |     log_info("logger system(%s) start: file:\"%s\", level:%s, maxsize:%luKiB\n\n", | 
 |  |  |             LOG_VERSION, L.file, level_names[level], size); | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | void log_close(void) | 
 |  |  | { | 
 |  |  |     if( L.fp && L.fp!=stderr ) | 
 |  |  |         fclose(L.fp); | 
 |  |  |  | 
 |  |  |     if (L.udata ) | 
 |  |  |         pthread_mutex_destroy( L.udata); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static void log_rollback(void) | 
 |  |  | { | 
 |  |  |     char       cmd[128]={0}; | 
 |  |  |     long       fsize; | 
 |  |  |  | 
 |  |  |     /* don't need rollback */ | 
 |  |  |     if(L.size <= 0 ) | 
 |  |  |         return ; | 
 |  |  |  | 
 |  |  |     fsize = ftell(L.fp); | 
 |  |  |     if( fsize < L.size ) | 
 |  |  |         return ; | 
 |  |  |  | 
 |  |  |     /* backup current log file  */ | 
 |  |  |     snprintf(cmd, sizeof(cmd), "cp %s %s.bak", L.file, L.file); | 
 |  |  |     system(cmd); | 
 |  |  |  | 
 |  |  |     /* rollback file */ | 
 |  |  |     fseek(L.fp, 0, SEEK_SET); | 
 |  |  |     truncate(L.file, 0); | 
 |  |  |  | 
 |  |  |     fprintf(L.fp, "\n"); | 
 |  |  |     log_info("logger system(%s) rollback: file:\"%s\", level:%s, maxsize:%luKiB\n\n", | 
 |  |  |             LOG_VERSION, L.file, level_names[L.level], L.size/1024); | 
 |  |  |  | 
 |  |  |     return ; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | void _log_write(int level, const char *file, int line, const char *fmt, ...) | 
 |  |  | { | 
 |  |  |     va_list    args; | 
 |  |  |     char       time_string[100]; | 
 |  |  |  | 
 |  |  |     if ( !L.fp || level>L.level ) | 
 |  |  |         return; | 
 |  |  |  | 
 |  |  |     /* Acquire lock */ | 
 |  |  |     if ( L.lockfn ) | 
 |  |  |         L.lockfn(L.udata, 1); | 
 |  |  |  | 
 |  |  |     log_rollback(); | 
 |  |  |  | 
 |  |  |     /* check and rollback file */ | 
 |  |  |     time_to_str(time_string); | 
 |  |  |  | 
 |  |  |     /* Log to stderr */ | 
 |  |  |     if ( L.fp == stderr ) | 
 |  |  |     { | 
 |  |  |         fprintf(L.fp, "%s %s %-5s\x1b[0m \x1b[90m%s:%03d:\x1b[0m ", | 
 |  |  |                 time_string, level_colors[level], level_names[level], file, line); | 
 |  |  |     } | 
 |  |  |     else /* Log to file */ | 
 |  |  |     { | 
 |  |  |         fprintf(L.fp, "%s %-5s %s:%03d: ", time_string, level_names[level], file, line); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     va_start(args, fmt); | 
 |  |  |     vfprintf(L.fp, fmt, args); | 
 |  |  |     va_end(args); | 
 |  |  |  | 
 |  |  |     fflush(L.fp); | 
 |  |  |  | 
 |  |  |     /* Release lock */ | 
 |  |  |     if ( L.lockfn ) | 
 |  |  |         L.lockfn(L.udata, 0); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | #define LINELEN 81 | 
 |  |  | #define CHARS_PER_LINE 16 | 
 |  |  | static char *print_char = | 
 |  |  | "                " | 
 |  |  | "                " | 
 |  |  | " !\"#$%&'()*+,-./" | 
 |  |  | "0123456789:;<=>?" | 
 |  |  | "@ABCDEFGHIJKLMNO" | 
 |  |  | "PQRSTUVWXYZ[\\]^_" | 
 |  |  | "`abcdefghijklmno" | 
 |  |  | "pqrstuvwxyz{|}~ " | 
 |  |  | "                " | 
 |  |  | "                " | 
 |  |  | " ???????????????" | 
 |  |  | "????????????????" | 
 |  |  | "????????????????" | 
 |  |  | "????????????????" | 
 |  |  | "????????????????" | 
 |  |  | "????????????????"; | 
 |  |  |  | 
 |  |  | void log_dump(int level, const char *prompt, char *buf, size_t len) | 
 |  |  | { | 
 |  |  |     int rc; | 
 |  |  |     int idx; | 
 |  |  |     char prn[LINELEN]; | 
 |  |  |     char lit[CHARS_PER_LINE + 2]; | 
 |  |  |     char hc[4]; | 
 |  |  |     short line_done = 1; | 
 |  |  |  | 
 |  |  |     if (!L.fp || level>L.level) | 
 |  |  |         return; | 
 |  |  |  | 
 |  |  |     if( prompt ) | 
 |  |  |         _log_write(level, __FILE__, __LINE__, "%s", prompt); | 
 |  |  |  | 
 |  |  |     rc = len; | 
 |  |  |     idx = 0; | 
 |  |  |     lit[CHARS_PER_LINE] = '\0'; | 
 |  |  |  | 
 |  |  |     while (rc > 0) | 
 |  |  |     { | 
 |  |  |         if (line_done) | 
 |  |  |             snprintf(prn, LINELEN, "%08X: ", idx); | 
 |  |  |  | 
 |  |  |         do | 
 |  |  |         { | 
 |  |  |             unsigned char c = buf[idx]; | 
 |  |  |             snprintf(hc, 4, "%02X ", c); | 
 |  |  |             strncat(prn, hc, LINELEN); | 
 |  |  |  | 
 |  |  |             lit[idx % CHARS_PER_LINE] = print_char[c]; | 
 |  |  |         } | 
 |  |  |         while (--rc > 0 && (++idx % CHARS_PER_LINE != 0)); | 
 |  |  |  | 
 |  |  |         line_done = (idx % CHARS_PER_LINE) == 0; | 
 |  |  |         if (line_done) | 
 |  |  |         { | 
 |  |  |             if (L.fp) | 
 |  |  |                 fprintf(L.fp, "%s  %s\n", prn, lit); | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if (!line_done) | 
 |  |  |     { | 
 |  |  |         int ldx = idx % CHARS_PER_LINE; | 
 |  |  |         lit[ldx++] = print_char[(int)buf[idx]]; | 
 |  |  |         lit[ldx] = '\0'; | 
 |  |  |  | 
 |  |  |         while ((++idx % CHARS_PER_LINE) != 0) | 
 |  |  |             strncat(prn, "   ", sizeof(prn)-strlen(prn)); | 
 |  |  |  | 
 |  |  |         if (L.fp) | 
 |  |  |             fprintf(L.fp, "%s  %s\n", prn, lit); | 
 |  |  |  | 
 |  |  |     } | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /* | 
 |  |  |  * Copyright (c) 2022 Guo Wenxue | 
 |  |  |  * Author: Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  * | 
 |  |  |  * This library is free software; you can redistribute it and/or modify it | 
 |  |  |  * under the terms of the GPL license. | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #ifndef  _LOGGER_H_ | 
 |  |  | #define  _LOGGER_H_ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <stdarg.h> | 
 |  |  |  | 
 |  |  | #define LOG_VERSION "v0.1" | 
 |  |  |  | 
 |  |  | /* log level */ | 
 |  |  | enum { | 
 |  |  |     LOG_LEVEL_FATAL, | 
 |  |  |     LOG_LEVEL_ERROR, | 
 |  |  |     LOG_LEVEL_WARN, | 
 |  |  |     LOG_LEVEL_INFO, | 
 |  |  |     LOG_LEVEL_DEBUG, | 
 |  |  |     LOG_LEVEL_TRACE, | 
 |  |  |     LOG_LEVEL_MAX | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | enum { | 
 |  |  |     LOG_LOCK_DISABLE, /* disable lock */ | 
 |  |  |     LOG_LOCK_ENABLE,  /* enable lock */ | 
 |  |  | }; | 
 |  |  |  | 
 |  |  | #define ROLLBACK_NONE   0 | 
 |  |  |  | 
 |  |  | /* description: Initial the logger system | 
 |  |  |  * arguments  : | 
 |  |  |  *             $fname: logger file name, NULL/"console"/"stderr" will log to console | 
 |  |  |  *             $level: logger level above; | 
 |  |  |  *             $size : logger file max size in KiB | 
 |  |  |  *             $lock : thread lock enable or not | 
 |  |  |  * return     : <0: Failed  ==0: Sucessfully | 
 |  |  |  */ | 
 |  |  | int  log_open(char *fname, int level, int size, int lock); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* description: Terminate the logger system */ | 
 |  |  | void log_close(void); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* description: log message into log file. Don't call this function directly. */ | 
 |  |  | void _log_write(int level, const char *file, int line, const char *fmt, ...); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* description: dump a buffer in hex to logger file */ | 
 |  |  | void log_dump(int level, const char *prompt, char *buf, size_t len); | 
 |  |  |  | 
 |  |  | /* function: log message into logger file with different log level */ | 
 |  |  | #define log_trace(...) _log_write(LOG_LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  | #define log_debug(...) _log_write(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  | #define log_info(...)  _log_write(LOG_LEVEL_INFO,  __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  | #define log_warn(...)  _log_write(LOG_LEVEL_WARN,  __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  | #define log_error(...) _log_write(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  | #define log_fatal(...) _log_write(LOG_LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__) | 
 |  |  |  | 
 |  |  | #endif | 
 
| New file | 
 |  |  | 
 |  |  |  | 
 |  |  | PWD=$(shell pwd ) | 
 |  |  |  | 
 |  |  | LIBNAME=$(shell basename ${PWD} ) | 
 |  |  | TOPDIR=$(shell dirname ${PWD} ) | 
 |  |  | CFLAGS+=-D_GNU_SOURCE | 
 |  |  |  | 
 |  |  | all: clean | 
 |  |  |     @rm -f *.o | 
 |  |  |     @${CROSSTOOL}gcc ${CFLAGS} -I${TOPDIR} -c *.c | 
 |  |  |     ${CROSSTOOL}ar -rcs  lib${LIBNAME}.a *.o | 
 |  |  |  | 
 |  |  | clean: | 
 |  |  |     @rm -f *.o | 
 |  |  |     @rm -f *.a | 
 |  |  |  | 
 |  |  | distclean: | 
 |  |  |     @make clean | 
 
| New file | 
 |  |  | 
 |  |  | /******************************************************************************** | 
 |  |  |  *      Copyright:  (C) 2021 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  ringbuf.h | 
 |  |  |  *    Description:  This head file | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(2021å¹´04月29æ—¥) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "2021å¹´04月29æ—¥ 12æ—¶18分32ç§’" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  | #include <string.h> | 
 |  |  | #include <assert.h> | 
 |  |  | #include "ringbuf.h" | 
 |  |  |  | 
 |  |  | void rb_init (struct ring_buffer *ring, unsigned char* buff, int size) | 
 |  |  | { | 
 |  |  |     memset (ring, 0, sizeof (struct ring_buffer)); | 
 |  |  |     ring->rd_pointer = 0; | 
 |  |  |     ring->wr_pointer = 0; | 
 |  |  |     ring->buffer= buff; | 
 |  |  |     ring->size = size; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | int rb_write (struct ring_buffer *rb, unsigned char * buf, int len) | 
 |  |  | { | 
 |  |  |     int                  total; | 
 |  |  |     int                  i; | 
 |  |  |  | 
 |  |  |     /* total = len = min(space, len) */ | 
 |  |  |     total = rb_free_size(rb); | 
 |  |  |     if(len > total) | 
 |  |  |         len = total; | 
 |  |  |     else | 
 |  |  |         total = len; | 
 |  |  |  | 
 |  |  |     i = rb->wr_pointer; | 
 |  |  |     if(i + len > rb->size) | 
 |  |  |     { | 
 |  |  |         memcpy(rb->buffer + i, buf, rb->size - i); | 
 |  |  |         buf += rb->size - i; | 
 |  |  |         len -= rb->size - i; | 
 |  |  |         i = 0; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     memcpy(rb->buffer + i, buf, len); | 
 |  |  |     rb->wr_pointer = i + len; | 
 |  |  |     return total; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | int rb_free_size (struct ring_buffer *rb) | 
 |  |  | { | 
 |  |  |     return (rb->size - 1 - rb_data_size(rb)); | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | int rb_read (struct ring_buffer *rb, unsigned char * buf, int max) | 
 |  |  | { | 
 |  |  |     int                  total; | 
 |  |  |     int                  i; | 
 |  |  |  | 
 |  |  |     /* total = len = min(used, len) */ | 
 |  |  |     total = rb_data_size(rb); | 
 |  |  |  | 
 |  |  |     if(max > total) | 
 |  |  |         max = total; | 
 |  |  |     else | 
 |  |  |         total = max; | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     i = rb->rd_pointer; | 
 |  |  |     if(i + max > rb->size) | 
 |  |  |     { | 
 |  |  |         memcpy(buf, rb->buffer + i, rb->size - i); | 
 |  |  |         buf += rb->size - i; | 
 |  |  |         max -= rb->size - i; | 
 |  |  |         i = 0; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     memcpy(buf, rb->buffer + i, max); | 
 |  |  |     rb->rd_pointer = i + max; | 
 |  |  |  | 
 |  |  |     return total; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int rb_data_size (struct ring_buffer *rb) | 
 |  |  | { | 
 |  |  |     return ((rb->wr_pointer - rb->rd_pointer) & (rb->size-1)); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | void rb_clear (struct ring_buffer *rb) | 
 |  |  | { | 
 |  |  |     memset(rb->buffer,0,rb->size); | 
 |  |  |     rb->rd_pointer=0; | 
 |  |  |     rb->wr_pointer=0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | unsigned char rb_peek(struct ring_buffer* rb, int index) | 
 |  |  | { | 
 |  |  |     assert(index < rb_data_size(rb)); | 
 |  |  |  | 
 |  |  |     return rb->buffer[((rb->rd_pointer + index) % rb->size)]; | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | /******************************************************************************** | 
 |  |  |  *      Copyright:  (C) 2021 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  ringbuf.h | 
 |  |  |  *    Description:  This head file | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(2021å¹´04月29æ—¥) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "2021å¹´04月29æ—¥ 12æ—¶18分32ç§’" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  |  | 
 |  |  | #ifndef  _RINGBUF_H_ | 
 |  |  | #define  _RINGBUF_H_ | 
 |  |  |  | 
 |  |  | struct ring_buffer | 
 |  |  | { | 
 |  |  |     unsigned char      *buffer; | 
 |  |  |     int                 wr_pointer; | 
 |  |  |     int                 rd_pointer; | 
 |  |  |     int                 size; | 
 |  |  | }; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Initial the ring buffer */ | 
 |  |  | void rb_init (struct ring_buffer *ring, unsigned char *buff, int size) ; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /*  Description: Write $len bytes data in $buf into ring buffer $rb | 
 |  |  |  * Return Value: The actual written into ring buffer data size, if ring buffer | 
 |  |  |  * left space size small than $len, then only part of the data be written into. | 
 |  |  |  */ | 
 |  |  | int rb_write (struct ring_buffer *rb, unsigned char *buf, int len) ; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Get ring buffer left free size  */ | 
 |  |  | int rb_free_size (struct ring_buffer *rb); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Read $max bytes data from ring buffer $rb to $buf */ | 
 |  |  | int rb_read (struct ring_buffer *rb, unsigned char *buf, int max); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Read a specify $index byte data in ring buffer $rb  */ | 
 |  |  | unsigned char rb_peek(struct ring_buffer *rb, int index); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Get data size in the ring buffer  */ | 
 |  |  | int rb_data_size (struct ring_buffer *rb); | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* Clear the ring buffer data  */ | 
 |  |  | void rb_clear (struct ring_buffer *rb) ; | 
 |  |  |  | 
 |  |  | #endif   /* ----- #ifndef _RINGBUF_H_  ----- */ | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /********************************************************************************* | 
 |  |  |  *      Copyright:  (C) 2020 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  util_proc.c | 
 |  |  |  *    Description:  This file is the process API | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(7/06/2020) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "7/06/2020 09:19:02 PM" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <stdlib.h> | 
 |  |  | #include <string.h> | 
 |  |  | #include <unistd.h> | 
 |  |  | #include <errno.h> | 
 |  |  | #include <fcntl.h> | 
 |  |  | #include <libgen.h> | 
 |  |  | #include <pthread.h> | 
 |  |  | #include <sys/types.h> | 
 |  |  | #include <sys/stat.h> | 
 |  |  |  | 
 |  |  | #include "util_proc.h" | 
 |  |  | #include "logger.h" | 
 |  |  |  | 
 |  |  | proc_signal_t     g_signal={0}; | 
 |  |  |  | 
 |  |  | void proc_default_sighandler(int sig) | 
 |  |  | { | 
 |  |  |     switch(sig) | 
 |  |  |     { | 
 |  |  |         case SIGINT: | 
 |  |  |             log_warn("SIGINT - stopping\n"); | 
 |  |  |             g_signal.stop = 1; | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         case SIGTERM: | 
 |  |  |             log_warn("SIGTERM - stopping\n"); | 
 |  |  |             g_signal.stop = 1; | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         case SIGSEGV: | 
 |  |  |             log_warn("SIGSEGV - stopping\n"); | 
 |  |  | #if 0 | 
 |  |  |             if(g_signal.stop) | 
 |  |  |                 exit(0); | 
 |  |  |  | 
 |  |  |             g_signal.stop = 1; | 
 |  |  | #endif | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         case SIGPIPE: | 
 |  |  |             log_warn("SIGPIPE - warnning\n"); | 
 |  |  |             break; | 
 |  |  |  | 
 |  |  |         default: | 
 |  |  |             break; | 
 |  |  |     } | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* install default signal process functions  */ | 
 |  |  | void install_default_signal(void) | 
 |  |  | { | 
 |  |  |     struct sigaction sigact, sigign; | 
 |  |  |  | 
 |  |  |     log_info("Install default signal handler.\n"); | 
 |  |  |  | 
 |  |  |     /*  Initialize the catch signal structure. */ | 
 |  |  |     sigemptyset(&sigact.sa_mask); | 
 |  |  |     sigact.sa_flags = 0; | 
 |  |  |     sigact.sa_handler = proc_default_sighandler; | 
 |  |  |  | 
 |  |  |     /*  Setup the ignore signal. */ | 
 |  |  |     sigemptyset(&sigign.sa_mask); | 
 |  |  |     sigign.sa_flags = 0; | 
 |  |  |     sigign.sa_handler = SIG_IGN; | 
 |  |  |  | 
 |  |  |     sigaction(SIGTERM, &sigact, 0); /*  catch terminate signal "kill" command */ | 
 |  |  |     sigaction(SIGINT,  &sigact, 0); /*  catch interrupt signal CTRL+C */ | 
 |  |  |     //sigaction(SIGSEGV, &sigact, 0); /*  catch segmentation faults  */ | 
 |  |  |     sigaction(SIGPIPE, &sigact, 0); /*  catch broken pipe */ | 
 |  |  | #if 0 | 
 |  |  |     sigaction(SIGCHLD, &sigact, 0); /*  catch child process return */ | 
 |  |  |     sigaction(SIGUSR2, &sigact, 0); /*  catch USER signal */ | 
 |  |  | #endif | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: daemonize | 
 |  |  |  * Description : Set the programe runs as daemon in background | 
 |  |  |  * Inputs      : nodir: DON'T change the work directory to / :  1:NoChange 0:Change | 
 |  |  |  *               noclose: close the opened file descrtipion or not 1:Noclose 0:Close | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : NONE | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | void daemonize(int nochdir, int noclose) | 
 |  |  | { | 
 |  |  |     int rv, fd; | 
 |  |  |     int i; | 
 |  |  |  | 
 |  |  |     /*  already a daemon */ | 
 |  |  |     if (1 == getppid()) | 
 |  |  |         return; | 
 |  |  |  | 
 |  |  |     /*  fork error */ | 
 |  |  |     rv = fork(); | 
 |  |  |     if (rv < 0) exit(1); | 
 |  |  |  | 
 |  |  |     /*  parent process exit */ | 
 |  |  |     if (rv > 0) | 
 |  |  |         exit(0); | 
 |  |  |  | 
 |  |  |     /*  obtain a new process session group */ | 
 |  |  |     setsid(); | 
 |  |  |  | 
 |  |  |     if (!noclose) | 
 |  |  |     { | 
 |  |  |         /*  close all descriptors */ | 
 |  |  |         for (i = getdtablesize(); i >= 0; --i) | 
 |  |  |         { | 
 |  |  |             //if (i != g_logPtr->fd) | 
 |  |  |                 close(i); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         /*  Redirect Standard input [0] to /dev/null */ | 
 |  |  |         fd = open("/dev/null", O_RDWR); | 
 |  |  |  | 
 |  |  |         /* Redirect Standard output [1] to /dev/null */ | 
 |  |  |         dup(fd); | 
 |  |  |  | 
 |  |  |         /* Redirect Standard error [2] to /dev/null */ | 
 |  |  |         dup(fd); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     umask(0); | 
 |  |  |  | 
 |  |  |     if (!nochdir) | 
 |  |  |         chdir("/"); | 
 |  |  |  | 
 |  |  |     return; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: check_set_program_running | 
 |  |  |  * Description : check program already running or not, if not then run it and | 
 |  |  |  *               record pid into $pidfile | 
 |  |  |  * Inputs      : daemon:  set program running in daemon or not | 
 |  |  |  *               pid_file:The record PID file path | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : 0: Record successfully  Else: Failure | 
 |  |  |  * *****************************************************************************/ | 
 |  |  |  | 
 |  |  | int check_set_program_running(int daemon, char *pidfile) | 
 |  |  | { | 
 |  |  |     if( !pidfile ) | 
 |  |  |         return 0; | 
 |  |  |  | 
 |  |  |     if( check_daemon_running(pidfile) ) | 
 |  |  |     { | 
 |  |  |         log_error("Program already running, process exit now"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( daemon ) | 
 |  |  |     { | 
 |  |  |         if( set_daemon_running(pidfile) < 0 ) | 
 |  |  |         { | 
 |  |  |             log_error("set program running as daemon failure\n"); | 
 |  |  |             return -2; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         if( record_daemon_pid(pidfile) < 0 ) | 
 |  |  |         { | 
 |  |  |             log_error("record program running PID failure\n"); | 
 |  |  |             return -3; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: record_daemon_pid | 
 |  |  |  * Description : Record the running daemon program PID to the file "pid_file" | 
 |  |  |  * Inputs      : pid_file:The record PID file path | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : 0: Record successfully  Else: Failure | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | int record_daemon_pid(const char *pid_file) | 
 |  |  | { | 
 |  |  |     struct stat fStatBuf; | 
 |  |  |     int fd = -1; | 
 |  |  |     int mode = S_IROTH | S_IXOTH | S_IRGRP | S_IXGRP | S_IRWXU; | 
 |  |  |     char ipc_dir[64] = { 0 }; | 
 |  |  |  | 
 |  |  |     strncpy(ipc_dir, pid_file, 64); | 
 |  |  |  | 
 |  |  |     /* dirname() will modify ipc_dir and save the result */ | 
 |  |  |     dirname(ipc_dir); | 
 |  |  |  | 
 |  |  |     /* If folder pid_file PATH doesnot exist, then we will create it" */ | 
 |  |  |     if (stat(ipc_dir, &fStatBuf) < 0) | 
 |  |  |     { | 
 |  |  |         if (mkdir(ipc_dir, mode) < 0) | 
 |  |  |         { | 
 |  |  |             log_error("cannot create %s: %s\n", ipc_dir, strerror(errno)); | 
 |  |  |             return -1; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         (void)chmod(ipc_dir, mode); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /*  Create the process running PID file */ | 
 |  |  |     mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; | 
 |  |  |     if ((fd = open(pid_file, O_RDWR | O_CREAT | O_TRUNC, mode)) >= 0) | 
 |  |  |     { | 
 |  |  |         char pid[PID_ASCII_SIZE]; | 
 |  |  |         snprintf(pid, sizeof(pid), "%u\n", (unsigned)getpid()); | 
 |  |  |         write(fd, pid, strlen(pid)); | 
 |  |  |         close(fd); | 
 |  |  |  | 
 |  |  |         log_debug("Record PID<%u> to file %s.\n", getpid(), pid_file); | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         log_error("cannot create %s: %s\n", pid_file, strerror(errno)); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: get_daemon_pid | 
 |  |  |  * Description : Get the daemon process PID from the PID record file "pid_file" | 
 |  |  |  * Inputs      : pid_file: the PID record file | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : pid_t: The daemon process PID number | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | pid_t get_daemon_pid(const char *pid_file) | 
 |  |  | { | 
 |  |  |     FILE *f; | 
 |  |  |     pid_t pid; | 
 |  |  |  | 
 |  |  |     if ((f = fopen(pid_file, "rb")) != NULL) | 
 |  |  |     { | 
 |  |  |         char pid_ascii[PID_ASCII_SIZE]; | 
 |  |  |         (void)fgets(pid_ascii, PID_ASCII_SIZE, f); | 
 |  |  |         (void)fclose(f); | 
 |  |  |         pid = atoi(pid_ascii); | 
 |  |  |     } | 
 |  |  |     else | 
 |  |  |     { | 
 |  |  |         log_error("Can't open PID record file %s: %s\n", pid_file, strerror(errno)); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |     return pid; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: check_daemon_running | 
 |  |  |  * Description : Check the daemon program already running or not | 
 |  |  |  * Inputs      : pid_file: The record running daemon program PID | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : 1: The daemon program alread running   0: Not running | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | int check_daemon_running(const char *pid_file) | 
 |  |  | { | 
 |  |  |     int rv = -1; | 
 |  |  |     struct stat fStatBuf; | 
 |  |  |  | 
 |  |  |     rv = stat(pid_file, &fStatBuf); | 
 |  |  |     if (0 == rv) | 
 |  |  |     { | 
 |  |  |         pid_t pid = -1; | 
 |  |  |         printf("PID record file \"%s\" exist.\n", pid_file); | 
 |  |  |  | 
 |  |  |         pid = get_daemon_pid(pid_file); | 
 |  |  |         if (pid > 0)  /*  Process pid exist */ | 
 |  |  |         { | 
 |  |  |             if ((rv = kill(pid, 0)) == 0) | 
 |  |  |             { | 
 |  |  |                 printf("Program with PID[%d] seems running.\n", pid); | 
 |  |  |                 return 1; | 
 |  |  |             } | 
 |  |  |             else   /* Send signal to the old process get no reply. */ | 
 |  |  |             { | 
 |  |  |                 printf("Program with PID[%d] seems exit.\n", pid); | 
 |  |  |                 remove(pid_file); | 
 |  |  |                 return 0; | 
 |  |  |             } | 
 |  |  |         } | 
 |  |  |         else if (0 == pid) | 
 |  |  |         { | 
 |  |  |             printf("Can not read program PID form record file.\n"); | 
 |  |  |             remove(pid_file); | 
 |  |  |             return 0; | 
 |  |  |         } | 
 |  |  |         else  /* Read pid from file "pid_file" failure */ | 
 |  |  |         { | 
 |  |  |             printf("Read record file \"%s\" failure, maybe program still running.\n", pid_file); | 
 |  |  |             return 1; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: stop_daemon_running | 
 |  |  |  * Description : Stop the daemon program running | 
 |  |  |  * Inputs      : pid_file: The record running daemon program PID | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : 1: The daemon program alread running   0: Not running | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | int stop_daemon_running(const char *pid_file) | 
 |  |  | { | 
 |  |  |     pid_t            pid = -1; | 
 |  |  |     struct stat      fStatBuf; | 
 |  |  |  | 
 |  |  |     if ( stat(pid_file, &fStatBuf) < 0) | 
 |  |  |         return 0; | 
 |  |  |  | 
 |  |  |     printf("PID record file \"%s\" exist.\n", pid_file); | 
 |  |  |     pid = get_daemon_pid(pid_file); | 
 |  |  |     if (pid > 0)  /*  Process pid exist */ | 
 |  |  |     { | 
 |  |  |         while ( (kill(pid, 0) ) == 0) | 
 |  |  |         { | 
 |  |  |             kill(pid, SIGTERM); | 
 |  |  |             sleep(1); | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         remove(pid_file); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* **************************************************************************** | 
 |  |  |  * FunctionName: set_daemon_running | 
 |  |  |  * Description : Set the programe running as daemon if it's not running and record | 
 |  |  |  *               its PID to the pid_file. | 
 |  |  |  * Inputs      : pid_file: The record running daemon program PID | 
 |  |  |  * Output      : NONE | 
 |  |  |  * Return      : 0: Successfully. 1: Failure | 
 |  |  |  * *****************************************************************************/ | 
 |  |  | int set_daemon_running(const char *pid_file) | 
 |  |  | { | 
 |  |  |     daemonize(0, 1); | 
 |  |  |     log_info("Program running as daemon [PID:%d].\n", getpid()); | 
 |  |  |  | 
 |  |  |     if (record_daemon_pid(pid_file) < 0) | 
 |  |  |     { | 
 |  |  |         log_error("Record PID to file \"%s\" failure.\n", pid_file); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* start a new thread to run $thread_workbody point function  */ | 
 |  |  | int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg) | 
 |  |  | { | 
 |  |  |     int                rv = 0; | 
 |  |  |     pthread_t          tid; | 
 |  |  |  | 
 |  |  |     pthread_attr_t     thread_attr; | 
 |  |  |  | 
 |  |  |     /* Initialize the thread  attribute */ | 
 |  |  |     rv = pthread_attr_init(&thread_attr); | 
 |  |  |     if(rv) | 
 |  |  |         return -1; | 
 |  |  |  | 
 |  |  |     /* Set the stack size of the thread */ | 
 |  |  |     rv = pthread_attr_setstacksize(&thread_attr, 120 * 1024); | 
 |  |  |     if(rv) | 
 |  |  |         goto CleanUp; | 
 |  |  |  | 
 |  |  |     /* Set thread to detached state:Don`t need pthread_join */ | 
 |  |  |     rv = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); | 
 |  |  |     if(rv) | 
 |  |  |         goto CleanUp; | 
 |  |  |  | 
 |  |  |     /* Create the thread */ | 
 |  |  |     rv = pthread_create(&tid, &thread_attr, thread_workbody, thread_arg); | 
 |  |  |     if(rv) | 
 |  |  |         goto CleanUp; | 
 |  |  |  | 
 |  |  | CleanUp: | 
 |  |  |  | 
 |  |  |  | 
 |  |  |     if( thread_id ) | 
 |  |  |     { | 
 |  |  |         if( rv ) | 
 |  |  |             *thread_id = 0; | 
 |  |  |         else | 
 |  |  |             *thread_id = tid; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     /* Destroy the  attributes  of  thread */ | 
 |  |  |     pthread_attr_destroy(&thread_attr); | 
 |  |  |     return rv; | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* excute a linux command by system() */ | 
 |  |  | void exec_system_cmd(const char *format, ...) | 
 |  |  | { | 
 |  |  |     char                cmd[256]; | 
 |  |  |     va_list             args; | 
 |  |  |  | 
 |  |  |     memset(cmd, 0, sizeof(cmd)); | 
 |  |  |  | 
 |  |  |     va_start(args, format); | 
 |  |  |     vsnprintf(cmd, sizeof(cmd), format, args); | 
 |  |  |     va_end(args); | 
 |  |  |  | 
 |  |  |     system(cmd); | 
 |  |  | } | 
 |  |  |  | 
 |  |  |  | 
 
| New file | 
 |  |  | 
 |  |  | /******************************************************************************** | 
 |  |  |  *      Copyright:  (C) 2020 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  util_proc.h | 
 |  |  |  *    Description:  This head file is for Linux process/thread API | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(7/06/2012~) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "7/06/2012 09:21:33 PM" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  |  | 
 |  |  | #ifndef __UTIL_PROC_H_ | 
 |  |  | #define __UTIL_PROC_H_ | 
 |  |  |  | 
 |  |  | #include <signal.h> | 
 |  |  |  | 
 |  |  | #define PID_ASCII_SIZE  11 | 
 |  |  |  | 
 |  |  | typedef struct proc_signal_s | 
 |  |  | { | 
 |  |  |     int       signal; | 
 |  |  |     unsigned  stop;     /* 0: Not term  1: Stop  */ | 
 |  |  | }  proc_signal_t; | 
 |  |  |  | 
 |  |  | typedef void *(* thread_body_t) (void *thread_arg); | 
 |  |  |  | 
 |  |  | extern proc_signal_t    g_signal; | 
 |  |  |  | 
 |  |  | /* install default signal process functions  */ | 
 |  |  | extern void install_default_signal(void); | 
 |  |  |  | 
 |  |  | /* excute a linux command by system() */ | 
 |  |  | extern void exec_system_cmd(const char *format, ...); | 
 |  |  |  | 
 |  |  | /* check program already running or not, if not then run it and record pid into $pidfile */ | 
 |  |  | extern int check_set_program_running(int daemon, char *pidfile); | 
 |  |  |  | 
 |  |  | /* check program already running or not from $pid_file  */ | 
 |  |  | extern int check_daemon_running(const char *pid_file); | 
 |  |  |  | 
 |  |  | /* set program daemon running and record pid in $pid_file  */ | 
 |  |  | extern int set_daemon_running(const char *pid_file); | 
 |  |  |  | 
 |  |  | /* record proces ID into $pid_file  */ | 
 |  |  | extern int record_daemon_pid(const char *pid_file); | 
 |  |  |  | 
 |  |  | /* stop program running from $pid_file  */ | 
 |  |  | extern int stop_daemon_running(const char *pid_file); | 
 |  |  |  | 
 |  |  | /* my implementation for set program running in daemon   */ | 
 |  |  | extern void daemonize(int nochdir, int noclose); | 
 |  |  |  | 
 |  |  | /* start a new thread to run $thread_workbody point function  */ | 
 |  |  | extern int thread_start(pthread_t *thread_id, thread_body_t thread_workbody, void *thread_arg); | 
 |  |  |  | 
 |  |  | /* +---------------------+ | 
 |  |  |  * |   Low level API     | | 
 |  |  |  * +---------------------+*/ | 
 |  |  |  | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /* get daemon process ID from $pid_file   */ | 
 |  |  | extern pid_t get_daemon_pid(const char *pid_file); | 
 |  |  |  | 
 |  |  | #endif | 
 
| New file | 
 |  |  | 
 |  |  | /********************************************************************************* | 
 |  |  |  *      Copyright:  (C) 2023 LingYun IoT System Studio | 
 |  |  |  *                  All rights reserved. | 
 |  |  |  * | 
 |  |  |  *       Filename:  gpsd.c | 
 |  |  |  *    Description:  This file is GPS location parser program | 
 |  |  |  * | 
 |  |  |  *        Version:  1.0.0(07/10/23) | 
 |  |  |  *         Author:  Guo Wenxue <guowenxue@gmail.com> | 
 |  |  |  *      ChangeLog:  1, Release initial version on "07/10/23 09:49:55" | 
 |  |  |  * | 
 |  |  |  ********************************************************************************/ | 
 |  |  |  | 
 |  |  | #include <stdio.h> | 
 |  |  | #include <string.h> | 
 |  |  | #include <unistd.h> | 
 |  |  |  | 
 |  |  | #include "logger.h" | 
 |  |  | #include "comport.h" | 
 |  |  | #include "util_proc.h" | 
 |  |  |  | 
 |  |  | typedef struct gps_fix_s | 
 |  |  | { | 
 |  |  |     char       time[32];  /* GPS UTC time, formt: hhmmss */ | 
 |  |  |     char       status;    /* A: Valid  V:Invalid */ | 
 |  |  |     float      latitude;  /* Latitude */ | 
 |  |  |     char       lat;       /* N: North  S: South */ | 
 |  |  |     float      longitude; /* Longitude */ | 
 |  |  |     char       lon;       /* E: East   W: West */ | 
 |  |  |     float      speed;     /* Speed over ground, meters/sec */ | 
 |  |  |     float      track;     /* Course made good (relative to true north) */ | 
 |  |  | } gps_fix_t; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | int proc_gprmc(char *buf, int size, gps_fix_t *info); | 
 |  |  |  | 
 |  |  | static inline void banner(const char *progname) | 
 |  |  | { | 
 |  |  |     printf("%s program Version v1.0.0 Build on (%s)\n", progname, __DATE__); | 
 |  |  |     printf("Copyright (C) 2023 LingYun IoT System Studio.\n"); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | static void program_usage(const char *progname) | 
 |  |  | { | 
 |  |  |     banner(progname); | 
 |  |  |  | 
 |  |  |     printf("Usage: %s [OPTION]...\n", progname); | 
 |  |  |     printf(" %s is a MQTT subscribe daemon program to control relay. \n", progname); | 
 |  |  |  | 
 |  |  |     printf("\nMandatory arguments to long options are mandatory for short options too:\n"); | 
 |  |  |     printf(" -D[device  ]  Set GPS connect serial port device\n"); | 
 |  |  |     printf(" -d[debug   ]  Running in debug mode\n"); | 
 |  |  |     printf(" -l[level   ]  Set the log level as [0..%d]\n", LOG_LEVEL_MAX-1); | 
 |  |  |     printf(" -h[help    ]  Display this help information\n"); | 
 |  |  |     printf(" -v[version ]  Display the program version\n"); | 
 |  |  |  | 
 |  |  |     return; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | int main (int argc, char **argv) | 
 |  |  | { | 
 |  |  |     const char             *progname=NULL; | 
 |  |  |     int                     opt = 0; | 
 |  |  |     int                     rv = 0; | 
 |  |  |     int                     debug = 0; | 
 |  |  |     char                    pid_file[64] = { 0 }; /*   The file used to record the PID */ | 
 |  |  |     char                   *log_file = "/tmp/gpsd.log"; | 
 |  |  |     int                     log_level = LOG_LEVEL_INFO; | 
 |  |  |     int                     log_size = 10; | 
 |  |  |  | 
 |  |  |     char                    buf[4096]; | 
 |  |  |     char                   *dev=NULL; | 
 |  |  |     comport_t               comport; | 
 |  |  |     gps_fix_t               info; | 
 |  |  |  | 
 |  |  |     struct option long_options[] = { | 
 |  |  |         {"device", required_argument, NULL, 'D'}, | 
 |  |  |         {"debug", no_argument, NULL, 'd'}, | 
 |  |  |         {"level", required_argument, NULL, 'l'}, | 
 |  |  |         {"version", no_argument, NULL, 'v'}, | 
 |  |  |         {"help", no_argument, NULL, 'h'}, | 
 |  |  |         {NULL, 0, NULL, 0} | 
 |  |  |     }; | 
 |  |  |  | 
 |  |  |     progname = basename(argv[0]); | 
 |  |  |     /*  Parser the command line parameters */ | 
 |  |  |     while ((opt = getopt_long(argc, argv, "D:dl:vh", long_options, NULL)) != -1) | 
 |  |  |     { | 
 |  |  |         switch (opt) | 
 |  |  |         { | 
 |  |  |             case 'D':  /*  Set serial port device */ | 
 |  |  |                 dev = optarg; | 
 |  |  |                 break; | 
 |  |  |  | 
 |  |  |             case 'd': /*  Set debug running */ | 
 |  |  |                 debug = 1; | 
 |  |  |                 log_file = "console"; | 
 |  |  |                 log_level = LOG_LEVEL_DEBUG; | 
 |  |  |                 log_size = ROLLBACK_NONE; | 
 |  |  |                 break; | 
 |  |  |  | 
 |  |  |             case 'l': /*  Set the log level */ | 
 |  |  |                 rv = atoi(optarg); | 
 |  |  |                 log_level = rv>LOG_LEVEL_MAX ? LOG_LEVEL_MAX-1 : rv; | 
 |  |  |                 break; | 
 |  |  |  | 
 |  |  |             case 'v':  /*  Get software version */ | 
 |  |  |                 banner(progname); /*  Defined in version.h */ | 
 |  |  |                 return EXIT_SUCCESS; | 
 |  |  |  | 
 |  |  |             case 'h':  /*  Get help information */ | 
 |  |  |                 program_usage(progname); | 
 |  |  |                 return 0; | 
 |  |  |  | 
 |  |  |             default: | 
 |  |  |                 break; | 
 |  |  |         } /*   end of "switch(opt)" */ | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( !dev ) | 
 |  |  |     { | 
 |  |  |         program_usage(progname); | 
 |  |  |         return 0; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( (rv=log_open(log_file, log_level, log_size, LOG_LOCK_DISABLE)) < 0 ) | 
 |  |  |     { | 
 |  |  |         fprintf(stderr, "open logger failed, rv=%d\n", rv); | 
 |  |  |         return 1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( !debug ) | 
 |  |  |     { | 
 |  |  |         snprintf(pid_file, sizeof(pid_file), "/var/run/%s.pid", progname); | 
 |  |  |         log_info("check program running in daemon or not by pidfile [%s]\n", pid_file); | 
 |  |  |         if( check_daemon_running(pid_file) ) | 
 |  |  |         { | 
 |  |  |             printf("Programe already running, exit now.\n"); | 
 |  |  |             return 3; | 
 |  |  |         } | 
 |  |  |  | 
 |  |  |         if( set_daemon_running(pid_file) < 0 ) | 
 |  |  |         { | 
 |  |  |             printf("Programe already running, exit now.\n"); | 
 |  |  |             return 3; | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( (rv=comport_open(&comport, dev, 4800, "8N1N")) < 0 ) | 
 |  |  |     { | 
 |  |  |         log_error("Open serial port \"%s\" failed, rv=%d\n", dev, rv); | 
 |  |  |         return 2; | 
 |  |  |     } | 
 |  |  |     log_info("Open serial port \"%s\" successfully\n", dev); | 
 |  |  |  | 
 |  |  |     while(1) | 
 |  |  |     { | 
 |  |  |         memset(buf, 0, sizeof(buf)); | 
 |  |  |         rv = comport_recv(&comport, buf, sizeof(buf), 3000); | 
 |  |  |         if( rv > 0 ) | 
 |  |  |         { | 
 |  |  |             proc_gprmc(buf, strlen(buf), &info); | 
 |  |  |         } | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     comport_close(&comport); | 
 |  |  |     return 0; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /* | 
 |  |  |  * $GPRMC,024216.000,A,3029.6651,N,11423.6251,E,0.08, 156.95,100723,,,A*65 | 
 |  |  |  * 1     024216.000    UTC time format: HHMMSS, 10:42:16(BeiJing) | 
 |  |  |  * 2     A             Status of Fix: | 
 |  |  |  *                      A = Autonomous, valid; | 
 |  |  |  *                      D = Differential, valid; | 
 |  |  |  *                      V = invalid | 
 |  |  |  * | 
 |  |  |  * 3,4   3029.6651,N   Latitude format: ddmm.mmmm, Latitude 30 deg. 29.6651 min North | 
 |  |  |  * 5,6   11423.6251,E  Longitude: dddmm.mmmm, Longitude 114 deg. 23.6251 min East | 
 |  |  |  * 7     0.08          Speed over ground, Knots | 
 |  |  |  * 8     156.95        Course Made Good, True north | 
 |  |  |  * 9     100723        Date of fix ddmmyy. 2023-07-10 | 
 |  |  |  * 10,11 ,,            Magnetic variation | 
 |  |  |  * 12    A             FAA mode indicator (NMEA 2.3 and later) | 
 |  |  |  *                      A=autonomous, | 
 |  |  |  *                      D=differential, | 
 |  |  |  *                      E=Estimated, | 
 |  |  |  *                      M=Manual input mode, | 
 |  |  |  *                      N=not valid, | 
 |  |  |  *                      S=Simulator, | 
 |  |  |  *                      V=Valid | 
 |  |  |  * 13   *68            mandatory nmea_checksum | 
 |  |  |  */ | 
 |  |  |  | 
 |  |  | #define DD(s)   ((int)((s)[0]-'0')*10+(int)((s)[1]-'0')) | 
 |  |  | int proc_gprmc(char *buf, int size, gps_fix_t *info) | 
 |  |  | { | 
 |  |  |     char           *ptr_start=NULL; | 
 |  |  |     char           *ptr_end=NULL; | 
 |  |  |     char            hhmmss[12]={0x0}; | 
 |  |  |     char            ddmmyy[12]={0x0}; | 
 |  |  |  | 
 |  |  |     if( !buf || size<=0 || !info ) | 
 |  |  |     { | 
 |  |  |         log_error("Invalid input arguments\n"); | 
 |  |  |         return -1; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     log_trace("GPS receive raw data\n:%s\n", buf); | 
 |  |  |  | 
 |  |  |     if( !(ptr_start=strstr(buf, "$GPRMC")) ) | 
 |  |  |     { | 
 |  |  |         log_warn("$GPRMC keywords not matched\n"); | 
 |  |  |         return -2; | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     if( !(ptr_end=strchr(ptr_start, 0x0D)) ) | 
 |  |  |     { | 
 |  |  |         log_warn("$GPRMC data not integrated\n", ptr_start); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     *ptr_end = '\0'; | 
 |  |  |  | 
 |  |  |     memset(info, 0, sizeof(*info)); | 
 |  |  |     sscanf(ptr_start, "$GPRMC,%[^,],%c,%f,%c,%f,%c,%f,%f,%[^,]", | 
 |  |  |             hhmmss, &info->status, &info->latitude, &info->lat, | 
 |  |  |             &info->longitude, &info->lon, &info->speed, &info->track, ddmmyy); | 
 |  |  |  | 
 |  |  |     snprintf(info->time, sizeof(info->time), "%04d-%02d-%02d %02d:%02d:%02d", | 
 |  |  |             DD(ddmmyy+4)+2000, DD(ddmmyy+2), DD(ddmmyy), | 
 |  |  |             DD(hhmmss)+8, DD(hhmmss+2), DD(hhmmss+4)); | 
 |  |  |  | 
 |  |  |     if( info->status == 'A' ) | 
 |  |  |     { | 
 |  |  |         log_info("NMEA0183: %s lat=%.2f(%c) lon=%.2f(%c) speed=%.2f, track=%.2f\n", | 
 |  |  |                 info->time, info->latitude, info->lat, | 
 |  |  |                 info->longitude, info->lon, info->speed, info->track); | 
 |  |  |     } | 
 |  |  |     else if( info->status == 'V' ) | 
 |  |  |     { | 
 |  |  |         log_info("NMEA0183: Invalid data\n"); | 
 |  |  |     } | 
 |  |  |  | 
 |  |  |     return 0; | 
 |  |  | } | 
 
| New file | 
 |  |  | 
 |  |  | #********************************************************************************* | 
 |  |  | #      Copyright:  (C) 2022 Avnet. All rights reserved. | 
 |  |  | #         Author:  Guo Wenxue<wenxue.guo@avnet.com> | 
 |  |  | # | 
 |  |  | #       Filename:  Makefile | 
 |  |  | #    Description:  This Makefile used to compile all the C source code file in | 
 |  |  | #                  current folder to a excutable binary file. | 
 |  |  | # | 
 |  |  | #********************************************************************************/ | 
 |  |  |  | 
 |  |  | PRJ_PATH=$(shell pwd) | 
 |  |  | APP_NAME = gpsd | 
 |  |  | INST_PATH= /tftp | 
 |  |  |  | 
 |  |  | BUILD_ARCH=$(shell uname -m) | 
 |  |  |  | 
 |  |  | ifneq ($(findstring $(BUILD_ARCH), "x86_64" "i386"),) | 
 |  |  |     CROSSTOOL=arm-linux-gnueabi- | 
 |  |  | endif | 
 |  |  |  | 
 |  |  | # C source files in top-level directory | 
 |  |  | SRCFILES = $(wildcard *.c) | 
 |  |  |  | 
 |  |  | # common CFLAGS for our source code | 
 |  |  | CFLAGS = -Wall -Wshadow -Wundef -Wmaybe-uninitialized -D_GNU_SOURCE | 
 |  |  |  | 
 |  |  | # C source file in sub-directory | 
 |  |  | DIRS= booster | 
 |  |  | DIRS_PATH=$(patsubst %,${PRJ_PATH}/%,$(DIRS)) | 
 |  |  | CFLAGS+=$(patsubst %,-I%,$(DIRS_PATH)) | 
 |  |  | LDFLAGS+=$(patsubst %,-L%,$(DIRS_PATH)) | 
 |  |  | LIBS=$(patsubst %,-l%,$(DIRS)) | 
 |  |  |  | 
 |  |  | LDFLAGS+=-lpthread | 
 |  |  |  | 
 |  |  | .PHONY:libs | 
 |  |  | all: entry modules binary | 
 |  |  |  | 
 |  |  | entry: | 
 |  |  |     @echo "Building ${APP_NAME} on ${BUILD_ARCH}" | 
 |  |  |  | 
 |  |  | modules: | 
 |  |  |     @set -e; for d in ${DIRS}; do $(MAKE) CROSSTOOL=${CROSSTOOL} CFLAGS="${CFLAGS}" -C $${d}; done | 
 |  |  |  | 
 |  |  | binary:  ${SRCFILES} | 
 |  |  |     $(CROSSTOOL)gcc $(CFLAGS) -o ${APP_NAME} $^ ${LDFLAGS} ${LIBS} | 
 |  |  |     @echo " Compile over" | 
 |  |  |  | 
 |  |  | clean: | 
 |  |  |     set -e; for d in ${DIRS}; do $(MAKE) clean -C $${d}; done | 
 |  |  |     @rm -f *.o $(APP_NAME) | 
 |  |  |  | 
 |  |  | distclean: clean | 
 |  |  |     @rm -rf cscope* tags | 
 |  |  |  | 
 |  |  | install: | 
 |  |  |     cp ${APP_NAME} ${INST_PATH} |