mirror of
				https://github.com/topjohnwu/Magisk.git
				synced 2025-10-31 08:44:07 +00:00 
			
		
		
		
	Add magiskhide list management
This commit is contained in:
		| @@ -29,6 +29,7 @@ LOCAL_SRC_FILES := \ | |||||||
| 	magiskhide/hide_daemon.c \ | 	magiskhide/hide_daemon.c \ | ||||||
| 	magiskhide/proc_monitor.c \ | 	magiskhide/proc_monitor.c \ | ||||||
| 	magiskhide/pre_process.c \ | 	magiskhide/pre_process.c \ | ||||||
|  | 	magiskhide/list_manager.c \ | ||||||
| 	magiskpolicy/magiskpolicy.c \ | 	magiskpolicy/magiskpolicy.c \ | ||||||
| 	magiskpolicy/rules.c \ | 	magiskpolicy/rules.c \ | ||||||
| 	magiskpolicy/sepolicy.c \ | 	magiskpolicy/sepolicy.c \ | ||||||
|   | |||||||
| @@ -35,10 +35,10 @@ static void request_handler(int client) { | |||||||
| 		stop_magiskhide(client); | 		stop_magiskhide(client); | ||||||
| 		break; | 		break; | ||||||
| 	case ADD_HIDELIST: | 	case ADD_HIDELIST: | ||||||
| 		// TODO: Add hidelist | 		add_hide_list(client); | ||||||
| 		break; | 		break; | ||||||
| 	case RM_HIDELIST: | 	case RM_HIDELIST: | ||||||
| 		// TODO: Remove hidelist | 		rm_hide_list(client); | ||||||
| 		break; | 		break; | ||||||
| 	case SUPERUSER: | 	case SUPERUSER: | ||||||
| 		su_daemon_receiver(client); | 		su_daemon_receiver(client); | ||||||
| @@ -135,7 +135,7 @@ void start_daemon() { | |||||||
| 	// Start log monitor | 	// Start log monitor | ||||||
| 	monitor_logs(); | 	monitor_logs(); | ||||||
|  |  | ||||||
| 	LOGI("Magisk v" xstr(VERSION) " daemon started\n"); | 	LOGI("Magisk v" xstr(MAGISK_VERSION) " daemon started\n"); | ||||||
|  |  | ||||||
| 	// Unlock all blocks for rw | 	// Unlock all blocks for rw | ||||||
| 	unlock_blocks(); | 	unlock_blocks(); | ||||||
|   | |||||||
| @@ -55,6 +55,8 @@ void late_start(int client); | |||||||
|  |  | ||||||
| void launch_magiskhide(int client); | void launch_magiskhide(int client); | ||||||
| void stop_magiskhide(int client); | void stop_magiskhide(int client); | ||||||
|  | void add_hide_list(int client); | ||||||
|  | void rm_hide_list(int client); | ||||||
|  |  | ||||||
| /************* | /************* | ||||||
|  * Superuser * |  * Superuser * | ||||||
|   | |||||||
| @@ -83,9 +83,8 @@ int hide_daemon() { | |||||||
| 		close(fd); | 		close(fd); | ||||||
|  |  | ||||||
| 		snprintf(buffer, sizeof(buffer), "/proc/%d/mounts", pid); | 		snprintf(buffer, sizeof(buffer), "/proc/%d/mounts", pid); | ||||||
| 		fp = xfopen(buffer, "r"); |  | ||||||
| 		vec_init(&mount_list); | 		vec_init(&mount_list); | ||||||
| 		file_to_vector(&mount_list, fp); | 		file_to_vector(buffer, &mount_list); | ||||||
|  |  | ||||||
| 		// Find the cache block name if not found yet | 		// Find the cache block name if not found yet | ||||||
| 		if (strlen(cache_block) == 0) { | 		if (strlen(cache_block) == 0) { | ||||||
| @@ -109,10 +108,9 @@ int hide_daemon() { | |||||||
| 		vec_destroy(&mount_list); | 		vec_destroy(&mount_list); | ||||||
|  |  | ||||||
| 		// Re-read mount infos | 		// Re-read mount infos | ||||||
| 		fseek(fp, 0, SEEK_SET); | 		snprintf(buffer, sizeof(buffer), "/proc/%d/mounts", pid); | ||||||
| 		vec_init(&mount_list); | 		vec_init(&mount_list); | ||||||
| 		file_to_vector(&mount_list, fp); | 		file_to_vector(buffer, &mount_list); | ||||||
| 		fclose(fp); |  | ||||||
|  |  | ||||||
| 		// Unmount loop mounts | 		// Unmount loop mounts | ||||||
| 		vec_for_each_r(&mount_list, line) { | 		vec_for_each_r(&mount_list, line) { | ||||||
|   | |||||||
							
								
								
									
										130
									
								
								jni/magiskhide/list_manager.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								jni/magiskhide/list_manager.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | /* list_manager.c - Hide list management | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include <string.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <pthread.h> | ||||||
|  |  | ||||||
|  | #include "magisk.h" | ||||||
|  | #include "utils.h" | ||||||
|  | #include "daemon.h" | ||||||
|  | #include "magiskhide.h" | ||||||
|  |  | ||||||
|  | int add_list(char *proc) { | ||||||
|  | 	if (!hideEnabled) | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
|  | 	char *line; | ||||||
|  | 	struct vector *new_list, *temp = hide_list; | ||||||
|  | 	new_list = xmalloc(sizeof(*new_list)); | ||||||
|  | 	if (new_list == NULL) | ||||||
|  | 		return 1; | ||||||
|  | 	vec_init(new_list); | ||||||
|  |  | ||||||
|  | 	vec_for_each(hide_list, line) { | ||||||
|  | 		// They should be unique | ||||||
|  | 		if (strcmp(line, proc) == 0) { | ||||||
|  | 			free(proc); | ||||||
|  | 			vec_destroy(new_list); | ||||||
|  | 			free(new_list); | ||||||
|  | 			return 2; | ||||||
|  | 		} | ||||||
|  | 		vec_push_back(new_list, line); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	vec_push_back(new_list, proc); | ||||||
|  | 	LOGI("hide_list add: [%s]\n", proc); | ||||||
|  | 	ps_filter_proc_name(proc, kill_proc); | ||||||
|  |  | ||||||
|  | 	// Critical region | ||||||
|  | 	pthread_mutex_lock(&lock); | ||||||
|  | 	hide_list = new_list; | ||||||
|  | 	pthread_mutex_unlock(&lock); | ||||||
|  |  | ||||||
|  | 	// Free old list | ||||||
|  | 	vec_destroy(temp); | ||||||
|  | 	free(temp); | ||||||
|  | 	if (vector_to_file(HIDELIST, hide_list)) | ||||||
|  | 		return 1; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int rm_list(char *proc) { | ||||||
|  | 	if (!hideEnabled) | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
|  | 	char *line; | ||||||
|  | 	struct vector *new_list, *temp; | ||||||
|  | 	temp = new_list = xmalloc(sizeof(*new_list)); | ||||||
|  | 	if (new_list == NULL) | ||||||
|  | 		return 1; | ||||||
|  | 	vec_init(new_list); | ||||||
|  |  | ||||||
|  | 	vec_for_each(hide_list, line) { | ||||||
|  | 		if (strcmp(line, proc) == 0) { | ||||||
|  | 			free(proc); | ||||||
|  | 			proc = line; | ||||||
|  | 			temp = hide_list; | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  | 		vec_push_back(new_list, line); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (temp == hide_list) { | ||||||
|  | 		LOGI("hide_list rm: [%s]\n", proc); | ||||||
|  | 		ps_filter_proc_name(proc, kill_proc); | ||||||
|  | 		// Critical region | ||||||
|  | 		pthread_mutex_lock(&lock); | ||||||
|  | 		hide_list = new_list; | ||||||
|  | 		pthread_mutex_unlock(&lock); | ||||||
|  | 		if (vector_to_file(HIDELIST, hide_list)) | ||||||
|  | 			return 1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	free(proc); | ||||||
|  | 	vec_destroy(temp); | ||||||
|  | 	free(temp); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int init_list() { | ||||||
|  | 	LOGD("hide_list: initialize...\n"); | ||||||
|  | 	if ((hide_list = xmalloc(sizeof(*hide_list))) == NULL) | ||||||
|  | 		return 1; | ||||||
|  | 	vec_init(hide_list); | ||||||
|  |  | ||||||
|  | 	// Might return 1 if first time | ||||||
|  | 	file_to_vector(HIDELIST, hide_list); | ||||||
|  |  | ||||||
|  | 	char *line; | ||||||
|  | 	vec_for_each(hide_list, line) { | ||||||
|  | 		LOGI("hide_list: [%s]\n", line); | ||||||
|  | 		ps_filter_proc_name(line, kill_proc); | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int destroy_list() { | ||||||
|  | 	char *line; | ||||||
|  | 	vec_for_each(hide_list, line) { | ||||||
|  | 		ps_filter_proc_name(line, kill_proc); | ||||||
|  | 	} | ||||||
|  | 	vec_deep_destroy(hide_list); | ||||||
|  | 	free(hide_list); | ||||||
|  | 	hide_list = NULL; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void add_hide_list(int client) { | ||||||
|  | 	char *proc = read_string(client); | ||||||
|  | 	// ack | ||||||
|  | 	write_int(client, add_list(proc)); | ||||||
|  | 	close(client); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void rm_hide_list(int client) { | ||||||
|  | 	char *proc = read_string(client); | ||||||
|  | 	// ack | ||||||
|  | 	write_int(client, rm_list(proc)); | ||||||
|  | 	close(client); | ||||||
|  | } | ||||||
| @@ -16,12 +16,14 @@ | |||||||
| #include "utils.h" | #include "utils.h" | ||||||
| #include "magiskhide.h" | #include "magiskhide.h" | ||||||
| #include "daemon.h" | #include "daemon.h" | ||||||
|  | #include "resetprop.h" | ||||||
|  |  | ||||||
| int sv[2], hide_pid = -1; | int sv[2], hide_pid = -1; | ||||||
| struct vector *hide_list, *new_list; | struct vector *hide_list = NULL; | ||||||
|  |  | ||||||
| int isEnabled = 0; | int hideEnabled = 0; | ||||||
| static pthread_t proc_monitor_thread; | static pthread_t proc_monitor_thread; | ||||||
|  | pthread_mutex_t lock; | ||||||
|  |  | ||||||
| void kill_proc(int pid) { | void kill_proc(int pid) { | ||||||
| 	kill(pid, SIGTERM); | 	kill(pid, SIGTERM); | ||||||
| @@ -41,7 +43,7 @@ static void usage(char *arg0) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void launch_magiskhide(int client) { | void launch_magiskhide(int client) { | ||||||
| 	if (isEnabled) | 	if (hideEnabled) | ||||||
| 		goto success; | 		goto success; | ||||||
| 	/* | 	/* | ||||||
| 	 * The setns system call do not support multithread processes | 	 * The setns system call do not support multithread processes | ||||||
| @@ -50,6 +52,9 @@ void launch_magiskhide(int client) { | |||||||
|  |  | ||||||
| 	LOGI("* Starting MagiskHide\n"); | 	LOGI("* Starting MagiskHide\n"); | ||||||
|  |  | ||||||
|  | 	hideEnabled = 1; | ||||||
|  | 	setprop("persist.magisk.hide", "1"); | ||||||
|  |  | ||||||
| 	hide_sensitive_props(); | 	hide_sensitive_props(); | ||||||
|  |  | ||||||
| 	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) | 	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) | ||||||
| @@ -62,31 +67,23 @@ void launch_magiskhide(int client) { | |||||||
| 	close(sv[1]); | 	close(sv[1]); | ||||||
|  |  | ||||||
| 	// Initialize the hide list | 	// Initialize the hide list | ||||||
| 	hide_list = new_list = xmalloc(sizeof(*hide_list)); | 	if (init_list()) | ||||||
| 	if (hide_list == NULL) |  | ||||||
| 		goto error; | 		goto error; | ||||||
| 	vec_init(hide_list); |  | ||||||
| 	FILE *fp = xfopen(HIDELIST, "r"); | 	// Add SafetyNet by default | ||||||
| 	if (fp == NULL) | 	add_list(strdup("com.google.android.gms.unstable")); | ||||||
| 		goto error; |  | ||||||
| 	file_to_vector(hide_list, fp); |  | ||||||
| 	fclose(fp); |  | ||||||
| 	char *line; |  | ||||||
| 	vec_for_each(hide_list, line) { |  | ||||||
| 		LOGI("hide_list: [%s]\n", line); |  | ||||||
| 		ps_filter_proc_name(line, kill_proc); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Start a new thread to monitor processes | 	// Start a new thread to monitor processes | ||||||
|  | 	pthread_mutex_init(&lock, NULL); | ||||||
| 	if (xpthread_create(&proc_monitor_thread, NULL, proc_monitor, NULL)) | 	if (xpthread_create(&proc_monitor_thread, NULL, proc_monitor, NULL)) | ||||||
| 		goto error; | 		goto error; | ||||||
|  |  | ||||||
| 	isEnabled = 1; |  | ||||||
| success: | success: | ||||||
| 	write_int(client, 0); | 	write_int(client, 0); | ||||||
| 	close(client); | 	close(client); | ||||||
| 	return; | 	return; | ||||||
| error: | error: | ||||||
|  | 	hideEnabled = 0; | ||||||
| 	write_int(client, 1); | 	write_int(client, 1); | ||||||
| 	close(client); | 	close(client); | ||||||
| 	if (hide_pid != -1) { | 	if (hide_pid != -1) { | ||||||
| @@ -101,7 +98,7 @@ error: | |||||||
| } | } | ||||||
|  |  | ||||||
| void stop_magiskhide(int client) { | void stop_magiskhide(int client) { | ||||||
| 	if (!isEnabled) | 	if (!hideEnabled) | ||||||
| 		return; | 		return; | ||||||
|  |  | ||||||
| 	LOGI("* Stopping MagiskHide\n"); | 	LOGI("* Stopping MagiskHide\n"); | ||||||
| @@ -109,7 +106,8 @@ void stop_magiskhide(int client) { | |||||||
| 	pthread_kill(proc_monitor_thread, SIGUSR1); | 	pthread_kill(proc_monitor_thread, SIGUSR1); | ||||||
| 	pthread_join(proc_monitor_thread, NULL); | 	pthread_join(proc_monitor_thread, NULL); | ||||||
|  |  | ||||||
| 	isEnabled = 0; | 	hideEnabled = 0; | ||||||
|  | 	setprop("persist.magisk.hide", "0"); | ||||||
| 	write_int(client, 0); | 	write_int(client, 0); | ||||||
| 	close(client); | 	close(client); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #ifndef MAGISK_HIDE_H | #ifndef MAGISK_HIDE_H | ||||||
| #define MAGISK_HIDE_H | #define MAGISK_HIDE_H | ||||||
|  |  | ||||||
|  | #include <pthread.h> | ||||||
|  |  | ||||||
| #define HIDELIST		"/magisk/.core/magiskhide/hidelist" | #define HIDELIST		"/magisk/.core/magiskhide/hidelist" | ||||||
| #define DUMMYPATH		"/dev/magisk/dummy" | #define DUMMYPATH		"/dev/magisk/dummy" | ||||||
| #define ENFORCE_FILE 	"/sys/fs/selinux/enforce" | #define ENFORCE_FILE 	"/sys/fs/selinux/enforce" | ||||||
| @@ -20,7 +22,14 @@ void manage_selinux(); | |||||||
| void hide_sensitive_props(); | void hide_sensitive_props(); | ||||||
| void relink_sbin(); | void relink_sbin(); | ||||||
|  |  | ||||||
| extern int sv[2], hide_pid, isEnabled; | // List managements | ||||||
| extern struct vector *hide_list, *new_list; | int add_list(char *proc); | ||||||
|  | int rm_list(char *proc); | ||||||
|  | int init_list(); | ||||||
|  | int destroy_list(); | ||||||
|  |  | ||||||
|  | extern int sv[2], hide_pid, hideEnabled; | ||||||
|  | extern struct vector *hide_list; | ||||||
|  | extern pthread_mutex_t lock; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -30,18 +30,8 @@ static void read_namespace(const int pid, char* target, const size_t size) { | |||||||
| // Workaround for the lack of pthread_cancel | // Workaround for the lack of pthread_cancel | ||||||
| static void quit_pthread(int sig) { | static void quit_pthread(int sig) { | ||||||
| 	LOGD("proc_monitor: running cleanup\n"); | 	LOGD("proc_monitor: running cleanup\n"); | ||||||
| 	char *line; | 	destroy_list(); | ||||||
| 	vec_for_each(hide_list, line) { | 	hideEnabled = 0; | ||||||
| 		ps_filter_proc_name(line, kill_proc); |  | ||||||
| 	} |  | ||||||
| 	vec_deep_destroy(hide_list); |  | ||||||
| 	free(hide_list); |  | ||||||
| 	if (new_list != hide_list) { |  | ||||||
| 		vec_deep_destroy(new_list); |  | ||||||
| 		free(new_list); |  | ||||||
| 	} |  | ||||||
| 	hide_list = new_list = NULL; |  | ||||||
| 	isEnabled = 0; |  | ||||||
| 	// Kill the logging if possible | 	// Kill the logging if possible | ||||||
| 	if (log_pid) { | 	if (log_pid) { | ||||||
| 		kill(log_pid, SIGTERM); | 		kill(log_pid, SIGTERM); | ||||||
| @@ -54,6 +44,7 @@ static void quit_pthread(int sig) { | |||||||
| 	write(sv[0], &kill, sizeof(kill)); | 	write(sv[0], &kill, sizeof(kill)); | ||||||
| 	close(sv[0]); | 	close(sv[0]); | ||||||
| 	waitpid(hide_pid, NULL, 0); | 	waitpid(hide_pid, NULL, 0); | ||||||
|  | 	pthread_mutex_destroy(&lock); | ||||||
| 	LOGD("proc_monitor: terminating...\n"); | 	LOGD("proc_monitor: terminating...\n"); | ||||||
| 	pthread_exit(NULL); | 	pthread_exit(NULL); | ||||||
| } | } | ||||||
| @@ -109,7 +100,6 @@ void *proc_monitor(void *args) { | |||||||
| 	while(fdgets(buffer, sizeof(buffer), log_fd)) { | 	while(fdgets(buffer, sizeof(buffer), log_fd)) { | ||||||
| 		int ret, comma = 0; | 		int ret, comma = 0; | ||||||
| 		char *pos = buffer, *line, processName[256]; | 		char *pos = buffer, *line, processName[256]; | ||||||
| 		struct vector *temp = NULL; |  | ||||||
|  |  | ||||||
| 		while(1) { | 		while(1) { | ||||||
| 			pos = strchr(pos, ','); | 			pos = strchr(pos, ','); | ||||||
| @@ -127,14 +117,10 @@ void *proc_monitor(void *args) { | |||||||
| 		if(ret != 2) | 		if(ret != 2) | ||||||
| 			continue; | 			continue; | ||||||
|  |  | ||||||
| 		// Should be thread safe |  | ||||||
| 		if (hide_list != new_list) { |  | ||||||
| 			temp = hide_list; |  | ||||||
| 			hide_list = new_list; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		ret = 0; | 		ret = 0; | ||||||
|  |  | ||||||
|  | 		// Critical region | ||||||
|  | 		pthread_mutex_lock(&lock); | ||||||
| 		vec_for_each(hide_list, line) { | 		vec_for_each(hide_list, line) { | ||||||
| 			if (strcmp(processName, line) == 0) { | 			if (strcmp(processName, line) == 0) { | ||||||
| 				read_namespace(pid, buffer, 32); | 				read_namespace(pid, buffer, 32); | ||||||
| @@ -166,10 +152,8 @@ void *proc_monitor(void *args) { | |||||||
| 				break; | 				break; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if (temp) { | 		pthread_mutex_unlock(&lock); | ||||||
| 			vec_deep_destroy(temp); |  | ||||||
| 			free(temp); |  | ||||||
| 		} |  | ||||||
| 		if (ret) { | 		if (ret) { | ||||||
| 			// Wait hide process to kill itself | 			// Wait hide process to kill itself | ||||||
| 			waitpid(hide_pid, NULL, 0); | 			waitpid(hide_pid, NULL, 0); | ||||||
|   | |||||||
| @@ -57,11 +57,15 @@ int check_data() { | |||||||
| } | } | ||||||
|  |  | ||||||
| /* All the string should be freed manually!! */ | /* All the string should be freed manually!! */ | ||||||
| void file_to_vector(struct vector *v, FILE *fp) { | int file_to_vector(const char* filename, struct vector *v) { | ||||||
| 	char *line = NULL; | 	char *line = NULL; | ||||||
| 	size_t len = 0; | 	size_t len = 0; | ||||||
| 	ssize_t read; | 	ssize_t read; | ||||||
|  |  | ||||||
|  | 	FILE *fp = xfopen(filename, "r"); | ||||||
|  | 	if (fp == NULL) | ||||||
|  | 		return 1; | ||||||
|  |  | ||||||
| 	while ((read = getline(&line, &len, fp)) != -1) { | 	while ((read = getline(&line, &len, fp)) != -1) { | ||||||
| 		// Remove end newline | 		// Remove end newline | ||||||
| 		if (line[read - 1] == '\n') | 		if (line[read - 1] == '\n') | ||||||
| @@ -69,6 +73,20 @@ void file_to_vector(struct vector *v, FILE *fp) { | |||||||
| 		vec_push_back(v, line); | 		vec_push_back(v, line); | ||||||
| 		line = NULL; | 		line = NULL; | ||||||
| 	} | 	} | ||||||
|  | 	fclose(fp); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int vector_to_file(const char *filename, struct vector *v) { | ||||||
|  | 	FILE *fp = xfopen(filename, "w"); | ||||||
|  | 	if (fp == NULL) | ||||||
|  | 		return 1; | ||||||
|  | 	char *line; | ||||||
|  | 	vec_for_each(v, line) { | ||||||
|  | 		fprintf(fp, "%s\n", line); | ||||||
|  | 	} | ||||||
|  | 	fclose(fp); | ||||||
|  | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* Check if the string only contains digits */ | /* Check if the string only contains digits */ | ||||||
|   | |||||||
| @@ -63,7 +63,8 @@ unsigned get_shell_uid(); | |||||||
| unsigned get_system_uid(); | unsigned get_system_uid(); | ||||||
| unsigned get_radio_uid(); | unsigned get_radio_uid(); | ||||||
| int check_data(); | int check_data(); | ||||||
| void file_to_vector(struct vector *v, FILE *fp); | int file_to_vector(const char* filename, struct vector *v); | ||||||
|  | int vector_to_file(const char* filename, struct vector *v); | ||||||
| int isNum(const char *s); | int isNum(const char *s); | ||||||
| ssize_t fdgets(char *buf, size_t size, int fd); | ssize_t fdgets(char *buf, size_t size, int fd); | ||||||
| void ps(void (*func)(int)); | void ps(void (*func)(int)); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 topjohnwu
					topjohnwu