*** empty log message ***
[midnight-commander.git] / src / ext.c
blob2c38bbdc35fe25d9ad7fdc05fe532371dd3d658d
1 /* Extension dependent execution.
2 Copyright (C) 1994, 1995 The Free Software Foundation
4 Written by: 1995 Jakub Jelinek
5 1994 Miguel de Icaza
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
21 #include <config.h>
22 #include <stdio.h>
23 #include <ctype.h>
25 #ifdef HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28 #include <fcntl.h>
29 #include <string.h>
30 #include <errno.h>
32 #include "global.h"
33 #include "tty.h"
34 #include "user.h"
35 #include "main.h"
36 #include "dialog.h"
37 #include "ext.h"
38 #include "view.h"
39 #include "main.h"
40 #include "../vfs/vfs.h"
42 #include "cons.saver.h"
43 #include "layout.h"
45 /* If set, we execute the file command to check the file type */
46 int use_file_to_check_type = 1;
48 /* This variable points to a copy of the mc.ext file in memory
49 * With this we avoid loading/parsing the file each time we
50 * need it
52 static char *data = NULL;
54 void
55 flush_extension_file (void)
57 if (data){
58 g_free (data);
59 data = NULL;
64 typedef char *(*quote_func_t)(const char *name, int i);
66 static void
67 exec_extension (const char *filename, const char *data, int *move_dir, int start_line)
69 char *file_name;
70 int cmd_file_fd;
71 FILE *cmd_file;
72 int expand_prefix_found = 0;
73 int parameter_found = 0;
74 char prompt [80];
75 int run_view = 0;
76 int def_hex_mode = default_hex_mode, changed_hex_mode = 0;
77 int def_nroff_flag = default_nroff_flag, changed_nroff_flag = 0;
78 int written_nonspace = 0;
79 int is_cd = 0;
80 char buffer [1024];
81 char *p = 0;
82 char *localcopy = NULL;
83 int do_local_copy;
84 time_t localmtime = 0;
85 struct stat mystat;
86 quote_func_t quote_func = name_quote;
88 g_return_if_fail (filename != NULL);
89 g_return_if_fail (data != NULL);
91 /* Avoid making a local copy if we are doing a cd */
92 if (!vfs_file_is_local(filename))
93 do_local_copy = 1;
94 else
95 do_local_copy = 0;
98 * All commands should be run in /bin/sh regardless of user shell.
99 * To do that, create temporary shell script and run it.
100 * Sometimes it's not needed (e.g. for %cd and %view commands),
101 * but it's easier to create it anyway.
103 cmd_file_fd = mc_mkstemps(&file_name, "mcext", SCRIPT_SUFFIX);
105 if (cmd_file_fd == -1){
106 message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "),
107 unix_error_string (errno));
108 return;
110 cmd_file = fdopen (cmd_file_fd, "w");
111 fputs ("#! /bin/sh\n", cmd_file);
113 prompt [0] = 0;
114 for (;*data && *data != '\n'; data++){
115 if (parameter_found){
116 if (*data == '}'){
117 char *parameter;
118 parameter_found = 0;
119 parameter = input_dialog (_(" Parameter "), prompt, "");
120 if (!parameter){
121 /* User canceled */
122 fclose (cmd_file);
123 unlink (file_name);
124 if (localcopy) {
125 mc_ungetlocalcopy (filename, localcopy, 0);
127 g_free (file_name);
128 return;
130 fputs (parameter, cmd_file);
131 written_nonspace = 1;
132 g_free (parameter);
133 } else {
134 int len = strlen (prompt);
136 if (len < sizeof (prompt) - 1){
137 prompt [len] = *data;
138 prompt [len+1] = 0;
141 } else if (expand_prefix_found){
142 expand_prefix_found = 0;
143 if (*data == '{')
144 parameter_found = 1;
145 else {
146 int i = check_format_view (data);
147 char *v;
149 if (i){
150 data += i - 1;
151 run_view = 1;
152 } else if ((i = check_format_cd (data)) > 0) {
153 is_cd = 1;
154 quote_func = fake_name_quote;
155 do_local_copy = 0;
156 p = buffer;
157 data += i - 1;
158 } else if ((i = check_format_var (data, &v)) > 0 && v){
159 fputs (v, cmd_file);
160 g_free (v);
161 data += i;
162 } else {
163 char *text;
165 if (*data == 'f'){
166 if (do_local_copy){
167 localcopy = mc_getlocalcopy (filename);
168 if (localcopy == NULL) {
169 fclose(cmd_file);
170 unlink(file_name);
171 g_free (file_name);
172 return;
174 mc_stat (localcopy, &mystat);
175 localmtime = mystat.st_mtime;
176 text = (*quote_func) (localcopy, 0);
177 } else {
178 text = (*quote_func) (filename, 0);
180 } else
181 text = expand_format (NULL, *data, !is_cd);
182 if (!is_cd)
183 fputs (text, cmd_file);
184 else {
185 strcpy (p, text);
186 p = strchr (p, 0);
188 g_free (text);
189 written_nonspace = 1;
192 } else {
193 if (*data == '%')
194 expand_prefix_found = 1;
195 else {
196 if (*data != ' ' && *data != '\t')
197 written_nonspace = 1;
198 if (is_cd)
199 *(p++) = *data;
200 else
201 fputc (*data, cmd_file);
204 } /* for */
206 /* Make sure that the file removes itself when it finishes */
207 fprintf (cmd_file, "\n/bin/rm -f %s\n", file_name);
208 fclose (cmd_file);
210 if ((run_view && !written_nonspace) || is_cd) {
211 unlink (file_name);
212 g_free (file_name);
213 file_name = NULL;
214 } else {
215 chmod (file_name, S_IRWXU);
218 if (run_view){
219 altered_hex_mode = 0;
220 altered_nroff_flag = 0;
221 if (def_hex_mode != default_hex_mode)
222 changed_hex_mode = 1;
223 if (def_nroff_flag != default_nroff_flag)
224 changed_nroff_flag = 1;
226 /* If we've written whitespace only, then just load filename
227 * into view
229 if (written_nonspace)
230 view (file_name, filename, move_dir, start_line);
231 else
232 view (0, filename, move_dir, start_line);
233 if (changed_hex_mode && !altered_hex_mode)
234 default_hex_mode = def_hex_mode;
235 if (changed_nroff_flag && !altered_nroff_flag)
236 default_nroff_flag = def_nroff_flag;
237 repaint_screen ();
238 } else if (is_cd) {
239 char *q;
240 *p = 0;
241 p = buffer;
242 /* while (*p == ' ' && *p == '\t')
243 * p++;
245 /* Search last non-space character. Start search at the end in order
246 not to short filenames containing spaces. */
247 q = p + strlen (p) - 1;
248 while (q >= p && (*q == ' ' || *q == '\t'))
249 q--;
250 q[1] = 0;
251 do_cd (p, cd_parse_command);
252 } else {
253 shell_execute (file_name, EXECUTE_INTERNAL);
254 if (console_flag) {
255 handle_console (CONSOLE_SAVE);
256 if (output_lines && keybar_visible) {
257 show_console_contents (output_start_y,
258 LINES-keybar_visible-output_lines-1,
259 LINES-keybar_visible-1);
264 if (file_name) {
265 g_free (file_name);
267 if (localcopy) {
268 mc_stat (localcopy, &mystat);
269 mc_ungetlocalcopy (filename, localcopy, localmtime != mystat.st_mtime);
273 #ifdef FILE_L
274 # define FILE_CMD "file -L "
275 #else
276 # define FILE_CMD "file "
277 #endif
280 * Run the "file" command on the local file.
281 * Return 1 if the data is valid, 0 otherwise.
284 get_file_type_local (char *filename, char *buf, int buflen)
286 int read_bytes = 0;
288 char *tmp = name_quote (filename, 0);
289 char *command = g_strconcat (FILE_CMD, tmp, NULL);
290 FILE *f = popen (command, "r");
292 g_free (tmp);
293 g_free (command);
294 if (f != NULL) {
295 read_bytes = (fgets (buf, buflen - 1, f)
296 != NULL);
297 if (read_bytes == 0)
298 buf[0] = 0;
299 pclose (f);
300 #ifdef SCO_FLAVOR
302 ** SCO 3.2 does has a buggy pclose(), so
303 ** <command> become zombie (alex)
305 waitpid (-1, NULL, WNOHANG);
306 #endif /* SCO_FLAVOR */
309 return (read_bytes > 0);
313 #ifdef FILE_STDIN
315 * Read file through VFS and feed is to the "file" command.
316 * Return 1 if the data is valid, 0 otherwise.
319 get_file_type_pipe (char *filename, char *buf, int buflen)
321 int read_bytes = 0;
323 int pipehandle, remotehandle;
324 pid_t p;
326 remotehandle = mc_open (filename, O_RDONLY);
327 if (remotehandle != -1) {
328 /* 8192 is HOWMANY hardcoded value in the file-3.14
329 * sources. Tell me if any other file uses larger
330 * chunk from beginning
332 pipehandle = mc_doublepopen
333 (remotehandle, 8192, &p, "file", "file", "-", NULL);
334 if (pipehandle != -1) {
335 int i;
336 while ((i = read (pipehandle, buf
337 + read_bytes, buflen - 1 - read_bytes)) > 0)
338 read_bytes += i;
339 mc_doublepclose (pipehandle, p);
340 buf[read_bytes] = 0;
342 mc_close (remotehandle);
345 return (read_bytes > 0);
347 #endif /* FILE_STDIN */
351 * Invoke the "file" command on the file and match its output against PTR.
352 * have_type is a flag that is set if we already have tried to determine
353 * the type of that file.
354 * Return 1 for match, 0 otherwise.
356 static int
357 regex_check_type (char *filename, int file_len, char *ptr, int *have_type)
359 int found = 0;
360 int islocal;
362 /* Following variables are valid if *have_type is 1 */
363 static char content_string[2048];
364 static int content_shift = 0;
365 static int got_data = 0;
367 if (!use_file_to_check_type) {
368 return 0;
371 islocal = vfs_file_is_local (filename);
373 if (!*have_type) {
374 /* Don't repeate even unsuccessful checks */
375 *have_type = 1;
377 if (islocal) {
378 got_data = get_file_type_local (filename, content_string,
379 sizeof (content_string));
380 } else
381 #ifdef FILE_STDIN
383 got_data = get_file_type_pipe (filename, content_string,
384 sizeof (content_string));
386 #else
387 /* Cannot use pipe, must make a local copy, not yet supported */
388 return 0;
389 #endif /* !FILE_STDIN */
391 if (got_data) {
392 char *pp;
394 /* Paranoid termination */
395 content_string[sizeof (content_string) - 1] = 0;
397 if ((pp = strchr (content_string, '\n')) != 0)
398 *pp = 0;
400 if (islocal && !strncmp (content_string, filename, file_len)) {
401 /* Skip "filename: " */
402 content_shift = file_len;
403 if (content_string[content_shift] == ':')
404 for (content_shift++;
405 content_string[content_shift] == ' ';
406 content_shift++);
407 } else if (!islocal
408 && !strncmp (content_string, "standard input:",
409 15)) {
410 /* Skip "standard input: " */
411 for (content_shift = 15;
412 content_string[content_shift] == ' ';
413 content_shift++);
415 } else {
416 /* No data */
417 content_string[0] = 0;
421 if (content_string && content_string[0] &&
422 regexp_match (ptr, content_string + content_shift, match_normal)) {
423 found = 1;
426 return found;
430 /* The second argument is action, i.e. Open, View or Edit
432 * This function returns:
434 * 1 if it ran some command or 0 otherwise.
436 * If action == "View" then a parameter is checked in the form of "View:%d",
437 * if the value for %d exists, then the viewer is started up at that line number.
440 regex_command (char *filename, char *action, int *move_dir)
442 char *p, *q, *r, c;
443 int file_len = strlen (filename);
444 int found = 0;
445 int ret = 0;
446 int old_patterns;
447 struct stat mystat;
448 int view_at_line_number;
449 char *include_target;
450 int include_target_len;
451 int have_type = 0; /* Flag used by regex_check_type() */
453 /* Check for the special View:%d parameter */
454 if (strncmp (action, "View:", 5) == 0) {
455 view_at_line_number = atoi (action + 5);
456 action[4] = 0;
457 } else {
458 view_at_line_number = 0;
461 if (data == NULL) {
462 char *extension_file;
463 int mc_user_ext = 1;
464 int home_error = 0;
466 extension_file = concat_dir_and_file (home_dir, MC_USER_EXT);
467 if (!exist_file (extension_file)) {
468 g_free (extension_file);
469 check_stock_mc_ext:
470 extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT);
471 mc_user_ext = 0;
473 data = load_file (extension_file);
474 g_free (extension_file);
475 if (data == NULL)
476 return 0;
478 if (!strstr (data, "default/")) {
479 if (!strstr (data, "regex/") && !strstr (data, "shell/") &&
480 !strstr (data, "type/")) {
481 g_free (data);
482 data = NULL;
483 if (mc_user_ext) {
484 home_error = 1;
485 goto check_stock_mc_ext;
486 } else {
487 char *msg;
488 char *msg2;
489 msg =
490 g_strconcat (" ", mc_home, MC_LIB_EXT,
491 _(" file error"), NULL);
492 msg2 =
493 g_strconcat (_("Format of the "), mc_home, _("mc.ext file has changed\n\
494 with version 3.0. It seems that installation\n\
495 failed. Please fetch a fresh new copy from the\n\
496 Midnight Commander package."), NULL);
497 message (1, msg, msg2);
498 g_free (msg);
499 g_free (msg2);
500 return 0;
504 if (home_error) {
505 char *msg;
506 char *msg2;
507 msg =
508 g_strconcat (" ~/", MC_USER_EXT, _(" file error "), NULL);
509 msg2 =
510 g_strconcat (_("Format of the ~/"), MC_USER_EXT, _(" file has changed\n\
511 with version 3.0. You may want either to\n\
512 copy it from "), mc_home, _("mc.ext or use that\n\
513 file as an example of how to write it.\n\
514 "), mc_home, _("mc.ext will be used for this moment."),
515 NULL);
516 message (1, msg, msg2);
517 g_free (msg);
518 g_free (msg2);
521 mc_stat (filename, &mystat);
523 old_patterns = easy_patterns;
524 easy_patterns = 0; /* Real regular expressions are needed :) */
525 include_target = NULL;
526 include_target_len = 0;
527 for (p = data; *p; p++) {
528 for (q = p; *q == ' ' || *q == '\t'; q++);
529 if (*q == '\n' || !*q)
530 p = q; /* empty line */
531 if (*p == '#') /* comment */
532 while (*p && *p != '\n')
533 p++;
534 if (*p == '\n')
535 continue;
536 if (!*p)
537 break;
538 if (p == q) { /* i.e. starts in the first column, should be
539 * keyword/descNL
541 found = 0;
542 q = strchr (p, '\n');
543 if (q == NULL)
544 q = strchr (p, 0);
545 c = *q;
546 *q = 0;
547 if (include_target) {
548 if ((strncmp (p, "include/", 8) == 0) &&
549 (strncmp (p + 8, include_target, include_target_len) ==
551 found = 1;
552 } else if (!strncmp (p, "regex/", 6)) {
553 p += 6;
554 /* Do not transform shell patterns, you can use shell/ for
555 * that
557 if (regexp_match (p, filename, match_normal))
558 found = 1;
559 } else if (!strncmp (p, "directory/", 10)) {
560 if (S_ISDIR (mystat.st_mode)
561 && regexp_match (p + 10, filename, match_normal))
562 found = 1;
563 } else if (!strncmp (p, "shell/", 6)) {
564 p += 6;
565 if (*p == '.' && file_len >= (q - p)) {
566 if (!strncmp (p, filename + file_len - (q - p), q - p))
567 found = 1;
568 } else {
569 if (q - p == file_len && !strncmp (p, filename, q - p))
570 found = 1;
572 } else if (!strncmp (p, "type/", 5)) {
573 p += 5;
574 found =
575 regex_check_type (filename, file_len, p, &have_type);
576 } else if (!strncmp (p, "default/", 8)) {
577 found = 1;
579 *q = c;
580 p = q;
581 if (!*p)
582 break;
583 } else { /* List of actions */
584 p = q;
585 q = strchr (p, '\n');
586 if (q == NULL)
587 q = strchr (p, 0);
588 if (found) {
589 r = strchr (p, '=');
590 if (r != NULL) {
591 c = *r;
592 *r = 0;
593 if (strcmp (p, "Include") == 0) {
594 char *t;
596 include_target = p + 8;
597 t = strchr (include_target, '\n');
598 if (t)
599 *t = 0;
600 include_target_len = strlen (include_target);
601 if (t)
602 *t = '\n';
604 *r = c;
605 p = q;
606 found = 0;
608 if (!*p)
609 break;
610 continue;
612 if (!strcmp (action, p)) {
613 *r = c;
614 for (p = r + 1; *p == ' ' || *p == '\t'; p++);
616 /* Empty commands just stop searching
617 * through, they don't do anything
619 * We need to copy the filename because exec_extension
620 * may end up invoking update_panels thus making the
621 * filename parameter invalid (ie, most of the time,
622 * we get filename as a pointer from cpanel->dir).
624 if (p < q) {
625 char *filename_copy = g_strdup (filename);
627 exec_extension (filename_copy, r + 1, move_dir,
628 view_at_line_number);
629 g_free (filename_copy);
631 ret = 1;
633 break;
634 } else
635 *r = c;
638 p = q;
639 if (!*p)
640 break;
643 easy_patterns = old_patterns;
644 return ret;