Browse Source

Merge branch 'dev'

dev
Dnomd343 2 years ago
parent
commit
2cd94f6604
  1. 8
      .gitignore
  2. 39
      CMakeLists.txt
  3. 26
      Dockerfile
  4. 3
      assets/china-ip.py
  5. 10
      include/bcrypt/bcrypt.h
  6. 3
      include/common/json.h
  7. 2
      include/common/sundry.h
  8. 1
      include/common/system.h
  9. 2
      include/constant.h.in
  10. 6
      include/loader/config.h
  11. 4
      include/loader/loader.h
  12. 11
      include/to_json.h
  13. 28
      include/utils/assets.h
  14. 7
      src/CMakeLists.txt
  15. 1275
      src/Cargo.lock
  16. 6
      src/Cargo.toml
  17. 21
      src/applet/adguard.c
  18. 15
      src/assets/Cargo.toml
  19. 3
      src/assets/cbindgen.toml
  20. 193
      src/assets/src/fetch.rs
  21. 97
      src/assets/src/ffi.rs
  22. 2
      src/assets/src/lib.rs
  23. 10
      src/bcrypt/hash.c
  24. 4
      src/cleardns.c
  25. 29
      src/common/json.c
  26. 4
      src/common/sundry.c
  27. 8
      src/common/system.c
  28. 12
      src/loader/config.c
  29. 4
      src/loader/default.c
  30. 19
      src/loader/loader.c
  31. 15
      src/loader/parser.c
  32. 91
      src/to-json/Cargo.lock
  33. 8
      src/to-json/Cargo.toml
  34. 2
      src/to-json/cbindgen.toml
  35. 135
      src/to-json/src/ffi.rs
  36. 21
      src/to-json/src/json.rs
  37. 2
      src/to-json/src/lib.rs
  38. 62
      src/to-json/src/parser.rs
  39. 80
      src/to-json/src/tests.rs
  40. 1
      src/utils/CMakeLists.txt
  41. 112
      src/utils/assets.c
  42. 11
      src/utils/process.c

8
.gitignore

@ -1,7 +1,9 @@
/bin/
/build/
/.idea/
/assets/*.txt
/src/target/
/cmake-build/
/cmake-build-debug/
/cmake-build-release/
/src/to-json/target/
/assets/*.txt
/include/constant.h

39
CMakeLists.txt

@ -1,6 +1,41 @@
cmake_minimum_required(VERSION 2.8.12)
project(cleardns)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
project(cleardns LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin/)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror")
###############################################################
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
###############################################################
macro(git_tag _tag)
find_package(Git QUIET)
if (GIT_FOUND)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE ${_tag}
OUTPUT_STRIP_TRAILING_WHITESPACE
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
)
endif()
endmacro()
set(VERSION "")
git_tag(VERSION)
if(VERSION STREQUAL "") # without git tag
message(FATAL_ERROR "Git command not found")
endif()
###############################################################
add_subdirectory(src)
###############################################################

26
Dockerfile

@ -1,10 +1,10 @@
ARG ALPINE="alpine:3.17"
ARG RUST="rust:1.65-alpine3.16"
ARG RUST="rust:1.67-alpine3.17"
ARG GOLANG="golang:1.18-alpine3.16"
FROM ${ALPINE} AS upx
RUN apk add build-base cmake
ENV UPX="4.0.1"
ENV UPX="4.0.2"
RUN wget https://github.com/upx/upx/releases/download/v${UPX}/upx-${UPX}-src.tar.xz && tar xf upx-${UPX}-src.tar.xz
WORKDIR ./upx-${UPX}-src/
RUN make UPX_CMAKE_CONFIG_FLAGS=-DCMAKE_EXE_LINKER_FLAGS=-static
@ -38,21 +38,23 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.VersionString=${DN
COPY --from=upx /tmp/upx /usr/bin/
RUN upx -9 /tmp/dnsproxy
FROM ${RUST} AS to-json
COPY ./src/to-json/ /to-json/
WORKDIR /to-json/
RUN cargo build --release
RUN cp ./target/release/libto_json.a /
FROM ${RUST} AS rust-mods
RUN apk add libc-dev openssl-dev
COPY ./src/ /cleardns/
WORKDIR /cleardns/
RUN cargo fetch
RUN cargo build --release && mv ./target/release/*.a /tmp/
FROM ${ALPINE} AS cleardns
RUN apk add build-base cmake
RUN apk add build-base cmake git openssl-libs-static
COPY ./ /cleardns/
COPY --from=to-json /libto_json.a /cleardns/src/to-json/target/release/
COPY --from=rust-mods /tmp/libassets.a /cleardns/src/target/release/
COPY --from=rust-mods /tmp/libto_json.a /cleardns/src/target/release/
WORKDIR /cleardns/bin/
RUN cmake -DCMAKE_EXE_LINKER_FLAGS=-static -DCMAKE_BUILD_TYPE=Release .. && make
RUN strip cleardns && mv cleardns /tmp/
RUN cmake -DCMAKE_EXE_LINKER_FLAGS=-static .. && make && mv cleardns /tmp/
COPY --from=upx /tmp/upx /usr/bin/
RUN upx -9 /tmp/cleardns
WORKDIR /tmp/
RUN strip cleardns && upx -9 cleardns
FROM ${ALPINE} AS build
RUN apk add xz

3
assets/china-ip.py

@ -22,7 +22,8 @@ for ipAddr in ipAddrs:
try:
ip = IP(ipAddr) # ip format check
(ipv4 if ip.version() == 4 else ipv6).add(ip)
except: pass
except:
pass
release = [('%s' if '/' in str(ip) else '%s/32') % str(ip) for ip in ipv4] # format into CIDR
release += [('%s' if '/' in str(ip) else '%s/128') % str(ip) for ip in ipv6]

10
include/bcrypt/bcrypt.h

@ -60,10 +60,16 @@ int bcrypt_hashpw(const char *passwd, const char salt[BCRYPT_HASHSIZE],
int bcrypt_checkpw(const char *passwd, const char hash[BCRYPT_HASHSIZE]);
/*
* This function expects a string and return bcrypt result (random salt)
* This function expects a string and return bcrypt result with random salt.
*/
char* bcrypt_cal(const char *data);
char* bcrypt_hash(const char *data);
/*
* This function verifies that the data matches the hash value.
*/
int bcrypt_verify(const char *data, const char *hash);
/*
* Brief Example

3
include/common/json.h

@ -4,7 +4,7 @@
#include <stdint.h>
#include "cJSON.h"
char* to_json(const char *content);
char* to_json_format(const char *content);
uint8_t is_json_suffix(const char *file_name);
cJSON* json_field_get(cJSON *entry, const char *key);
void json_field_replace(cJSON *entry, const char *key, cJSON *content);
@ -14,6 +14,5 @@ uint8_t json_bool_value(char *caption, cJSON *json);
char* json_string_value(char* caption, cJSON *json);
char** json_string_list_value(char *caption, cJSON *json, char **string_list);
uint32_t** json_uint32_list_value(char *caption, cJSON *json, uint32_t **uint32_list);
void json_string_map_value(char *caption, cJSON *json, char ***key_list, char ***value_list);
#endif

2
include/common/sundry.h

@ -3,8 +3,8 @@
#include <stdint.h>
char* show_bool(uint8_t value);
uint8_t check_port(uint16_t port);
const char* show_bool(uint8_t value);
char* string_load(const char *fmt, ...);
char* uint32_to_string(uint32_t number);
char* string_join(const char *base, const char *add);

1
include/common/system.h

@ -8,7 +8,6 @@ int run_command(const char *command);
void create_folder(const char *folder);
uint8_t is_file_exist(const char *file);
void save_file(const char *file, const char *content);
void download_file(const char *file, const char *url);
void save_string_list(const char *file, char **string_list);
void file_append(const char *base_file, const char *append_file);

2
include/constant.h → include/constant.h.in

@ -13,7 +13,7 @@
#define RESTART_DELAY 1
#define DIVERTER_TIMEOUT 6
#define VERSION "1.3.3"
#define VERSION "@VERSION@"
#define CONFIG_FILE "cleardns.yml"
#define DNSPROXY_BIN "dnsproxy"

6
include/loader/config.h

@ -2,6 +2,7 @@
#define CONFIG_H_
#include <stdint.h>
#include "assets.h"
typedef struct {
uint16_t port;
@ -34,10 +35,9 @@ typedef struct {
} adguard_config;
typedef struct {
uint8_t disable; // bool value
char *cron;
char **update_file;
char **update_url;
uint8_t disable; // bool value
asset **resources;
} assets_config;
typedef struct {

4
include/loader/loader.h

@ -15,11 +15,13 @@ struct cleardns {
overture *diverter;
adguard *filter;
crontab *crond;
assets *resource;
asset **resource;
};
extern struct cleardns loader;
void load_diverter_assets();
void load_config(const char *config_file);
#endif

11
include/to_json.h

@ -1,12 +1,17 @@
#pragma once
/* Generated with cbindgen:0.23.0 */
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* Free the exported c-style string.
*/
void free_rust_string(const char *ptr);
const char *to_json_ffi(const char *content);
/**
* Format the input text into JSON format and return a c-style string, or return
* `NULL` if an error occurs.
*/
const char *to_json(const char *content);

