another typo: too late at light corrections...:-(
[midnight-commander.git] / src / ext.c
blob4669fea9432b8e27508a8b5d2b0833d498bf62f8
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, int start_line)
68 char *file_name;
69 int cmd_file_fd;
70 FILE *cmd_file;
71 int expand_prefix_found = 0;
72 int parameter_found = 0;
73 char prompt [80];
74 int run_view = 0;
75 int def_hex_mode = default_hex_mode, changed_hex_mode = 0;
76 int def_nroff_flag = default_nroff_flag, changed_nroff_flag = 0;
77 int written_nonspace = 0;
78 int is_cd = 0;
79 char buffer [1024];
80 char *p = 0;
81 char *localcopy = NULL;
82 int do_local_copy;
83 time_t localmtime = 0;
84 struct stat mystat;
85 quote_func_t quote_func = name_quote;
87 g_return_if_fail (filename != NULL);
88 g_return_if_fail (data != NULL);
90 /* Avoid making a local copy if we are doing a cd */
91 if (!vfs_file_is_local(filename))
92 do_local_copy = 1;
93 else
94 do_local_copy = 0;
97 * All commands should be run in /bin/sh regardless of user shell.
98 * To do that, create temporary shell script and run it.
99 * Sometimes it's not needed (e.g. for %cd and %view commands),
100 * but it's easier to create it anyway.
102 cmd_file_fd = mc_mkstemps(&file_name, "mcext", SCRIPT_SUFFIX);
104 if (cmd_file_fd == -1){
105 message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "),
106 unix_error_string (errno));
107 return;
109 cmd_file = fdopen (cmd_file_fd, "w");
110 fputs ("#! /bin/sh\n", cmd_file);
112 prompt [0] = 0;
113 for (;*data && *data != '\n'; data++){
114 if (parameter_found){
115 if (*data == '}'){
116 char *parameter;
117 parameter_found = 0;
118 parameter = input_dialog (_(" Parameter "), prompt, "");
119 if (!parameter){
120 /* User canceled */
121 fclose (cmd_file);
122 unlink (file_name);
123 if (localcopy) {
124 mc_ungetlocalcopy (filename, localcopy, 0);
126 g_free (file_name);
127 return;
129 fputs (parameter, cmd_file);
130 written_nonspace = 1;
131 g_free (parameter);
132 } else {
133 int len = strlen (prompt);
135 if (len < sizeof (prompt) - 1){
136 prompt [len] = *data;
137 prompt [len+1] = 0;
140 } else if (expand_prefix_found){
141 expand_prefix_found = 0;
142 if (*data == '{')
143 parameter_found = 1;
144 else {
145 int i = check_format_view (data);
146 char *v;
148 if (i){
149 data += i - 1;
150 run_view = 1;
151 } else if ((i = check_format_cd (data)) > 0) {
152 is_cd = 1;
153 quote_func = fake_name_quote;
154 do_local_copy = 0;
155 p = buffer;
156 data += i - 1;
157 } else if ((i = check_format_var (data, &v)) > 0 && v){
158 fputs (v, cmd_file);
159 g_free (v);
160 data += i;
161 } else {
162 char *text;
164 if (*data == 'f'){
165 if (do_local_copy){
166 localcopy = mc_getlocalcopy (filename);
167 if (localcopy == NULL) {
168 fclose(cmd_file);
169 unlink(file_name);
170 g_free (file_name);
171 return;
173 mc_stat (localcopy, &mystat);
174 localmtime = mystat.st_mtime;
175 text = (*quote_func) (localcopy, 0);
176 } else {
177 text = (*quote_func) (filename, 0);
179 } else
180 text = expand_format (NULL, *data, !is_cd);
181 if (!is_cd)
182 fputs (text, cmd_file);
183 else {
184 strcpy (p, text);
185 p = strchr (p, 0);
187 g_free (text);
188 written_nonspace = 1;
191 } else {
192 if (*data == '%')
193 expand_prefix_found = 1;
194 else {
195 if (*data != ' ' && *data != '\t')
196 written_nonspace = 1;
197 if (is_cd)
198 *(p++) = *data;
199 else
200 fputc (*data, cmd_file);
203 } /* for */
205 /* Make sure that the file removes itself when it finishes */
206 fprintf (cmd_file, "\n/bin/rm -f %s\n", file_name);
207 fclose (cmd_file);
209 if ((run_view && !written_nonspace) || is_cd) {
210 unlink (file_name);
211 g_free (file_name);
212 file_name = NULL;
213 } else {
214 chmod (file_name, S_IRWXU);
217 if (run_view){
218 altered_hex_mode = 0;
219 altered_nroff_flag = 0;
220 if (def_hex_mode != default_hex_mode)
221 changed_hex_mode = 1;
222 if (def_nroff_flag != default_nroff_flag)
223 changed_nroff_flag = 1;
225 /* If we've written whitespace only, then just load filename
226 * into view
228 if (written_nonspace)
229 view (file_name, filename, move_dir, start_line);
230 else
231 view (0, filename, move_dir, start_line);
232 if (changed_hex_mode && !altered_hex_mode)
233 default_hex_mode = def_hex_mode;
234 if (changed_nroff_flag && !altered_nroff_flag)
235 default_nroff_flag = def_nroff_flag;
236 repaint_screen ();
237 } else if (is_cd) {
238 char *q;
239 *p = 0;
240 p = buffer;
241 /* while (*p == ' ' && *p == '\t')
242 * p++;
244 /* Search last non-space character. Start search at the end in order
245 not to short filenames containing spaces. */
246 q = p + strlen (p) - 1;
247 while (q >= p && (*q == ' ' || *q == '\t'))
248 q--;
249 q[1] = 0;
250 do_cd (p, cd_parse_command);
251 } else {
252 shell_execute (file_name, EXECUTE_INTERNAL);
253 if (console_flag) {
254 handle_console (CONSOLE_SAVE);
255 if (output_lines && keybar_visible) {
256 show_console_contents (output_start_y,
257 LINES-keybar_visible-output_lines-1,
258 LINES-keybar_visible-1);
263 if (file_name) {
264 g_free (file_name);
266 if (localcopy) {
267 mc_stat (localcopy, &mystat);
268 mc_ungetlocalcopy (filename, localcopy, localmtime != mystat.st_mtime);
272 #ifdef FILE_L
273 # define FILE_CMD "file -L "
274 #else
275 # define FILE_CMD "file "
276 #endif
279 * Run the "file" command on the local file.
280 * Return 1 if the data is valid, 0 otherwise.
283 get_file_type_local (char *filename, char *buf, int buflen)
285 int read_bytes = 0;
287 char *tmp = name_quote (filename, 0);
288 char *command = g_strconcat (FILE_CMD, tmp, NULL);
289 FILE *f = popen (command, "r");
291 g_free (tmp);
292 g_free (command);
293 if (f != NULL) {
294 read_bytes = (fgets (buf, buflen - 1, f)
295 != NULL);
296 if (read_bytes == 0)
297 buf[0] = 0;
298 pclose (f);
299 #ifdef SCO_FLAVOR
301 ** SCO 3.2 does has a buggy pclose(), so
302 ** <command> become zombie (alex)
304 waitpid (-1, NULL, WNOHANG);
305 #endif /* SCO_FLAVOR */
308 return (read_bytes > 0);
312 #ifdef FILE_STDIN
314 * Read file through VFS and feed is to the "file" command.
315 * Return 1 if the data is valid, 0 otherwise.
318 get_file_type_pipe (char *filename, char *buf, int buflen)
320 int read_bytes = 0;
322 int pipehandle, remotehandle;
323 pid_t p;
325 remotehandle = mc_open (filename, O_RDONLY);
326 if (remotehandle != -1) {
327 /* 8192 is HOWMANY hardcoded value in the file-3.14
328 * sources. Tell me if any other file uses larger
329 * chunk from beginning
331 pipehandle = mc_doublepopen
332 (remotehandle, 8192, &p, "file", "file", "-", NULL);
333 if (pipehandle != -1) {
334 int i;
335 while ((i = read (pipehandle, buf
336 + read_bytes, buflen - 1 - read_bytes)) > 0)
337 read_bytes += i;
338 mc_doublepclose (pipehandle, p);
339 buf[read_bytes] = 0;
341 mc_close (remotehandle);
344 return (read_bytes > 0);
346 #endif /* FILE_STDIN */
350 * Invoke the "file" command on the file and match its output against PTR.
351 * have_type is a flag that is set if we already have tried to determine
352 * the type of that file.
353 * Return 1 for match, 0 otherwise.
355 static int
356 regex_check_type (char *filename, int file_len, char *ptr, int *have_type)
358 int found = 0;
359 int islocal;
361 /* Following variables are valid if *have_type is 1 */
362 static char content_string[2048];
363 static int content_shift = 0;
364 static int got_data = 0;
366 if (!use_file_to_check_type) {
367 return 0;
370 islocal = vfs_file_is_local (filename);
372 if (!*have_type) {
373 /* Don't repeate even unsuccessful checks */
374 *have_type = 1;
376 if (islocal) {
377 got_data = get_file_type_local (filename, content_string,
378 sizeof (content_string));
379 } else
380 #ifdef FILE_STDIN
382 got_data = get_file_type_pipe (filename, content_string,
383 sizeof (content_string));
385 #else
386 /* Cannot use pipe, must make a local copy, not yet supported */
387 return 0;
388 #endif /* !FILE_STDIN */
390 if (got_data) {
391 char *pp;
393 /* Paranoid termination */
394 content_string[sizeof (content_string) - 1] = 0;
396 if ((pp = strchr (content_string, '\n')) != 0)
397 *pp = 0;
399 if (islocal && !strncmp (content_string, filename, file_len)) {
400 /* Skip "filename: " */
401 content_shift = file_len;
402 if (content_string[content_shift] == ':')
403 for (content_shift++;
404 content_string[content_shift] == ' ';
405 content_shift++);
406 } else if (!islocal
407 && !strncmp (content_string, "standard input:",
408 15)) {
409 /* Skip "standard input: " */
410 for (content_shift = 15;
411 content_string[content_shift] == ' ';
412 content_shift++);
414 } else {
415 /* No data */
416 content_string[0] = 0;
420 if (content_string && content_string[0] &&
421 regexp_match (ptr, content_string + content_shift, match_normal)) {
422 found = 1;
425 return found;
429 /* The second argument is action, i.e. Open, View or Edit
431 * This function returns:
433 * 1 if it ran some command or 0 otherwise.
435 * If action == "View" then a parameter is checked in the form of "View:%d",
436 * if the value for %d exists, then the viewer is started up at that line number.
439 regex_command (char *filename, char *action, int *move_dir)
441 char *p, *q, *r, c;
442 int file_len = strlen (filename);
443 int found = 0;
444 int ret = 0;
445 int old_patterns;
446 struct stat mystat;
447 int view_at_line_number;
448 char *include_target;
449 int include_target_len;
450 int have_type = 0; /* Flag used by regex_check_type() */
452 /* Check for the special View:%d parameter */
453 if (strncmp (action, "View:", 5) == 0) {
454 view_at_line_number = atoi (action + 5);
455 action[4] = 0;
456 } else {
457 view_at_line_number = 0;
460 if (data == NULL) {
461 char *extension_file;
462 int mc_user_ext = 1;
463 int home_error = 0;
465 extension_file = concat_dir_and_file (home_dir, MC_USER_EXT);
466 if (!exist_file (extension_file)) {
467 g_free (extension_file);
468 check_stock_mc_ext:
469 extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT);
470 mc_user_ext = 0;
472 data = load_file (extension_file);
473 g_free (extension_file);
474 if (data == NULL)
475 return 0;
477 if (!strstr (data, "default/")) {
478 if (!strstr (data, "regex/") && !strstr (data, "shell/") &&
479 !strstr (data, "type/")) {
480 g_free (data);
481 data = NULL;
482 if (mc_user_ext) {
483 home_error = 1;
484 goto check_stock_mc_ext;
485 } else {
486 char *msg;
487 char *msg2;
488 msg =
489 g_strconcat (" ", mc_home, MC_LIB_EXT,
490 _(" file error "), NULL);
491 msg2 =
492 g_strconcat (_("Format of the "), mc_home,
493 _("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."),
497 NULL);
498 message (1, msg, "%s", msg2);
499 g_free (msg);
500 g_free (msg2);
501 return 0;
505 if (home_error) {
506 char *msg;
507 char *msg2;
508 msg =
509 g_strconcat (" ~/", MC_USER_EXT, _(" file error "), NULL);
510 msg2 =
511 g_strconcat (_("Format of the "), "~/", MC_USER_EXT,
512 _(" file has changed\n"
513 "with version 3.0. You may want either to\n"
514 "copy it from "), mc_home,
515 _("mc.ext or use that\n"
516 "file as an example of how to write it.\n"),
517 mc_home,
518 _("mc.ext will be used for this moment."),
519 NULL);
520 message (1, msg, "%s", msg2);
521 g_free (msg);
522 g_free (msg2);
525 mc_stat (filename, &mystat);
527 old_patterns = easy_patterns;
528 easy_patterns = 0; /* Real regular expressions are needed :) */
529 include_target = NULL;
530 include_target_len = 0;
531 for (p = data; *p; p++) {
532 for (q = p; *q == ' ' || *q == '\t'; q++);
533 if (*q == '\n' || !*q)
534 p = q; /* empty line */
535 if (*p == '#') /* comment */
536 while (*p && *p != '\n')
537 p++;
538 if (*p == '\n')
539 continue;
540 if (!*p)
541 break;
542 if (p == q) { /* i.e. starts in the first column, should be
543 * keyword/descNL
545 found = 0;
546 q = strchr (p, '\n');
547 if (q == NULL)
548 q = strchr (p, 0);
549 c = *q;
550 *q = 0;
551 if (include_target) {
552 if ((strncmp (p, "include/", 8) == 0) &&
553 (strncmp (p + 8, include_target, include_target_len) ==
555 found = 1;
556 } else if (!strncmp (p, "regex/", 6)) {
557 p += 6;
558 /* Do not transform shell patterns, you can use shell/ for
559 * that
561 if (regexp_match (p, filename, match_normal))
562 found = 1;
563 } else if (!strncmp (p, "directory/", 10)) {
564 if (S_ISDIR (mystat.st_mode)
565 && regexp_match (p + 10, filename, match_normal))
566 found = 1;
567 } else if (!strncmp (p, "shell/", 6)) {
568 p += 6;
569 if (*p == '.' && file_len >= (q - p)) {
570 if (!strncmp (p, filename + file_len - (q - p), q - p))
571 found = 1;
572 } else {
573 if (q - p == file_len && !strncmp (p, filename, q - p))
574 found = 1;
576 } else if (!strncmp (p, "type/", 5)) {
577 p += 5;
578 found =
579 regex_check_type (filename, file_len, p, &have_type);
580 } else if (!strncmp (p, "default/", 8)) {
581 found = 1;
583 *q = c;
584 p = q;
585 if (!*p)
586 break;
587 } else { /* List of actions */
588 p = q;
589 q = strchr (p, '\n');
590 if (q == NULL)
591 q = strchr (p, 0);
592 if (found) {
593 r = strchr (p, '=');
594 if (r != NULL) {
595 c = *r;
596 *r = 0;
597 if (strcmp (p, "Include") == 0) {
598 char *t;
600 include_target = p + 8;
601 t = strchr (include_target, '\n');
602 if (t)
603 *t = 0;
604 include_target_len = strlen (include_target);
605 if (t)
606 *t = '\n';
608 *r = c;
609 p = q;
610 found = 0;
612 if (!*p)
613 break;
614 continue;
616 if (!strcmp (action, p)) {
617 *r = c;
618 for (p = r + 1; *p == ' ' || *p == '\t'; p++);
620 /* Empty commands just stop searching
621 * through, they don't do anything
623 * We need to copy the filename because exec_extension
624 * may end up invoking update_panels thus making the
625 * filename parameter invalid (ie, most of the time,
626 * we get filename as a pointer from cpanel->dir).
628 if (p < q) {
629 char *filename_copy = g_strdup (filename);
631 exec_extension (filename_copy, r + 1, move_dir,
632 view_at_line_number);
633 g_free (filename_copy);
635 ret = 1;
637 break;
638 } else
639 *r = c;
642 p = q;
643 if (!*p)
644 break;
647 easy_patterns = old_patterns;
648 return ret;