1 /* actions.c -- a few functions for the plugins systems */
2 
3 /*
4  * This file is part of CliFM
5  *
6  * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7  * All rights reserved.
8 
9  * CliFM is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * CliFM is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA 02110-1301, USA.
23 */
24 
25 #include "helpers.h"
26 
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 #include <unistd.h>
34 
35 #include "aux.h"
36 #include "checks.h"
37 #include "exec.h"
38 #include "file_operations.h"
39 #include "init.h"
40 #include "mime.h"
41 #include "misc.h"
42 
43 /* The core of this function was taken from NNN's run_selected_plugin
44  * function and modified to fit our needs. Thanks NNN! */
45 int
run_action(char * action,char ** args)46 run_action(char *action, char **args)
47 {
48 	if (!action)
49 		return EXIT_FAILURE;
50 
51 	int exit_status = EXIT_SUCCESS;
52 	char *cmd = (char *)NULL;
53 	size_t len = 0,
54 		   action_len = strlen(action);
55 
56 		/* #####################################
57 		 * #    1) CREATE CMD TO BE EXECUTED   #
58 		 * ##################################### */
59 
60 	/* Remove terminating new line char */
61 	if (action[action_len - 1] == '\n')
62 		action[action_len - 1] = '\0';
63 
64 	int dir_path = 0;
65 	if (strchr(action, '/')) {
66 		cmd = (char *)xnmalloc(action_len + 1, sizeof(char));
67 		strcpy(cmd, action);
68 		dir_path = 1;
69 	} else { /* If not a path, PLUGINS_DIR is assumed */
70 		if (!plugins_dir || !*plugins_dir) {
71 			fprintf(stderr, "%s: Plugins directory not defined\n", PROGRAM_NAME);
72 			return EXIT_FAILURE;
73 		}
74 		cmd = (char *)xnmalloc(action_len + strlen(plugins_dir) + 2,
75 								sizeof(char));
76 		sprintf(cmd, "%s/%s", plugins_dir, action);
77 	}
78 
79 	/* Check if the action file exists and is executable */
80 	if (access(cmd, X_OK) == -1) {
81 		/* If not in local dir, check system data dir as well */
82 		if (data_dir && !dir_path) {
83 			cmd = (char *)xrealloc(cmd, (action_len + strlen(data_dir)
84 						+ strlen(PNL) + 11) * sizeof(char));
85 			sprintf(cmd, "%s/%s/plugins/%s", data_dir, PNL, action);
86 			if (access(cmd, X_OK) == -1) {
87 				fprintf(stderr, "actions: %s: %s\n", cmd, strerror(errno));
88 				free(cmd);
89 				return EXIT_FAILURE;
90 			}
91 		} else {
92 			fprintf(stderr, "actions: %s: %s\n", cmd, strerror(errno));
93 			free(cmd);
94 			return EXIT_FAILURE;
95 		}
96 	}
97 
98 	/* Append arguments to command */
99 	/* Arguments are already escaped */
100 	size_t i;
101 	len = strlen(cmd);
102 	for (i = 1; args[i]; i++) {
103 		len += (strlen(args[i]) + 2);
104 		cmd = (char *)xrealloc(cmd, len * sizeof(char));
105 		strcat(cmd, " ");
106 		strcat(cmd, args[i]);
107 	}
108 
109 			/* ##############################
110 			 * #    2) CREATE A PIPE FILE   #
111 			 * ############################## */
112 
113 	char *rand_ext = gen_rand_str(6);
114 	if (!rand_ext) {
115 		free(cmd);
116 		return EXIT_FAILURE;
117 	}
118 
119 	char fifo_path[PATH_MAX];
120 	sprintf(fifo_path, "%s/.pipe.%s", tmp_dir, rand_ext);
121 	free(rand_ext);
122 
123 	setenv("CLIFM_BUS", fifo_path, 1);
124 
125 	if (mkfifo(fifo_path, 0600) != EXIT_SUCCESS) {
126 		free(cmd);
127 		printf("%s: %s\n", fifo_path, strerror(errno));
128 		return EXIT_FAILURE;
129 	}
130 
131 	/* ################################################
132 	 * #   3) EXEC CMD & LET THE CHILD WRITE TO PIPE  #
133 	 * ################################################ */
134 
135 	/* Set terminal title to plugin name */
136 	if (xargs.cwd_in_title == 1)
137 		set_term_title(action);
138 
139 	pid_t pid = fork();
140 
141 	if (pid == 0) {
142 		/* Child: write-only end of the pipe */
143 		int wfd = open(fifo_path, O_WRONLY | O_CLOEXEC);
144 
145 		if (wfd == -1)
146 			_exit(EXIT_FAILURE);
147 
148 		launch_execle(cmd);
149 		close(wfd);
150 		_exit(EXIT_SUCCESS);
151 	}
152 
153 	free(cmd);
154 
155 		/* ########################################
156 		 * #    4) LET THE PARENT READ THE PIPE   #
157 		 * ######################################## */
158 
159 	/* Parent: read-only end of the pipe */
160 	int rfd;
161 
162 	do
163 		rfd = open(fifo_path, O_RDONLY);
164 	while (rfd == -1 && errno == EINTR);
165 
166 	char buf[PATH_MAX] = "";
167 	ssize_t buf_len = 0;
168 
169 	do
170 		buf_len = read(rfd, buf, sizeof(buf));
171 	while (buf_len == -1 && errno == EINTR);
172 
173 	close(rfd);
174 
175 	/* Wait for the child to finish. Otherwise, the child is left as
176 	 * zombie process */
177 	int status = 0;
178 	waitpid(pid, &status, 0);
179 
180 	/* If the pipe is empty */
181 	if (!*buf) {
182 		unlink(fifo_path);
183 		if (xargs.cwd_in_title == 1)
184 			set_term_title(ws[cur_ws].path);
185 		return EXIT_SUCCESS;
186 	}
187 
188 	if (buf[buf_len - 1] == '\n')
189 		buf[buf_len - 1] = '\0';
190 
191 	/* If a valid file */
192 	struct stat attr;
193 
194 	if (lstat(buf, &attr) != -1) {
195 		char *o_cmd[] = {"o", buf, NULL};
196 		exit_status = open_function(o_cmd);
197 	} else { /* If not a file, take it as a command*/
198 		size_t old_args = args_n;
199 		args_n = 0;
200 
201 		char **_cmd = parse_input_str(buf);
202 		if (_cmd) {
203 			char **alias_cmd = check_for_alias(_cmd);
204 
205 			if (alias_cmd) {
206 				exit_status = exec_cmd(alias_cmd);
207 				for (i = 0; alias_cmd[i]; i++)
208 					free(alias_cmd[i]);
209 				free(alias_cmd);
210 			} else {
211 				exit_status = exec_cmd(_cmd);
212 				for (i = 0; i <= args_n; i++)
213 					free(_cmd[i]);
214 				free(_cmd);
215 			}
216 		}
217 
218 		args_n = old_args;
219 	}
220 
221 	/* Remove the pipe file */
222 	unlink(fifo_path);
223 
224 	if (xargs.cwd_in_title == 1)
225 		set_term_title(ws[cur_ws].path);
226 
227 	return exit_status;
228 }
229 
230 int
edit_actions(void)231 edit_actions(void)
232 {
233 	if (xargs.stealth_mode == 1) {
234 		printf("%s: Access to configuration files is not allowed in "
235 		       "stealth mode\n", PROGRAM_NAME);
236 		return EXIT_SUCCESS;
237 	}
238 
239 	/* Get actions file's current modification time */
240 	struct stat file_attrib;
241 
242 	if (stat(actions_file, &file_attrib) == -1) {
243 		fprintf(stderr, "actions: %s: %s\n", actions_file, strerror(errno));
244 		return EXIT_FAILURE;
245 	}
246 
247 	time_t mtime_bfr = (time_t)file_attrib.st_mtime;
248 
249 	int ret = open_file(actions_file);
250 	if (ret != EXIT_SUCCESS)
251 		return EXIT_FAILURE;
252 
253 	/* Get modification time after opening the file */
254 	stat(actions_file, &file_attrib);
255 
256 	/* If modification times differ, the file was modified after being
257 	 * opened */
258 	if (mtime_bfr != (time_t)file_attrib.st_mtime) {
259 		/* Reload the array of available actions */
260 		if (load_actions() != EXIT_SUCCESS)
261 			return EXIT_FAILURE;
262 
263 		/* Reload PATH commands as well to add new action(s) */
264 		if (bin_commands) {
265 			size_t i;
266 			for (i = 0; bin_commands[i]; i++)
267 				free(bin_commands[i]);
268 
269 			free(bin_commands);
270 			bin_commands = (char **)NULL;
271 		}
272 
273 		if (paths) {
274 			size_t i;
275 			for (i = 0; i < path_n; i++)
276 				free(paths[i]);
277 		}
278 
279 		path_n = (size_t)get_path_env();
280 		get_path_programs();
281 	}
282 
283 	return EXIT_SUCCESS;
284 }
285