28
include/utils/assets.h

@ -1,15 +1,29 @@
#ifndef ASSETS_H_
#define ASSETS_H_
typedef struct {
char **update_file;
char **update_url;
} assets;
#include <stdint.h>
extern char **custom_gfwlist;
extern char **custom_china_ip;
extern char **custom_chinalist;
assets* assets_init();
void assets_free(assets *info);
void assets_load(assets *info);
typedef struct {
char *file; // string
char **sources; // string list
} asset;
void assets_extract();
void assets_load(asset **info);
asset** assets_init();
asset* asset_init(const char *name);
void assets_dump(asset **asset_list);
void assets_free(asset **asset_list);
uint32_t assets_size(asset **asset_list);
void assets_append(asset ***asset_list, asset *res);
/// Rust assets interface
void assets_log_init(uint8_t verbose);
uint8_t asset_update(const char *file, char *const *sources, const char *assets_dir);
#endif

7
src/CMakeLists.txt

@ -7,7 +7,12 @@ include_directories(${PROJECT_SOURCE_DIR}/include/bcrypt)
include_directories(${PROJECT_SOURCE_DIR}/include/common)
include_directories(${PROJECT_SOURCE_DIR}/include/loader)
link_directories(${PROJECT_SOURCE_DIR}/src/to-json/target/release)
link_directories(${PROJECT_SOURCE_DIR}/src/target/release)
configure_file(
${PROJECT_SOURCE_DIR}/include/constant.h.in
${PROJECT_SOURCE_DIR}/include/constant.h
)
add_subdirectory(utils)
add_subdirectory(applet)

1275
src/Cargo.lock

File diff suppressed because it is too large

6
src/Cargo.toml

@ -0,0 +1,6 @@
[workspace]
members = [
"assets",
"to-json"
]

21
src/applet/adguard.c

