*** empty log message ***
[midnight-commander.git] / src / ext.c
blob183aff7a37b7d29312137343d9ba2fdb745740ed
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 char *
67 quote_block (quote_func_t quote_func, char **quoting_block)
69 char **p;
70 char *result;
71 char *tail;
72 int tail_index;
73 int current_len;
75 result = NULL;
76 current_len = 0;
77 tail_index = 0;
79 for (p = quoting_block; *p; p++) {
80 int temp_len;
81 char *temp;
83 temp = quote_func (*p, FALSE);
84 temp_len = strlen (temp);
86 current_len += temp_len + 2;
87 result = g_realloc (result, current_len);
88 tail = result + tail_index;
90 strcpy (tail, temp);
91 tail[temp_len] = ' ';
92 tail[temp_len + 1] = '\0';
93 tail_index += temp_len + 1;
95 g_free (temp);
98 return result;
101 void
102 exec_extension (const char *filename, const char *data, char **drops, int *move_dir, int start_line, int needs_term)
104 char *file_name;
105 int cmd_file_fd;
106 FILE *cmd_file;
107 int expand_prefix_found = 0;
108 int parameter_found = 0;
109 char prompt [80];
110 int run_view = 0;
111 int def_hex_mode = default_hex_mode, changed_hex_mode = 0;
112 int def_nroff_flag = default_nroff_flag, changed_nroff_flag = 0;
113 int written_nonspace = 0;
114 int is_cd = 0;
115 char buffer [1024];
116 char *p = 0;
117 char *localcopy = NULL;
118 int do_local_copy;
119 time_t localmtime = 0;
120 struct stat mystat;
121 quote_func_t quote_func = name_quote;
123 g_return_if_fail (filename != NULL);
124 g_return_if_fail (data != NULL);
126 /* Avoid making a local copy if we are doing a cd */
127 if (!vfs_file_is_local(filename))
128 do_local_copy = 1;
129 else
130 do_local_copy = 0;
133 * All commands should be run in /bin/sh regardless of user shell.
134 * To do that, create temporary shell script and run it.
135 * Sometimes it's not needed (e.g. for %cd and %view commands),
136 * but it's easier to create it anyway.
138 cmd_file_fd = mc_mkstemps(&file_name, "mcext", SCRIPT_SUFFIX);
140 if (cmd_file_fd == -1){
141 message (1, MSG_ERROR, _(" Cannot create temporary command file \n %s "),
142 unix_error_string (errno));
143 return;
145 cmd_file = fdopen (cmd_file_fd, "w");
146 fputs ("#!/bin/sh\n", cmd_file);
148 prompt [0] = 0;
149 for (;*data && *data != '\n'; data++){
150 if (parameter_found){
151 if (*data == '}'){
152 char *parameter;
153 parameter_found = 0;
154 parameter = input_dialog (_(" Parameter "), prompt, "");
155 if (!parameter){
156 /* User canceled */
157 fclose (cmd_file);
158 unlink (file_name);
159 if (localcopy) {
160 mc_ungetlocalcopy (filename, localcopy, 0);
162 g_free (file_name);
163 return;
165 fputs (parameter, cmd_file);
166 written_nonspace = 1;
167 g_free (parameter);
168 } else {
169 int len = strlen (prompt);
171 if (len < sizeof (prompt) - 1){
172 prompt [len] = *data;
173 prompt [len+1] = 0;
176 } else if (expand_prefix_found){
177 expand_prefix_found = 0;
178 if (*data == '{')
179 parameter_found = 1;
180 else {
181 int i = check_format_view (data);
182 char *v;
184 if (i){
185 data += i - 1;
186 run_view = 1;
187 } else if ((i = check_format_cd (data)) > 0) {
188 is_cd = 1;
189 quote_func = fake_name_quote;
190 do_local_copy = 0;
191 p = buffer;
192 data += i - 1;
193 } else if ((i = check_format_var (data, &v)) > 0 && v){
194 fputs (v, cmd_file);
195 g_free (v);
196 data += i;
197 } else {
198 char *text;
200 if (*data == 'f'){
201 if (do_local_copy){
202 localcopy = mc_getlocalcopy (filename);
203 if (localcopy == NULL) {
204 fclose(cmd_file);
205 unlink(file_name);
206 g_free (file_name);
207 return;
209 mc_stat (localcopy, &mystat);
210 localmtime = mystat.st_mtime;
211 text = (*quote_func) (localcopy, 0);
212 } else {
213 text = (*quote_func) (filename, 0);
215 } else if (*data == 'q') {
216 text = quote_block (quote_func, drops);
217 } else
218 text = expand_format (NULL, *data, !is_cd);
219 if (!is_cd)
220 fputs (text, cmd_file);
221 else {
222 strcpy (p, text);
223 p = strchr (p, 0);
225 g_free (text);
226 written_nonspace = 1;
229 } else {
230 if (*data == '%')
231 expand_prefix_found = 1;
232 else {
233 if (*data != ' ' && *data != '\t')
234 written_nonspace = 1;
235 if (is_cd)
236 *(p++) = *data;
237 else
238 fputc (*data, cmd_file);
241 } /* for */
243 /* Make sure that the file removes itself when it finishes */
244 fprintf (cmd_file, "\n/bin/rm -f %s\n", file_name);
245 fclose (cmd_file);
247 if ((run_view && !written_nonspace) || is_cd) {
248 unlink (file_name);
249 g_free (file_name);
250 file_name = NULL;
251 } else {
252 chmod (file_name, S_IRWXU);
255 if (run_view){
256 altered_hex_mode = 0;
257 altered_nroff_flag = 0;
258 if (def_hex_mode != default_hex_mode)
259 changed_hex_mode = 1;
260 if (def_nroff_flag != default_nroff_flag)
261 changed_nroff_flag = 1;
263 /* If we've written whitespace only, then just load filename
264 * into view
266 if (written_nonspace)
267 view (file_name, filename, move_dir, start_line);
268 else
269 view (0, filename, move_dir, start_line);
270 if (changed_hex_mode && !altered_hex_mode)
271 default_hex_mode = def_hex_mode;
272 if (changed_nroff_flag && !altered_nroff_flag)
273 default_nroff_flag = def_nroff_flag;
274 repaint_screen ();
275 } else if (is_cd) {
276 char *q;
277 *p = 0;
278 p = buffer;
279 while (*p == ' ' && *p == '\t')
280 p++;
282 /* Search last non-space character. Start search at the end in order
283 not to short filenames containing spaces. */
284 q = p + strlen (p) - 1;
285 while (q >= p && (*q == ' ' || *q == '\t'))
286 q--;
287 q[1] = 0;
288 do_cd (p, cd_parse_command);
289 } else {
290 shell_execute (file_name, EXECUTE_INTERNAL | EXECUTE_TEMPFILE);
291 if (console_flag) {
292 handle_console (CONSOLE_SAVE);
293 if (output_lines && keybar_visible) {
294 show_console_contents (output_start_y,
295 LINES-keybar_visible-output_lines-1,
296 LINES-keybar_visible-1);
301 if (file_name) {
302 g_free (file_name);
304 if (localcopy) {
305 mc_stat (localcopy, &mystat);
306 mc_ungetlocalcopy (filename, localcopy, localmtime != mystat.st_mtime);
310 #ifdef FILE_L
311 # define FILE_CMD "file -L "
312 #else
313 # define FILE_CMD "file "
314 #endif
316 /* The second argument is action, i.e. Open, View, Edit, Drop, or NULL if
317 * we want regex_command to return a list of all user defined actions.
318 * Third argument is space separated list of dropped files (for actions
319 * other then Drop it should be NULL);
321 * This function returns:
323 * If action != NULL, then it returns "Success" (not allocated) if it ran
324 * some command or NULL if not.
326 * If action == NULL, it returns NULL if there are no user defined commands
327 * or an allocated space separated list of user defined Actions.
329 * If action == "Icon", we are doing again something special. We return
330 * icon name and we set the variable regex_command_title to Title for
331 * that icon.
333 * If action == "View" then a parameter is checked in the form of "View:%d",
334 * if the value for %d exists, then the viewer is started up at that line number.
336 char *regex_command_title = NULL;
337 char *regex_command (char *filename, char *action, char **drops, int *move_dir)
339 char *p, *q, *r, c;
340 int file_len = strlen (filename);
341 int found = 0;
342 char content_string [2048];
343 int content_shift = 0;
344 char *to_return = NULL;
345 int old_patterns;
346 struct stat mystat;
347 int asked_file;
348 int view_at_line_number;
349 char *include_target;
350 int include_target_len;
352 #ifdef FILE_STDIN
353 int file_supports_stdin = 1;
354 #else
355 int file_supports_stdin = 0;
356 #endif
358 /* Check for the special View:%d parameter */
359 if (action && strncmp (action, "View:", 5) == 0){
360 view_at_line_number = atoi (action + 5);
361 action [4] = 0;
362 } else {
363 view_at_line_number = 0;
365 /* Have we asked file for the file contents? */
366 asked_file = 0;
368 if (data == NULL) {
369 char *extension_file;
370 int mc_user_ext = 1;
371 int home_error = 0;
373 extension_file = concat_dir_and_file (home_dir, MC_USER_EXT);
374 if (!exist_file (extension_file)) {
375 g_free (extension_file);
376 check_stock_mc_ext:
377 extension_file = concat_dir_and_file (mc_home, MC_LIB_EXT);
378 mc_user_ext = 0;
380 data = load_file (extension_file);
381 g_free (extension_file);
382 if (data == NULL)
383 return 0;
385 if (!strstr (data, "default/")) {
386 if (!strstr (data, "regex/") && !strstr (data, "shell/") &&
387 !strstr (data, "type/")) {
388 g_free (data);
389 data = NULL;
390 if (mc_user_ext) {
391 home_error = 1;
392 goto check_stock_mc_ext;
393 } else {
394 char *msg;
395 char *msg2;
396 msg = g_strconcat (" ", mc_home, MC_LIB_EXT, _(" file error"), NULL);
397 msg2 = g_strconcat (_("Format of the "),
398 mc_home,
399 _("mc.ext file has changed\n\
400 with version 3.0. It seems that installation\n\
401 failed. Please fetch a fresh new copy from the\n\
402 Midnight Commander package."), NULL);
403 message (1, msg, msg2);
404 g_free (msg);
405 g_free (msg2);
406 return 0;
410 if (home_error) {
411 char *msg;
412 char *msg2;
413 msg = g_strconcat (" ~/", MC_USER_EXT, _(" file error "), NULL);
414 msg2 = g_strconcat (_("Format of the ~/"), MC_USER_EXT, _(" file has changed\n\
415 with version 3.0. You may want either to\n\
416 copy it from "), mc_home, _("mc.ext or use that\n\
417 file as an example of how to write it.\n\
418 "), mc_home, _("mc.ext will be used for this moment."), NULL);
419 message (1, msg, msg2);
420 g_free (msg);
421 g_free (msg2);
424 mc_stat (filename, &mystat);
426 if (regex_command_title){
427 g_free (regex_command_title);
428 regex_command_title = NULL;
430 old_patterns = easy_patterns;
431 easy_patterns = 0; /* Real regular expressions are needed :) */
432 include_target = NULL;
433 include_target_len = 0;
434 for (p = data; *p; p++) {
435 for (q = p; *q == ' ' || *q == '\t'; q++)
437 if (*q == '\n' || !*q)
438 p = q; /* empty line */
439 if (*p == '#') /* comment */
440 while (*p && *p != '\n')
441 p++;
442 if (*p == '\n')
443 continue;
444 if (!*p)
445 break;
446 if (p == q) { /* i.e. starts in the first column, should be
447 * keyword/descNL
449 if (found && action == NULL) /* We have already accumulated all
450 * the user actions
452 break;
453 found = 0;
454 q = strchr (p, '\n');
455 if (q == NULL)
456 q = strchr (p, 0);
457 c = *q;
458 *q = 0;
459 if (include_target){
460 if ((strncmp (p, "include/", 8) == 0) &&
461 (strncmp (p+8, include_target, include_target_len) == 0))
462 found = 1;
463 } else if (!strncmp (p, "regex/", 6)) {
464 p += 6;
465 /* Do not transform shell patterns, you can use shell/ for
466 * that
468 if (regexp_match (p, filename, match_normal))
469 found = 1;
470 } else if (!strncmp (p, "directory/", 10)) {
471 if (S_ISDIR (mystat.st_mode) && regexp_match (p+10, filename, match_normal))
472 found = 1;
473 } else if (!strncmp (p, "shell/", 6)) {
474 p += 6;
475 if (*p == '.') {
476 if (!strncmp (p, filename + file_len - (q - p),
477 q - p))
478 found = 1;
479 } else {
480 if (q - p == file_len && !strncmp (p, filename, q - p))
481 found = 1;
483 } else if (!strncmp (p, "type/", 5)) {
484 int islocal = vfs_file_is_local (filename);
485 p += 5;
487 if (islocal || file_supports_stdin) {
488 char *pp;
489 int hasread = use_file_to_check_type;
491 if (asked_file || !use_file_to_check_type)
492 goto match_file_output;
494 hasread = 0;
495 if (islocal) {
496 char *tmp = name_quote (filename, 0);
497 char *command =
498 g_strconcat (FILE_CMD, tmp, NULL);
499 FILE *f = popen (command, "r");
501 g_free (tmp);
502 g_free (command);
503 if (f != NULL) {
504 hasread = (fgets (content_string, 2047, f)
505 != NULL);
506 if (!hasread)
507 content_string [0] = 0;
508 pclose (f);
509 #ifdef SCO_FLAVOR
511 ** SCO 3.2 does has a buggy pclose(), so
512 ** <command> become zombie (alex)
514 waitpid(-1,NULL,WNOHANG);
515 #endif /* SCO_FLAVOR */
517 } else {
518 #ifdef _OS_NT
519 message (1, " Win32 ", " Unimplemented file prediction ");
520 #else
521 int pipehandle, remotehandle;
522 pid_t p;
524 remotehandle = mc_open (filename, O_RDONLY);
525 if (remotehandle != -1) {
526 /* 8192 is HOWMANY hardcoded value in the file-3.14
527 * sources. Tell me if any other file uses larger
528 * chunk from beginning
530 pipehandle = mc_doublepopen
531 (remotehandle, 8192, &p,"file", "file", "-", NULL);
532 if (pipehandle != -1) {
533 int i;
534 while ((i = read (pipehandle, content_string
535 + hasread, 2047 - hasread)) > 0)
536 hasread += i;
537 mc_doublepclose (pipehandle, p);
538 content_string [hasread] = 0;
540 mc_close (remotehandle);
542 #endif /* _OS_NT */
544 asked_file = 1;
545 match_file_output:
546 if (hasread) {
547 if ((pp = strchr (content_string, '\n')) != 0)
548 *pp = 0;
549 if (islocal && !strncmp (content_string,
550 filename, file_len)) {
551 content_shift = file_len;
552 if (content_string [content_shift] == ':')
553 for (content_shift++;
554 content_string [content_shift] == ' ';
555 content_shift++);
556 } else if (!islocal
557 && !strncmp (content_string,
558 "standard input:", 15)) {
559 for (content_shift = 15;
560 content_string [content_shift] == ' ';
561 content_shift++);
563 if (content_string &&
564 regexp_match (p, content_string +
565 content_shift, match_normal)){
566 found = 1;
570 } else if (!strncmp (p, "default/", 8)) {
571 p += 8;
572 found = 1;
574 *q = c;
575 p = q;
576 if (!*p)
577 break;
578 } else { /* List of actions */
579 p = q;
580 q = strchr (p, '\n');
581 if (q == NULL)
582 q = strchr (p, 0);
583 if (found) {
584 r = strchr (p, '=');
585 if (r != NULL) {
586 c = *r;
587 *r = 0;
588 if (strcmp (p, "Include") == 0){
589 char *t;
591 include_target = p + 8;
592 t = strchr (include_target, '\n');
593 if (t) *t = 0;
594 include_target_len = strlen (include_target);
595 if (t) *t = '\n';
597 *r = c;
598 p = q;
599 found = 0;
601 if (!*p)
602 break;
603 continue;
605 if (action == NULL) {
606 if (strcmp (p, "Open") &&
607 strcmp (p, "View") &&
608 strcmp (p, "Edit") &&
609 strcmp (p, "Drop") &&
610 strcmp (p, "Icon") &&
611 strcmp (p, "Include") &&
612 strcmp (p, "Title")) {
613 /* I.e. this is a name of a user defined action */
614 static char *q;
616 if (to_return == NULL) {
617 to_return = g_malloc (512);
618 q = to_return;
619 } else
620 *(q++) = '='; /* Mark separator */
621 strcpy (q, p);
622 q = strchr (q, 0);
624 *r = c;
625 } else if (!strcmp (action, "Icon")) {
626 if (!strcmp (p, "Icon") && to_return == NULL) {
627 *r = c;
628 c = *q;
629 *q = 0;
630 to_return = g_strdup (r + 1);
631 } else if (!strcmp (p, "Title") && regex_command_title == NULL) {
632 *r = c;
633 c = *q;
634 *q = 0;
635 regex_command_title = g_strdup (r + 1);
636 } else {
637 *r = c;
638 c = *q;
640 *q = c;
641 if (to_return != NULL && regex_command_title != NULL)
642 break;
643 } else if (!strcmp (action, p)) {
644 *r = c;
645 for (p = r + 1; *p == ' ' || *p == '\t'; p++)
648 /* Empty commands just stop searching
649 * through, they don't do anything
651 * We need to copy the filename because exec_extension
652 * may end up invoking update_panels thus making the
653 * filename parameter invalid (ie, most of the time,
654 * we get filename as a pointer from cpanel->dir).
656 if (p < q) {
657 char *filename_copy = g_strdup (filename);
659 exec_extension (filename_copy, r + 1, drops, move_dir, view_at_line_number, 0);
660 g_free (filename_copy);
662 to_return = "Success";
664 break;
665 } else
666 *r = c;
669 p = q;
670 if (!*p)
671 break;
674 easy_patterns = old_patterns;
675 return to_return;