* view.c (view_update_bytes_per_line): Don't use vertical bars
[midnight-commander.git] / src / ext.c
blob30a7471890f16bb5190212a0ac21a8034fcea7f1
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 <string.h>
29 #include <errno.h>
31 #include "global.h"
32 #include "tty.h"
33 #include "user.h"
34 #include "main.h"
35 #include "dialog.h"
36 #include "ext.h"
37 #include "view.h"
38 #include "main.h"
39 #include "../vfs/vfs.h"
41 #include "cons.saver.h"
42 #include "layout.h"
44 /* If set, we execute the file command to check the file type */
45 int use_file_to_check_type = 1;
47 /* This variable points to a copy of the mc.ext file in memory
48 * With this we avoid loading/parsing the file each time we
49 * need it
51 static char *data = NULL;
53 void
54 flush_extension_file (void)
56 if (data) {
57 g_free (data);
58 data = NULL;
63 typedef char *(*quote_func_t) (const char *name, int i);
65 static void
66 exec_extension (const char *filename, const char *data, int *move_dir,
67 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,
107 _(" Cannot create temporary command file \n %s "),
108 unix_error_string (errno));
109 return;
111 cmd_file = fdopen (cmd_file_fd, "w");
112 fputs ("#! /bin/sh\n", cmd_file);
114 prompt[0] = 0;
115 for (; *data && *data != '\n'; data++) {
116 if (parameter_found) {
117 if (*data == '}') {
118 char *parameter;
119 parameter_found = 0;
120 parameter = input_dialog (_(" Parameter "), prompt, "");
121 if (!parameter) {
122 /* User canceled */
123 fclose (cmd_file);
124 unlink (file_name);
125 if (localcopy) {
126 mc_ungetlocalcopy (filename, localcopy, 0);
128 g_free (file_name);
129 return;
131 fputs (parameter, cmd_file);
132 written_nonspace = 1;
133 g_free (parameter);
134 } else {
135 int len = strlen (prompt);
137 if (len < sizeof (prompt) - 1) {
138 prompt[len] = *data;
139 prompt[len + 1] = 0;
142 } else if (expand_prefix_found) {
143 expand_prefix_found = 0;
144 if (*data == '{')
145 parameter_found = 1;
146 else {
147 int i = check_format_view (data);
148 char *v;
150 if (i) {
151 data += i - 1;
152 run_view = 1;
153 } else if ((i = check_format_cd (data)) > 0) {
154 is_cd = 1;
155 quote_func = fake_name_quote;
156 do_local_copy = 0;
157 p = buffer;
158 data += i - 1;
159 } else if ((i = check_format_var (data, &v)) > 0 && v) {
160 fputs (v, cmd_file);
161 g_free (v);
162 data += i;
163 } else {
164 char *text;
166 if (*data == 'f') {
167 if (do_local_copy) {
168 localcopy = mc_getlocalcopy (filename);
169 if (localcopy == NULL) {
170 fclose (cmd_file);
171 unlink (file_name);
172 g_free (file_name);
173 return;
175 mc_stat (localcopy, &mystat);
176 localmtime = mystat.st_mtime;
177 text = (*quote_func) (localcopy, 0);
178 } else {
179 text = (*quote_func) (filename, 0);
181 } else
182 text = expand_format (NULL, *data, !is_cd);
183 if (!is_cd)
184 fputs (text, cmd_file);
185 else {
186 strcpy (p, text);
187 p = strchr (p, 0);
189 g_free (text);
190 written_nonspace = 1;
193 } else {
194 if (*data == '%')
195 expand_prefix_found = 1;
196 else {
197 if (*data != ' ' && *data != '\t')
198 written_nonspace = 1;
199 if (is_cd)
200 *(p++) = *data;
201 else
202 fputc (*data, cmd_file);
205 } /* for */
207 /* Make sure that the file removes itself when it finishes */
208 fprintf (cmd_file, "\n/bin/rm -f %s\n", file_name);
209 fclose (cmd_file);
211 if ((run_view && !written_nonspace) || is_cd) {
212 unlink (file_name);
213 g_free (file_name);
214 file_name = NULL;
215 } else {
216 chmod (file_name, S_IRWXU);
219 if (run_view) {
220 altered_hex_mode = 0;
221 altered_nroff_flag = 0;
222 if (def_hex_mode != default_hex_mode)
223 changed_hex_mode = 1;
224 if (def_nroff_flag != default_nroff_flag)
225 changed_nroff_flag = 1;
227 /* If we've written whitespace only, then just load filename
228 * into view
230 if (written_nonspace)
231 view (file_name, filename, move_dir, start_line);
232 else
233 view (0, filename, move_dir, start_line);
234 if (changed_hex_mode && !altered_hex_mode)
235 default_hex_mode = def_hex_mode;
236 if (changed_nroff_flag && !altered_nroff_flag)
237 default_nroff_flag = def_nroff_flag;
238 repaint_screen ();
239 } else if (is_cd) {
240 char *q;
241 *p = 0;
242 p = buffer;
243 /* while (*p == ' ' && *p == '\t')
244 * p++;
246 /* Search last non-space character. Start search at the end in order
247 not to short filenames containing spaces. */
248 q = p + strlen (p) - 1;
249 while (q >= p && (*q == ' ' || *q == '\t'))
250 q--;
251 q[1] = 0;
252 do_cd (p, cd_parse_command);
253 } else {
254 shell_execute (file_name, EXECUTE_INTERNAL);
255 if (console_flag) {
256 handle_console (CONSOLE_SAVE);
257 if (output_lines && keybar_visible) {
258 show_console_contents (output_start_y,
259 LINES - keybar_visible -
260 output_lines - 1,
261 LINES - keybar_visible - 1);
266 if (file_name) {
267 g_free (file_name);
269 if (localcopy) {
270 mc_stat (localcopy, &mystat);
271 mc_ungetlocalcopy (filename, localcopy,
272 localmtime != mystat.st_mtime);
276 #ifdef FILE_L
277 # define FILE_CMD "file -L "
278 #else
279 # define FILE_CMD "file "
280 #endif
283 * Run the "file" command on the local file.
284 * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
286 static int
287 get_file_type_local (char *filename, char *buf, int buflen)
289 int read_bytes = 0;
291 char *tmp = name_quote (filename, 0);
292 char *command = g_strconcat (FILE_CMD, tmp, NULL);
293 FILE *f = popen (command, "r");
295 g_free (tmp);
296 g_free (command);
297 if (f != NULL) {
298 read_bytes = (fgets (buf, buflen - 1, f)
299 != NULL);
300 if (read_bytes == 0)
301 buf[0] = 0;
302 pclose (f);
303 #ifdef SCO_FLAVOR
305 ** SCO 3.2 does has a buggy pclose(), so
306 ** <command> become zombie (alex)
308 waitpid (-1, NULL, WNOHANG);
309 #endif /* SCO_FLAVOR */
310 } else {
311 return -1;
314 return (read_bytes > 0);
318 #ifdef FILE_STDIN
320 * Read file through VFS and feed is to the "file" command.
321 * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
323 static int
324 get_file_type_pipe (char *filename, char *buf, int buflen)
326 int read_bytes = 0;
328 int pipehandle, remotehandle;
329 pid_t p;
331 remotehandle = mc_open (filename, O_RDONLY);
332 if (remotehandle != -1) {
333 /* 8192 is HOWMANY hardcoded value in the file-3.14
334 * sources. Tell me if any other file uses larger
335 * chunk from beginning
337 pipehandle =
338 mc_doublepopen (remotehandle, 8192, &p, "file", "file", "-",
339 NULL);
340 if (pipehandle != -1) {
341 int i;
342 while ((i =
343 read (pipehandle, buf + read_bytes,
344 buflen - 1 - read_bytes)) > 0)
345 read_bytes += i;
346 mc_doublepclose (pipehandle, p);
347 buf[read_bytes] = 0;
349 mc_close (remotehandle);
350 } else {
351 return -1;
354 return (read_bytes > 0);
356 #endif /* FILE_STDIN */
360 * Invoke the "file" command on the file and match its output against PTR.
361 * have_type is a flag that is set if we already have tried to determine
362 * the type of that file.
363 * Return 1 for match, 0 otherwise.
365 static int
366 regex_check_type (char *filename, int file_len, char *ptr, int *have_type)
368 int found = 0;
369 int islocal;
371 /* Following variables are valid if *have_type is 1 */
372 static char content_string[2048];
373 static int content_shift = 0;
374 static int got_data = 0;
376 if (!use_file_to_check_type) {
377 return 0;
380 islocal = vfs_file_is_local (filename);
382 if (!*have_type) {
383 /* Don't repeate even unsuccessful checks */
384 *have_type = 1;
386 if (islocal) {
387 got_data =
388 get_file_type_local (filename, content_string,
389 sizeof (content_string));
390 } else
391 #ifdef FILE_STDIN
393 got_data =
394 get_file_type_pipe (filename, content_string,
395 sizeof (content_string));
397 #else
398 /* Cannot use pipe, must make a local copy, not yet supported */
399 return 0;
400 #endif /* !FILE_STDIN */
402 if (got_data > 0) {
403 char *pp;
405 /* Paranoid termination */
406 content_string[sizeof (content_string) - 1] = 0;
408 if ((pp = strchr (content_string, '\n')) != 0)
409 *pp = 0;
411 if (islocal && !strncmp (content_string, filename, file_len)) {
412 /* Skip "filename: " */
413 content_shift = file_len;
414 if (content_string[content_shift] == ':')
415 for (content_shift++;
416 content_string[content_shift] == ' ';
417 content_shift++);
418 } else if (!islocal
419 && !strncmp (content_string, "standard input:",
420 15)) {
421 /* Skip "standard input: " */
422 for (content_shift = 15;
423 content_string[content_shift] == ' ';
424 content_shift++);
426 } else {
427 /* No data */
428 content_string[0] = 0;
432 if (got_data == -1) {
433 return -1;
436 if (content_string && content_string[0]
437 && regexp_match (ptr, content_string + content_shift,
438 match_normal)) {
439 found = 1;
442 return found;
446 /* The second argument is action, i.e. Open, View or Edit
448 * This function returns:
450 * -1 for a failure or user interrupt
451 * 0 if no command was run
452 * 1 if some command was run
454 * If action == "View" then a parameter is checked in the form of "View:%d",
455 * if the value for %d exists, then the viewer is started up at that line number.
458 regex_command (char *filename, char *action, int *move_dir)
460 char *p, *q, *r, c;
461 int file_len = strlen (filename);
462 int found = 0;
463 int error_flag = 0;
464 int ret = 0;
465 int old_patterns;
466 struct stat mystat;
467 int view_at_line_number;
468 char *include_target;
469 int include_target_len;
470 int have_type = 0; /* Flag used by regex_check_type() */
472 /* Check for the special View:%d parameter */
473 if (strncmp (action, "View:", 5) == 0) {
474 view_at_line_number = atoi (action + 5);
475 action[4] = 0;
476 } else {
477 view_at_line_number = 0;
480 if (data == NULL) {
481 char *extension_file;
482 int mc_user_ext = 1;
483 int home_error = 0;
485 extension_file = concat_dir_and_file (home_dir, MC_USER_EXT);
486 if (!exist_file (extension_file)) {
487 g_free (extension_file);
488 check_stock_mc_ext:
489 extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT);
490 mc_user_ext = 0;
492 data = load_file (extension_file);
493 g_free (extension_file);
494 if (data == NULL)
495 return 0;
497 if (!strstr (data, "default/")) {
498 if (!strstr (data, "regex/") && !strstr (data, "shell/")
499 && !strstr (data, "type/")) {
500 g_free (data);
501 data = NULL;
502 if (mc_user_ext) {
503 home_error = 1;
504 goto check_stock_mc_ext;
505 } else {
506 char *msg;
507 char *msg2;
508 msg =
509 g_strconcat (" ", mc_home, MC_LIB_EXT,
510 _(" file error "), NULL);
511 msg2 =
512 g_strconcat (_("Format of the "), mc_home,
513 _("mc.ext file has changed\n"
514 "with version 3.0. It seems that installation\n"
515 "failed. Please fetch a fresh new copy from the\n"
516 "Midnight Commander package."),
517 NULL);
518 message (1, msg, "%s", msg2);
519 g_free (msg);
520 g_free (msg2);
521 return 0;
525 if (home_error) {
526 char *msg;
527 char *msg2;
528 msg =
529 g_strconcat (" ~/", MC_USER_EXT, _(" file error "), NULL);
530 msg2 =
531 g_strconcat (_("Format of the "), "~/", MC_USER_EXT,
532 _(" file has changed\n"
533 "with version 3.0. You may want either to\n"
534 "copy it from "), mc_home,
535 _("mc.ext or use that\n"
536 "file as an example of how to write it.\n"),
537 mc_home,
538 _("mc.ext will be used for this moment."),
539 NULL);
540 message (1, msg, "%s", msg2);
541 g_free (msg);
542 g_free (msg2);
545 mc_stat (filename, &mystat);
547 old_patterns = easy_patterns;
548 easy_patterns = 0; /* Real regular expressions are needed :) */
549 include_target = NULL;
550 include_target_len = 0;
551 for (p = data; *p; p++) {
552 for (q = p; *q == ' ' || *q == '\t'; q++);
553 if (*q == '\n' || !*q)
554 p = q; /* empty line */
555 if (*p == '#') /* comment */
556 while (*p && *p != '\n')
557 p++;
558 if (*p == '\n')
559 continue;
560 if (!*p)
561 break;
562 if (p == q) { /* i.e. starts in the first column, should be
563 * keyword/descNL
565 found = 0;
566 q = strchr (p, '\n');
567 if (q == NULL)
568 q = strchr (p, 0);
569 c = *q;
570 *q = 0;
571 if (include_target) {
572 if ((strncmp (p, "include/", 8) == 0)
573 && (strncmp (p + 8, include_target, include_target_len)
574 == 0))
575 found = 1;
576 } else if (!strncmp (p, "regex/", 6)) {
577 p += 6;
578 /* Do not transform shell patterns, you can use shell/ for
579 * that
581 if (regexp_match (p, filename, match_normal))
582 found = 1;
583 } else if (!strncmp (p, "directory/", 10)) {
584 if (S_ISDIR (mystat.st_mode)
585 && regexp_match (p + 10, filename, match_normal))
586 found = 1;
587 } else if (!strncmp (p, "shell/", 6)) {
588 p += 6;
589 if (*p == '.' && file_len >= (q - p)) {
590 if (!strncmp (p, filename + file_len - (q - p), q - p))
591 found = 1;
592 } else {
593 if (q - p == file_len && !strncmp (p, filename, q - p))
594 found = 1;
596 } else if (!strncmp (p, "type/", 5)) {
597 int res;
598 p += 5;
599 res = regex_check_type (filename, file_len, p, &have_type);
600 if (res == 1)
601 found = 1;
602 if (res == -1)
603 error_flag = 1; /* leave it if file cannot be opened */
604 } else if (!strncmp (p, "default/", 8)) {
605 found = 1;
607 *q = c;
608 p = q;
609 if (!*p)
610 break;
611 } else { /* List of actions */
612 p = q;
613 q = strchr (p, '\n');
614 if (q == NULL)
615 q = strchr (p, 0);
616 if (found && !error_flag) {
617 r = strchr (p, '=');
618 if (r != NULL) {
619 c = *r;
620 *r = 0;
621 if (strcmp (p, "Include") == 0) {
622 char *t;
624 include_target = p + 8;
625 t = strchr (include_target, '\n');
626 if (t)
627 *t = 0;
628 include_target_len = strlen (include_target);
629 if (t)
630 *t = '\n';
632 *r = c;
633 p = q;
634 found = 0;
636 if (!*p)
637 break;
638 continue;
640 if (!strcmp (action, p)) {
641 *r = c;
642 for (p = r + 1; *p == ' ' || *p == '\t'; p++);
644 /* Empty commands just stop searching
645 * through, they don't do anything
647 * We need to copy the filename because exec_extension
648 * may end up invoking update_panels thus making the
649 * filename parameter invalid (ie, most of the time,
650 * we get filename as a pointer from cpanel->dir).
652 if (p < q) {
653 char *filename_copy = g_strdup (filename);
655 exec_extension (filename_copy, r + 1, move_dir,
656 view_at_line_number);
657 g_free (filename_copy);
659 ret = 1;
661 break;
662 } else
663 *r = c;
666 p = q;
667 if (!*p)
668 break;
671 easy_patterns = old_patterns;
672 if (error_flag)
673 return -1;
674 return ret;