@ -46,9 +46,26 @@ char *adguard_config(adguard *info, const char *raw_config) { // modify adguard
log_fatal("AdGuardHome configure error");
}
char *password = NULL;
cJSON *user_passwd = cJSON_GetObjectItem(cJSON_GetArrayItem(
cJSON_GetObjectItem(json, "users"), 0), "password");
if (cJSON_IsString(user_passwd)) {
char *hash_val = user_passwd->valuestring;
log_debug("Legacy hash value -> `%s`", hash_val);
if (bcrypt_verify(info->password, hash_val)) {
log_debug("Legacy hash value verify success");
password = strdup(hash_val);
} else {
log_debug("Legacy hash value verify failed");
}
}
if (password == NULL) { // password hash not ready
password = bcrypt_hash(info->password);
}
log_debug("AdGuardHome password -> `%s`", password);
cJSON *user_config = cJSON_CreateObject(); // setting up username and password
cJSON *users_config = cJSON_CreateArray();
char *password = bcrypt_cal(info->password);
cJSON_AddItemToObject(user_config, "name", cJSON_CreateString(info->username));
cJSON_AddItemToObject(user_config, "password", cJSON_CreateString(password));
cJSON_AddItemToArray(users_config, user_config);
@ -93,7 +110,7 @@ process* adguard_load(adguard *info, const char *dir) { // load adguard options
adguard_config_ret = adguard_config(info, "{}"); // begin with empty json
} else { // configure exist -> modify
char *adguard_config_content = read_file(adguard_config_file);
char *adguard_config_json = to_json(adguard_config_content);
char *adguard_config_json = to_json_format(adguard_config_content);
adguard_config_ret = adguard_config(info, adguard_config_json);
free(adguard_config_content);
free(adguard_config_json);

15
src/assets/Cargo.toml

@ -0,0 +1,15 @@
[package]
name = "assets"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
env_logger = "0.10.0"
log = "0.4.17"
reqwest = { version = "0.11.14", features = ["deflate", "gzip", "brotli"] }
tokio = { version = "1.26.0", features = ["macros", "rt-multi-thread"] }

3
src/assets/cbindgen.toml

@ -0,0 +1,3 @@
language = "C"
pragma_once = true
include_version = false

193
src/assets/src/fetch.rs

@ -0,0 +1,193 @@
use std::fs::File;
use std::io::Read;
use reqwest::Client;
use std::time::Duration;
use log::{debug, info, warn};
use std::collections::HashSet;
/// Http download timeout limit
const TIMEOUT: u64 = 120;
/// Cut text line by line and remove invisible characters on both sides.
fn asset_tidy(data: &str) -> Vec<String> {
data.lines()
.map(|x| String::from(x.trim()))
.filter(|x| !x.is_empty())
.collect()
}
/// Remove duplicate elements from an array.
fn remove_dup(data: &Vec<String>) -> Vec<String> {
let mut result: Vec<String> = vec![];
let mut tmp: HashSet<String> = HashSet::new();
for val in data {
if tmp.insert(val.clone()) { // value already exist
result.push(val.clone());
}
}
result
}
/// Download the specified text file and organize it into a String array.
async fn http_fetch(url: &str, timeout: u64) -> Result<Vec<String>, String> {
let client = Client::builder()
.timeout(Duration::from_secs(timeout))
.build().unwrap();
debug!("Start downloading `{}`", url);
match client.get(url).send().await {
Ok(response) => {
match response.text().await {
Ok(text) => {
info!("Remote file `{}` download success", url);
Ok(asset_tidy(&text))
},
Err(err) => Err(format!("http content error: {}", err))
}
},
Err(err) => Err(format!("http request failed: {}", err))
}
}
/// Read the specified text file and organize it into a String array.
async fn local_fetch(path: &str) -> Result<Vec<String>, String> {
match File::open(path) {
Ok(mut file) => {
let mut text = String::new();
if let Err(err) = file.read_to_string(&mut text) {
return Err(format!("file `{}` read failed: {}", path, err));
};
info!("Local file `{}` read success", path);
Ok(asset_tidy(&text))
},
Err(err) => Err(format!("file `{}` open failed: {}", path, err)),
}
}
/// Get multiple resource data and merge them.
pub(crate) async fn asset_fetch(name: &str, sources: &Vec<String>) -> Option<Vec<String>> {
let is_remote = |src: &str| {
src.starts_with("http://") || src.starts_with("https://")
};
let mut contents: Vec<Vec<String>> = vec![];
for source in sources {
contents.push(match if is_remote(&source) {
http_fetch(source.trim(), TIMEOUT).await // from remote text file
} else {
local_fetch(source.trim()).await // from local text file
} {
Ok(data) => {
debug!("Asset source `{}` fetch success with {} items", source.trim(), data.len());
data
},
Err(err) => {
warn!("Asset source `{}` fetch failed: {}", source.trim(), err);
return None; // stop fetch process
}
});
}
let contents = remove_dup(&contents
.into_iter()
.flatten()
.collect::<Vec<String>>());
info!("Asset `{}` fetch complete with {} items", name, contents.len());
Some(contents)
}
#[cfg(test)]
mod tests {
use std::fs;
use std::pin::Pin;
use std::future::Future;
use std::fs::OpenOptions;
use std::io::Write;
use super::{asset_tidy, remove_dup};
use super::{http_fetch, local_fetch, asset_fetch};
const TEST_DATA: &str = "\tabc \n123 \n 456\r\nabc\n\n789 ";
#[test]
fn basic() {
assert_eq!(asset_tidy(TEST_DATA), vec![
String::from("abc"),
String::from("123"),
String::from("456"),
String::from("abc"),
String::from("789"),
]);
assert_eq!(remove_dup(&asset_tidy(TEST_DATA)), vec![
String::from("abc"),
String::from("123"),
String::from("456"),
String::from("789"),
]);
}
fn run_async<T>(func: Pin<Box<impl Future<Output=T>>>) {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(func);
}
fn gen_test_file() {
let mut fp = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open("/tmp/assets_test_file")
.unwrap();
fp.write_all(TEST_DATA.as_ref()).expect("test file create error");
}
#[test]
fn asset() {
// test http_fetch function
run_async(Box::pin(async {
assert!(http_fetch("invalid url", 10).await.is_err());
assert_eq!(
http_fetch("https://gstatic.com/generate_204", 10).await,
Ok(vec![])
);
}));
// test local_fetch function
gen_test_file();
run_async(Box::pin(async {
assert!(local_fetch("/").await.is_err());
assert_eq!(
local_fetch("/tmp/assets_test_file").await,
Ok(vec![
String::from("abc"),
String::from("123"),
String::from("456"),
String::from("abc"),
String::from("789"),
])
);
}));
// test combine asset_fetch function
run_async(Box::pin(async {
assert!(
asset_fetch("", &vec![]).await.unwrap().is_empty()
);
assert!(
asset_fetch("", &vec![String::from("...")]).await.is_none()
);
assert_eq!(
asset_fetch("", &vec![
String::from("/tmp/assets_test_file"),
String::from("https://gstatic.com/generate_204")
]).await,
Some(vec![
String::from("abc"),
String::from("123"),
String::from("456"),
String::from("789"),
])
);
}));
fs::remove_file("/tmp/assets_test_file").expect("test file delete error");
}
}

97
src/assets/src/ffi.rs

@ -0,0 +1,97 @@
use std::io::Write;
use std::env::set_var;
use std::path::PathBuf;
use std::fs::OpenOptions;
use std::os::raw::c_char;
use log::{debug, trace, warn};
use std::ffi::{CStr, CString};
use crate::fetch::asset_fetch;
/// Compatible with C89 bool value.
const TRUE: u8 = 1;
const FALSE: u8 = 0;
/// Load c-style string from `char *` pointer.
unsafe fn load_c_string(ptr: *const c_char) -> String {
CString::from(CStr::from_ptr(ptr))
.into_string()
.unwrap()
}
/// Load c-style string list from `char **` pointer.
unsafe fn load_c_string_list(ptr: *const *const c_char) -> Vec<String> {
let mut index = 0;
let mut string_list: Vec<String> = vec![];
while *ptr.offset(index) != std::ptr::null() { // traverse until `NULL`
string_list.push(load_c_string(*ptr.offset(index)));
index += 1;
}
string_list
}
/// Initialize the rust module log, enable trace level log when verbose is not `0`.
#[no_mangle]
pub unsafe extern "C" fn assets_log_init(verbose: u8) {
if verbose == FALSE { // bool value `FALSE`
set_var("RUST_LOG", "info");
} else {
set_var("RUST_LOG", "trace");
}
env_logger::init();
}
/// Update the specified resource file, return `0` on failure.
#[no_mangle]
#[tokio::main]
pub async unsafe extern "C" fn asset_update(
file: *const c_char, sources: *const *const c_char, assets_dir: *const c_char) -> u8 {
let name = load_c_string(file); // import c-style string
let sources = load_c_string_list(sources);
let assets_dir = load_c_string(assets_dir);
trace!("Working folder is `{}`", assets_dir);
trace!("Updating `{}` from {:?}", name, sources);
let assets_dir = PathBuf::from(&assets_dir);
let is_remote = |src: &str| {
src.starts_with("http://") || src.starts_with("https://")
};
let sources = sources.iter()
.map(|src| {
if !is_remote(&src) && !src.starts_with("/") { // local relative path
let file_path = assets_dir.join(src);
String::from(file_path.to_str().unwrap())
} else {
src.clone()
}
})
.collect::<Vec<String>>();
let file = String::from(assets_dir.join(&name).to_str().unwrap());
debug!("Asset sources -> {:?}", sources);
debug!("Asset target -> `{}`", file);
match asset_fetch(&name, &sources).await {
Some(data) => {
let mut content = String::new();
let _ = data.iter().map(|item| {
content.push_str(item);
content.push('\n');
}).collect::<Vec<()>>();
match OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&file) { // open target file
Ok(mut fp) => {
match fp.write_all(content.as_ref()) {
Err(err) => warn!("File `{}` save error: {}", file, err),
_ => debug!("File `{}` save success", file),
}
},
Err(err) => warn!("File `{}` open failed: {}", file, err),
};
TRUE // asset fetch success
},
None => FALSE, // asset fetch failed
}
}

2
src/assets/src/lib.rs

@ -0,0 +1,2 @@
mod ffi;
mod fetch;

10
src/bcrypt/hash.c

@ -1,8 +1,9 @@
#include <stdlib.h>
#include "bcrypt.h"
#include "logger.h"
#include "constant.h"
char* bcrypt_cal(const char *data) {
char* bcrypt_hash(const char *data) {
char salt[BCRYPT_HASHSIZE];
log_debug("BCrypt data -> `%s`", data);
if (bcrypt_gensalt(10, salt)) {
@ -17,3 +18,10 @@ char* bcrypt_cal(const char *data) {
log_debug("BCrypt hash -> `%s`", hash);
return hash;
}
int bcrypt_verify(const char *data, const char *hash) {
if (bcrypt_checkpw(data, hash) == 0) {
return TRUE;
}
return FALSE;
}

4
src/cleardns.c

@ -5,7 +5,6 @@
#include <sys/wait.h>
#include "loader.h"
#include "logger.h"
#include "sundry.h"
#include "system.h"
#include "assets.h"
#include "adguard.h"
@ -67,6 +66,9 @@ void init(int argc, char *argv[]) { // return config file
void cleardns() { // cleardns service
if (settings.verbose || settings.debug) {
LOG_LEVEL = LOG_DEBUG; // enable debug log level
assets_log_init(TRUE);
} else {
assets_log_init(FALSE);
}
create_folder(EXPOSE_DIR);
create_folder(WORK_DIR);

29
src/common/json.c

@ -17,20 +17,18 @@ uint8_t is_json_suffix(const char *file_name) { // whether file name end with `.
return FALSE;
}
char* to_json(const char *content) { // convert JSON / TOML / YAML to json format (failed -> NULL)
const char *json_string = to_json_ffi(content); // convert to json format
char *json_content = strdup(json_string); // load string into owner heap
free_rust_string(json_string); // free rust string
if (strlen(json_content) == 0) { // empty string -> convert error
char* to_json_format(const char *content) { // convert JSON / TOML / YAML to json format (failed -> NULL)
const char *json_string = to_json(content); // convert to json format
if (json_string == NULL) {
log_warn("JSON convert error ->\n%s", content);
free(json_content);
return NULL;
return NULL; // convert failed
}
char *json_content = strdup(json_string); // load string into owner heap
free_rust_string(json_string); // free rust string
log_debug("JSON convert result ->\n%s", json_content);
return json_content;
}
cJSON* json_field_get(cJSON *entry, const char *key) { // fetch key from json map (create when key not exist)
cJSON *sub = entry->child;
while (sub != NULL) { // traverse all keys
@ -115,18 +113,3 @@ uint32_t** json_uint32_list_value(char *caption, cJSON *json, uint32_t **uint32_
}
return uint32_list;
}
void json_string_map_value(char *caption, cJSON *json, char ***key_list, char ***value_list) { // json string map
if (!cJSON_IsObject(json)) {
log_fatal("`%s` must be map", caption);
}
json = json->child;
while (json != NULL) { // traverse all json field
if (!cJSON_IsString(json)) {
log_fatal("`%s` must be string-string map", caption);
}
string_list_append(key_list, json->string);
string_list_append(value_list, json->valuestring);
json = json->next;
}
}

4
src/common/sundry.c

@ -10,7 +10,7 @@
#include "constant.h"
#include "structure.h"
char* show_bool(uint8_t value) { // return `true` or `false`
const char* show_bool(uint8_t value) { // return `true` or `false`
if (value) {
return "true";
}
@ -49,7 +49,7 @@ void uint32_list_debug(char *describe, uint32_t **uint32_list) { // show uint32
}
uint8_t check_port(uint16_t port) { // whether port is valid
if (port > 0 && port <= 65535) { // 1 ~ 65535
if (port > 0) { // 1 ~ 65535 (uint16_t <= 65535)
return TRUE;
}
return FALSE;

8
src/common/system.c

@ -95,11 +95,3 @@ void save_string_list(const char *file, char **string_list) { // save string lis
fclose(fp);
log_debug("Save `%s` success", file);
}
void download_file(const char *file, const char *url) { // download file
log_debug("Download file `%s` -> %s", file, url);
char *download_cmd = string_load("wget -T 8 -O %s %s", file, url);
if (run_command(download_cmd)) {
log_warn("File `%s` download failed", url);
}
}

12
src/loader/config.c

@ -41,8 +41,7 @@ cleardns_config* config_init() { // init config struct of cleardns
config->assets.disable = FALSE;
config->assets.cron = strdup(UPDATE_CRON);
config->assets.update_file = string_list_init();
config->assets.update_url = string_list_init();
config->assets.resources = assets_init();
config->reject = uint32_list_init();
config->hosts = string_list_init();
@ -85,10 +84,8 @@ void config_dump(cleardns_config *config) { // dump config info of cleardns
log_debug("Assets disable -> %s", show_bool(config->assets.disable));
log_debug("Assets update cron -> `%s`", config->assets.cron);
for (char **file = config->assets.update_file; *file != NULL; ++file) { // show string mapping
char **url = file - config->assets.update_file + config->assets.update_url;
log_debug("Assets file `%s` -> %s", *file, *url);
}
log_debug("Assets with %d resource items", assets_size(config->assets.resources));
assets_dump(config->assets.resources);
uint32_list_debug("DNS reject type", config->reject);
string_list_debug("Domain TTL", config->ttl);
@ -113,8 +110,7 @@ void config_free(cleardns_config *config) { // free config struct of cleardns
free(config->adguard.password);
free(config->assets.cron);
string_list_free(config->assets.update_file);
string_list_free(config->assets.update_url);
assets_free(config->assets.resources);
uint32_list_free(config->reject);
string_list_free(config->hosts);

4
src/loader/default.c

@ -51,13 +51,13 @@ assets:\n\
void load_default_config(const char *config_file) {
if (is_file_exist(config_file)) {
log_debug("Configure file exist -> skip load default");
log_debug("Configure file exist -> skip loading default");
return;
}
log_info("Loading default configure file");
char *config_content = NULL;
if (is_json_suffix(config_file)) { // convert to json format
config_content = to_json(DEFAULT_CONFIG);
config_content = to_json_format(DEFAULT_CONFIG);
} else {
config_content = strdup(DEFAULT_CONFIG);
}

19
src/loader/loader.c

@ -95,6 +95,13 @@ overture* load_diverter(cleardns_config *config) {
free(china_ip);
free(gfwlist);
custom_gfwlist = config->diverter.gfwlist;
custom_china_ip = config->diverter.china_ip;
custom_chinalist = config->diverter.chinalist;
config->diverter.gfwlist = string_list_init();
config->diverter.china_ip = string_list_init();
config->diverter.chinalist = string_list_init();
uint32_list_update(&diverter->reject_type, config->reject);
if (!config->assets.disable) {
assets_extract(); // extract built-in resource
@ -127,11 +134,13 @@ crontab* load_crond(cleardns_config *config) {
return crond;
}
assets* load_assets(cleardns_config *config) {
assets *resource = assets_init();
string_list_update(&resource->update_file, config->assets.update_file);
string_list_update(&resource->update_url, config->assets.update_url);
return resource;
asset** load_assets(cleardns_config *config) {
asset **resources = assets_init();
for (asset **res = config->assets.resources; *res != NULL; ++res) {
assets_append(&resources, *res); // pointer movement
}
*(config->assets.resources) = NULL; // disable old assets list
return resources;
}
void load_config(const char *config_file) { // parser and load cleardns configure

15
src/loader/parser.c

@ -130,7 +130,18 @@ void assets_parser(assets_config *config, cJSON *json) { // assets options parse
config->cron = json_string_value("assets.cron", json);
}
if (!strcmp(json->string, "update")) {
json_string_map_value("assets.update", json, &config->update_file, &config->update_url);
if (!cJSON_IsObject(json)) {
log_fatal("`%s` must be map", "assets.update");
}
cJSON *asset_item = json->child;
while (asset_item != NULL) { // traverse all json field
asset *res = asset_init(asset_item->string);
char *caption = string_join("assets.update.", asset_item->string);
res->sources = json_string_list_value(caption, asset_item, res->sources);
free(caption);
assets_append(&config->resources, res);
asset_item = asset_item->next;
}
}
json = json->next; // next field
}
@ -188,7 +199,7 @@ void config_parser(cleardns_config *config, const char *config_file) {
log_info("Start JSON configure parser");
} else { // YAML or TOML format
log_info("Start configure parser");
char *convert_ret = to_json(config_content);
char *convert_ret = to_json_format(config_content);
if (convert_ret == NULL) { // convert failed
log_fatal("Configure parser error");
}

91
src/to-json/Cargo.lock

@ -1,91 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "serde"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
[[package]]
name = "serde_json"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "to-json"
version = "0.1.0"
dependencies = [
"serde_json",
"serde_yaml",
"toml",
]
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"

8
src/to-json/Cargo.toml

@ -1,5 +1,5 @@
[package]
name = "to-json"
name = "to_json"
version = "0.1.0"
edition = "2021"
@ -9,6 +9,6 @@ crate-type = ["staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde_json = "1.0.89"
serde_yaml = "0.9.14"
toml = "0.5.9"
serde_json = "1.0.94"
serde_yaml = "0.9.19"
toml = "0.7.2"

2
src/to-json/cbindgen.toml

@ -1,3 +1,3 @@
language = "C"
pragma_once = true
include_version = true
include_version = false

135
src/to-json/src/ffi.rs

@ -1,27 +1,130 @@
use crate::json::to_json;
use std::ffi::{c_char, CStr, CString};
use std::os::raw::c_char;
use std::ffi::{CStr, CString};
use crate::parser::{parser, Value};
fn to_c_string(string: String) -> *const c_char { // fetch c-style ptr of string
CString::new(string).unwrap().into_raw()
/// Load c-style string from `const char *` pointer.
#[inline]
unsafe fn load_c_string(ptr: *const c_char) -> String {
CString::from(CStr::from_ptr(ptr))
.into_string()
.unwrap()
}
unsafe fn load_c_string(ptr: *const c_char) -> String { // load string from c-style ptr
String::from(
CStr::from_ptr(ptr).to_str().unwrap()
)
/// Export c-style string as `const char *` pointer.
/// # NOTE
/// The exported string cannot be freed by the c language `free(void *)` function,
/// but should use the `free_rust_string` callback function, if this interface is
/// not called, a memory leak will occur.
#[inline]
fn export_c_string(string: String) -> *const c_char {
CString::new(string).unwrap().into_raw()
}
/// Free the exported c-style string.
#[no_mangle]
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) { // free string memory
let _ = CString::from_raw(ptr as *mut _);
pub unsafe extern "C" fn free_rust_string(ptr: *const c_char) {
let _ = CString::from_raw(ptr as *mut _); // reclaim rust ownership
}
/// Deserialize text content and serialize to JSON format.
fn json_format(content: &str) -> Option<String> {
let result = match parser(&content) {
Ok(value) => match value {
Value::JSON(json) => serde_json::to_string(&json),
Value::YAML(yaml) => serde_json::to_string(&yaml),
Value::TOML(toml) => serde_json::to_string(&toml),
},
_ => return None,
};
match result {
Ok(data) => Some(data),
Err(_) => None,
}
}
/// Format the input text into JSON format and return a c-style string, or return
/// `NULL` if an error occurs.
#[no_mangle]
pub unsafe extern "C" fn to_json_ffi(content: *const c_char) -> *const c_char {
pub unsafe extern "C" fn to_json(content: *const c_char) -> *const c_char {
let content = load_c_string(content);
let result = match to_json(&content) { // convert to JSON format
Some(data) => data,
None => String::new(), // convert failed -> empty string
};
to_c_string(result) // return c-style ptr
match json_format(&content) {
Some(data) => export_c_string(data),
None => std::ptr::null(),
}
}
#[cfg(test)]
mod tests {
use super::json_format;
const JSON_TEST_STR: &str = r#"
{
"int": 123,
"bool": true,
"float": 3.141592,
"string": "json test",
"array": [1, 2, 3, 4, 5],
"object": {
"sub": "test"
}
}
"#;
const YAML_TEST_STR: &str = r#"
int: 123
bool: true
float: 3.141592
string: "json test"
array:
- 1
- 2
- 3
- 4
- 5
object:
sub: test
"#;
const TOML_TEST_STR: &str = r#"
int = 123
bool = true
float = 3.141592
string = "json test"
array = [ 1, 2, 3, 4, 5 ]
[object]
sub = "test"
"#;
#[inline]
fn format(raw: &str) -> String {
match json_format(raw) {
Some(data) => data,
None => panic!("format error"),
}
}
#[test]
fn json_input() {
assert_eq!(
format(JSON_TEST_STR),
format(&json_format(JSON_TEST_STR).unwrap()),
);
}
#[test]
fn yaml_input() {
assert_eq!(
format(JSON_TEST_STR),
format(&json_format(YAML_TEST_STR).unwrap()),
);
}
#[test]
fn toml_input() {
assert_eq!(
format(JSON_TEST_STR),
format(&json_format(TOML_TEST_STR).unwrap()),
);
}
}

21
src/to-json/src/json.rs

@ -1,21 +0,0 @@
use serde_json as json;
use crate::parser::{parser, Value};
fn json_convert(content: &str) -> Result<String, String> { // convert to JSON format
let data = match parser(content)? {
Value::JSON(_json) => json::to_string(&_json),
Value::YAML(_yaml) => json::to_string(&_yaml),
Value::TOML(_toml) => json::to_string(&_toml),
};
match data {
Ok(data) => Ok(data),
Err(err) => Err(err.to_string()),
}
}
pub fn to_json(content: &str) -> Option<String> { // to JSON string
match json_convert(content) {
Ok(data) => Some(data),
Err(_) => None, // convert failed
}
}

2
src/to-json/src/lib.rs

@ -1,4 +1,2 @@
mod ffi;
mod json;
mod tests;
mod parser;

62
src/to-json/src/parser.rs

@ -8,36 +8,88 @@ pub enum Value {
TOML(toml::Value),
}
fn json_parser(content: &str) -> Option<json::Value> { // parse json content
/// Deserialize text content into JSON format.
fn json_parser(content: &str) -> Option<json::Value> {
match json::from_str::<json::Value>(content) {
Ok(result) => Some(result),
Err(_) => None,
}
}
fn yaml_parser(content: &str) -> Option<yaml::Value> { // parse yaml content
/// Deserialize text content into YAML format.
fn yaml_parser(content: &str) -> Option<yaml::Value> {
match yaml::from_str::<yaml::Value>(content) {
Ok(result) => Some(result),
Err(_) => None,
}
}
fn toml_parser(content: &str) -> Option<toml::Value> { // parse toml content
/// Deserialize text content into TOML format.
fn toml_parser(content: &str) -> Option<toml::Value> {
match toml::from_str::<toml::Value>(content) {
Ok(result) => Some(result),
Err(_) => None,
}
}
pub fn parser(content: &str) -> Result<Value, String> {
/// Try to deserialize the text in JSON, TOML and YAML format.
pub fn parser(content: &str) -> Result<Value, &'static str> {
match json_parser(content) { // try JSON format
Some(data) => Ok(Value::JSON(data)),
None => match toml_parser(content) { // try TOML format
Some(data) => Ok(Value::TOML(data)),
None => match yaml_parser(content) { // try YAML format
Some(data) => Ok(Value::YAML(data)),
None => Err(String::from("unknown input format")),
None => Err("unknown input format"),
}
}
}
}
#[cfg(test)]
mod tests {
use super::Value;
use super::parser;
use super::json_parser;
use super::yaml_parser;
use super::toml_parser;
const JSON_STR: &str = "{\"test\": \"ok\"}";
const YAML_STR: &str = "test: ok";
const TOML_STR: &str = "test = \"ok\"";
#[test]
fn json() {
assert!(json_parser("").is_none()); // parse invalid text
assert!(json_parser(JSON_STR).is_some());
}
#[test]
fn yaml() {
assert!(yaml_parser("").is_none()); // parse invalid text
assert!(yaml_parser(YAML_STR).is_some());
}
#[test]
fn toml() {
assert!(toml_parser(".").is_none()); // parse invalid text
assert!(toml_parser(TOML_STR).is_some());
}
#[test]
fn global() {
match parser(JSON_STR).unwrap() {
Value::JSON(_) => (),
_ => panic!("JSON parser error"),
};
match parser(YAML_STR).unwrap() {
Value::YAML(_) => (),
_ => panic!("YAML parser error"),
};
match parser(TOML_STR).unwrap() {
Value::TOML(_) => (),
_ => panic!("TOML parser error"),
};
assert!(parser("\0").is_err()); // parse invalid text
}
}

80
src/to-json/src/tests.rs

@ -1,80 +0,0 @@
use crate::json::to_json;
#[allow(dead_code)]
const JSON_TEST_CONTENT: &str = r#"
{
"int": 123,
"bool": true,
"float": 3.141592,
"string": "json test",
"array": [1, 2, 3, 4, 5],
"object": {
"sub": "test"
}
}
"#;
#[allow(dead_code)]
const YAML_TEST_CONTENT: &str = r#"
int: 123
bool: true
float: 3.141592
string: "json test"
array:
- 1
- 2
- 3
- 4
- 5
object:
sub: test
"#;
#[allow(dead_code)]
const TOML_TEST_CONTENT: &str = r#"
int = 123
bool = true
float = 3.141592
string = "json test"
array = [ 1, 2, 3, 4, 5 ]
[object]
sub = "test"
"#;
mod tests {
use super::*;
#[allow(dead_code)]
fn format_json(raw: &str) -> String {
match to_json(raw) {
Some(data) => data,
None => panic!("JSON format error"),
}
}
#[test]
fn json_to_json() {
assert_eq!(
format_json(JSON_TEST_CONTENT),
format_json(&to_json(JSON_TEST_CONTENT).unwrap()),
);
}
#[test]
fn yaml_to_json() {
assert_eq!(
format_json(JSON_TEST_CONTENT),
format_json(&to_json(YAML_TEST_CONTENT).unwrap()),
);
}
#[test]
fn toml_to_json() {
assert_eq!(
format_json(JSON_TEST_CONTENT),
format_json(&to_json(TOML_TEST_CONTENT).unwrap()),
);
}
}

1
src/utils/CMakeLists.txt

@ -1,3 +1,4 @@
cmake_minimum_required(VERSION 2.8.12)
add_library(utils cJSON.c assets.c logger.c process.c)
target_link_libraries(utils assets ssl crypto)

112
src/utils/assets.c

@ -1,62 +1,106 @@
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "assets.h"
#include "loader.h"
#include "logger.h"
#include "sundry.h"
#include "system.h"
#include "constant.h"
#include "structure.h"
#include "assets.h"
assets update;
asset **update_info;
void assets_update();
void assets_dump(assets *info);
char **custom_gfwlist;
char **custom_china_ip;
char **custom_chinalist;
void assets_update_entry();
void extract(const char *file);
void assets_free(assets *info) { // free assets mapping
string_list_free(info->update_file);
string_list_free(info->update_url);
free(info);
asset* asset_init(const char *name) { // init asset item
asset *res = (asset *)malloc(sizeof(asset));
res->file = strdup(name);
res->sources = string_list_init(); // with multi sources
return res;
}
assets* assets_init() { // init assets mapping
assets *info = (assets *)malloc(sizeof(assets));
info->update_file = string_list_init();
info->update_url = string_list_init();
return info;
asset** assets_init() { // init assets list
asset **asset_list = (asset **)malloc(sizeof(asset *));
*asset_list = NULL; // list end sign
return asset_list;
}
void assets_dump(assets *info) { // show assets mapping in debug log
for (char **file = info->update_file; *file != NULL; ++file) {
char **url = file - info->update_file + info->update_url;
log_info("Asset `%s` -> %s", *file, *url);
void assets_free(asset **asset_list) { // free assets list
for (asset **res = asset_list; *res != NULL; ++res) {
string_list_free((*res)->sources);
free((*res)->file);
free(*res);
}
free(asset_list);
}
void assets_load(assets *info) { // load assets mapping
update.update_file = string_list_init();
update.update_url = string_list_init();
string_list_update(&update.update_file, info->update_file);
string_list_update(&update.update_url, info->update_url);
signal(SIGALRM, assets_update); // receive SIGALRM signal
assets_dump(&update);
uint32_t assets_size(asset **asset_list) { // get size of assets list
uint32_t num = 0;
while(asset_list[num++] != NULL); // get list size
return num - 1;
}
void assets_update() { // update all assets
if (!string_list_len(update.update_file)) { // empty assets mapping
void assets_dump(asset **asset_list) { // dump assets list content into debug log
for (asset **res = asset_list; *res != NULL; ++res) { // iterate over each item
char *sources = string_list_dump((*res)->sources);
log_debug("Asset item `%s` -> %s", (*res)->file, sources);
free(sources);
}
}
void assets_append(asset ***asset_list, asset *res) { // push asset item for assets list
uint32_t len = assets_size(*asset_list);
*asset_list = (asset **)realloc(*asset_list, sizeof(asset *) * (len + 2)); // extend asset list
(*asset_list)[len] = res;
(*asset_list)[len + 1] = NULL; // list end sign
}
void assets_load(asset **info) { // load assets list
update_info = assets_init();
for (asset **res = info; *res != NULL; ++res) {
assets_append(&update_info, *res); // pointer movement
}
*info = NULL; // disable old assets list
assets_dump(update_info);
log_info("Remote assets load success");
signal(SIGALRM, assets_update_entry); // receive SIGALRM signal
}
void assets_update_entry() { // receive SIGALRM for update all assets
if (assets_size(update_info) == 0) { // empty assets list
log_info("Skip update assets");
return;
}
log_info("Start assets update");
for (char **file = update.update_file; *file != NULL; ++file) {
char **url = file - update.update_file + update.update_url;
char *asset_file = string_join(ASSETS_DIR, *file);
log_info("Update asset `%s` -> %s", asset_file, *url);
download_file(asset_file, *url); // download asset from url
free(asset_file);
log_info("Start updating assets");
for (asset **res = update_info; *res != NULL; ++res) {
char *content = string_list_dump((*res)->sources);
log_debug("Updating `%s` -> %s", (*res)->file, content);
if (asset_update((*res)->file, (*res)->sources, ASSETS_DIR)) {
log_debug("Asset `%s` update success", (*res)->file);
} else {
log_warn("Asset `%s` update failed", (*res)->file);
}
free(content);
}
log_info("Restart overture");
char *gfwlist = string_join(WORK_DIR, ASSET_GFW_LIST);
char *china_ip = string_join(WORK_DIR, ASSET_CHINA_IP);
char *chinalist = string_join(WORK_DIR, ASSET_CHINA_LIST);
save_string_list(gfwlist, custom_gfwlist);
save_string_list(china_ip, custom_china_ip);
save_string_list(chinalist, custom_chinalist);
free(chinalist);
free(china_ip);
free(gfwlist);
load_diverter_assets(); // load assets data into `WORK_DIR`
log_info("Restart overture to apply new assets");
run_command("pgrep overture | xargs kill"); // restart overture
log_info("Assets update complete");
}

11
src/utils/process.c

@ -55,10 +55,17 @@ void process_exec(process *proc) {
log_perror("%s fork error -> ", proc->name);
server_exit(EXIT_FORK_ERROR);
} else if (pid == 0) { // child process
log_debug("Subprocess fork success -> PID = %d", getpid());
if (chdir(proc->cwd)) { // change working directory
log_perror("%s with invalid cwd `%s` -> ", proc->name, proc->cwd);
exit(EXIT_EXEC_ERROR);
}
pid_t sid = setsid(); // create new session -> detach current terminal
if (sid == -1) { // session create failed
log_warn("Subprocess failed to create new session");
} else {
log_debug("Subprocess at new session -> SID = %d", sid);
}
prctl(PR_SET_PDEATHSIG, SIGKILL); // child process die with father process
if (execvpe(*(proc->cmd), proc->cmd, proc->env) < 0) {
log_perror("%s exec error -> ", proc->name);
@ -152,7 +159,7 @@ void get_sub_exit() { // catch child process exit
return;
}
int status;
log_debug("Handle SIGCHLD start");
log_debug("Handle SIGCHLD begin");
for (process **proc = process_list; *proc != NULL; ++proc) {
if ((*proc)->pid == 0) {
continue; // skip not running process
@ -177,7 +184,7 @@ void get_sub_exit() { // catch child process exit
server_exit(EXIT_WAIT_ERROR);
} else if (wait_ret) { // process exit
char *exit_msg = get_exit_msg(status);
log_debug("Sub-process (PID = %d) -> %s", wait_ret, exit_msg);
log_debug("Subprocess (PID = %d) -> %s", wait_ret, exit_msg);
free(exit_msg);
}
log_debug("Handle SIGCHLD complete");

Loading…
Cancel
Save