diff --git a/ChangeLog b/ChangeLog index c27399b00..99fbd9b45 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,11 @@ * src/main.c, src/panel.h, src/screen.c, src/setup.c: - Add support for showing executables at first in the panel view +2009-01-25 Patrick Winnertz & Sergei Trofimovich + + * mhl/string.h, vfs/fish.c, vfs/utilvfs.c, vfs/utilvfs.h: Reworked fish code + so that symlinks and files which special characters works now + 2009-01-25 Enrico Weigelt * edit/editcmd.c, src/cmd.c, src/ext.c, src/history.h: diff --git a/mhl/string.h b/mhl/string.h index fde86a71d..28f61985b 100644 --- a/mhl/string.h +++ b/mhl/string.h @@ -9,6 +9,11 @@ #define mhl_str_ndup(str,len) ((str ? strndup(str,len) : strdup(""))) #define mhl_str_len(str) ((str ? strlen(str) : 0)) +static inline char * mhl_str_dup_range(const char * s_start, const char * s_bound) +{ + return mhl_str_ndup(s_start, s_bound - s_start); +} + static inline char* mhl_str_trim(char* str) { if (!str) return NULL; // NULL string ?! bail out. @@ -121,4 +126,4 @@ static inline char* mhl_str_reverse(char* ptr) return ptr; } -#endif +#endif /* __MHL_STRING_H */ diff --git a/vfs/fish.c b/vfs/fish.c index 63e4d60f9..29b5f67b8 100644 --- a/vfs/fish.c +++ b/vfs/fish.c @@ -50,6 +50,9 @@ #include "tcputil.h" #include "../src/unixcompat.h" #include "fish.h" +#include "../mhl/memory.h" +#include "../mhl/string.h" +#include "../mhl/escape.h" int fish_directory_timeout = 900; @@ -358,33 +361,100 @@ fish_dir_load(struct vfs_class *me, struct vfs_s_inode *dir, char *remote_path) char *quoted_path; int reply_code; +#if 0 + /* + * Simple FISH debug interface :] + */ + if (!(MEDATA->logfile)) + { + MEDATA->logfile = fopen("/tmp/mc-FISH.sh", "w"); + } +#endif // 0 + logfile = MEDATA->logfile; print_vfs_message(_("fish: Reading directory %s..."), remote_path); gettimeofday(&dir->timestamp, NULL); dir->timestamp.tv_sec += fish_directory_timeout; - quoted_path = name_quote (remote_path, 0); + quoted_path = mhl_shell_escape_dup (remote_path); fish_command (me, super, NONE, - "#LIST /%s\n" - "if ls -1 /%s >/dev/null 2>&1 ;\n" - "then\n" - "ls -lLan /%s 2>/dev/null | grep '^[^cbt]' | (\n" - "while read p l u g s m d y n; do\n" - "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n" - "done\n" - ")\n" - "ls -lan /%s 2>/dev/null | grep '^[cb]' | (\n" - "while read p l u g a i m d y n; do\n" - "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n" - "done\n" - ")\n" - "echo '### 200'\n" - "else\n" - "echo '### 500'\n" - "fi\n", - remote_path, quoted_path, quoted_path, quoted_path); - g_free (quoted_path); + "#LIST /%s\n" + "if `perl -v > /dev/null 2>&1` ; then\n" + "perl -e '\n" + "use strict;\n" + "use POSIX;\n" + "use Fcntl;\n" + "use POSIX \":fcntl_h\"; #S_ISLNK was here until 5.6\n" + "import Fcntl \":mode\" unless defined &S_ISLNK; #and is now here\n" + "my $dirname = $ARGV[0];\n" + "if (opendir ( DIR, $dirname )) {\n" + "while( (my $filename = readdir(DIR))){\n" + "my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = lstat(\"$dirname/$filename\");\n" + "my $mloctime= scalar localtime $mtime;\n" + "my $shell_escape_regex= s/([;<>\\*\\|`&\\$!#\\(\\)\\[\\]\\{\\}:'\\''\"\\ \\\\])/\\\\$1/g;\n" + "my $e_filename = $filename;\n" + "$e_filename =~ $shell_escape_regex;\n" + "if (S_ISLNK($mode) ) {\n" + "my $linkname = readlink (\"$dirname/$filename\");\n" + "$linkname =~ $shell_escape_regex;\n" + "\n" + "printf(\"R%%o %%o $uid.$gid\\n" + "S$size\\n" + "d$mloctime\\n" + ":\\\"$e_filename\\\" -> \\\"$linkname\\\"\\n" + "\\n\", S_IMODE($mode), S_IFMT($mode));\n" + "} else {\n" + "printf(\"R%%o %%o $uid.$gid\\n" + "S$size\\n" + "d$mloctime\\n" + ":\\\"$e_filename\\\"\\n" + "\\n\", S_IMODE($mode), S_IFMT($mode));\n" + "}}\n" + "printf(\"### 200\\n\");\n" + "closedir(DIR);\n" + "} else {\n" + "printf(\"### 500\\n\");\n" + "}\n" + "exit 0\n" + "' /%s ||\n" /* ARGV[0] - path to browse */ + " echo '### 500'\n" /* do not hang if perl is to eval it */ + "elif `ls -1 /%s >/dev/null 2>&1` ; then\n" + "if `ls -Q /%s >/dev/null 2>&1`; then\n" + "LSOPT=\"-Qlan\";\n" + "ADD=0;\n" + "else\n" + "LSOPT=\"-lan\";\n" + "ADD=1;\n" + "fi\n" + "ls $LSOPT \"/%s\" 2>/dev/null | grep '^[^cbt]' | (\n" + "while read p l u g s m d y n; do\n" + "if [ $ADD = 0 ]; then\n" + "echo \"P$p $u.$g\nS$s\nd$m $d $y\n:$n\n\"\n" + "elif `sed --version >/dev/null 2>&1` ; then\n" + "file=`echo $n | sed -e 's#^\\(.*\\) -> \\(.*\\)#\\1\" -> \"\\2#'`\n" + "echo \"P$p $u $g\nS$s\nd$m $d $y\n:\"$file\"\n\"\n" + "else\n" + "echo \"P$p $u $g\nS$s\nd$m $d $y\n:\"$n\"\n\"\n" + "fi\n" + "done )\n" + "ls $LSOPT /%s 2>/dev/null | grep '^[cb]' | (\n" + "while read p l u g a i m d y n; do\n" + "if [ $ADD = 0 ]; then\n" + "echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"\n" + "elif `sed --version >/dev/null 2>&1` ; then\n" + "file=`echo $n | sed -e 's#^\\(.*\\) -> \\(.*\\)#\\1\" -> \"\\2#'`\n" + "echo \"P$p $u $g\nS$s\nd$m $d $y\n:\"$file\"\n\"\n" + "else\n" + "echo \"P$p $u $g\nS$s\nd$m $d $y\n:\"$n\"\n\"\n" + "fi\n" + "done)\n" + "echo '### 200'\n" + "else\n" + "echo '### 500'\n" + "fi\n", + quoted_path, quoted_path, quoted_path, quoted_path, quoted_path, quoted_path); + mhl_mem_free (quoted_path); ent = vfs_s_generate_entry(me, NULL, dir, 0); while (1) { int res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), SUP.sockr); @@ -412,11 +482,61 @@ fish_dir_load(struct vfs_class *me, struct vfs_s_inode *dir, char *remote_path) switch(buffer[0]) { case ':': { - if (!strcmp(buffer+1, ".") || !strcmp(buffer+1, "..")) - break; /* We'll do . and .. ourself */ - ent->name = g_strdup(buffer+1); - break; - } + char *data_start = buffer+1; + char *filename = data_start; + char *linkname = data_start; + char *filename_bound = filename + strlen(filename); + char *linkname_bound = filename_bound; + if (!strcmp(data_start, "\".\"") || !strcmp(data_start, "\"..\"")) + break; /* We'll do "." and ".." ourselves */ + + if (S_ISLNK(ST.st_mode)) { + // we expect: "escaped-name" -> "escaped-name" + // -> cannot occur in filenames, + // because it will be escaped to -\> + + if (*filename == '"') + ++filename; + + linkname = strstr(filename, "\" -> \""); + if (!linkname) + { + /* broken client, or smth goes wrong */ + linkname = filename_bound; + if (filename_bound > filename + && *(filename_bound - 1) == '"') + --filename_bound; // skip trailing " + } + else + { + filename_bound = linkname; + linkname += 6; // strlen ("\" -> \"") + if (*(linkname_bound - 1) == '"') + --linkname_bound; // skip trailing " + } + + ent->name = mhl_str_dup_range(filename, filename_bound); + mhl_shell_unescape_buf(ent->name); + + ent->ino->linkname = mhl_str_dup_range(linkname, linkname_bound); + mhl_shell_unescape_buf(ent->ino->linkname); + } else { + // we expect: "escaped-name" + if (filename_bound - filename > 2) + { + // there is at least 2 " + // and we skip them + if (*filename == '"') + ++filename; + if (*(filename_bound - 1) == '"') + --filename_bound; + } + + ent->name = mhl_str_dup_range(filename, filename_bound); + mhl_shell_unescape_buf(ent->name); + } + break; + } case 'S': #ifdef HAVE_ATOLL ST.st_size = (off_t) atoll (buffer+1); @@ -426,11 +546,14 @@ fish_dir_load(struct vfs_class *me, struct vfs_s_inode *dir, char *remote_path) break; case 'P': { size_t skipped; - - if (vfs_parse_filemode (buffer + 1, &skipped, &ST.st_mode)) { - if (S_ISLNK(ST.st_mode)) - ST.st_mode = 0; - } + vfs_parse_filemode (buffer + 1, &skipped, &ST.st_mode); + break; + } + case 'R': { + // raw filemode: + // we expect: Roctal-filemode octal-filetype uid.gid + size_t skipped; + vfs_parse_raw_filemode (buffer + 1, &skipped, &ST.st_mode); break; } case 'd': { @@ -456,8 +579,6 @@ fish_dir_load(struct vfs_class *me, struct vfs_s_inode *dir, char *remote_path) ST.st_rdev = makedev (maj, min); #endif } - case 'L': ent->ino->linkname = g_strdup(buffer+1); - break; } } @@ -528,8 +649,8 @@ fish_file_store(struct vfs_class *me, struct vfs_s_fh *fh, char *name, char *loc * algorithm for file appending case, therefore just "dd" is used for it. */ - print_vfs_message(_("fish: store %s: sending command..."), name ); - quoted_name = name_quote (name, 0); + quoted_name = mhl_shell_escape_dup(name); + print_vfs_message(_("fish: store %s: sending command..."), quoted_name ); /* FIXME: File size is limited to ULONG_MAX */ if (!fh->u.fish.append) @@ -541,7 +662,7 @@ fish_file_store(struct vfs_class *me, struct vfs_s_fh *fh, char *name, char *loc "(\n" "head -c %lu -q - || echo DD >&3\n" ") 2>/dev/null | (\n" - "cat > \"$file\"\n" + "cat > $file\n" "cat > /dev/null\n" ")`; [ \"$res\" = DD ] && {\n" "> \"$file\"\n" @@ -553,7 +674,7 @@ fish_file_store(struct vfs_class *me, struct vfs_s_fh *fh, char *name, char *loc " rest=`expr $rest - $n`\n" "done\n" "}; echo '### 200'\n", - (unsigned long) s.st_size, name, + (unsigned long) s.st_size, quoted_name, quoted_name, (unsigned long) s.st_size, (unsigned long) s.st_size); else @@ -566,14 +687,13 @@ fish_file_store(struct vfs_class *me, struct vfs_s_fh *fh, char *name, char *loc "while [ $rest -gt 0 ]\n" "do\n" " cnt=`expr \\( $rest + 255 \\) / 256`\n" - " n=`dd bs=256 count=$cnt | tee -a \"$file\" | wc -c`\n" + " n=`dd bs=256 count=$cnt | tee -a $file | wc -c`\n" " rest=`expr $rest - $n`\n" "done\n" "}; echo '### 200'\n", - (unsigned long) s.st_size, name, + (unsigned long) s.st_size, quoted_name, quoted_name, (unsigned long) s.st_size); - g_free (quoted_name); if (n != PRELIM) { close (h); ERRNOR(E_REMOTE, -1); @@ -606,12 +726,14 @@ fish_file_store(struct vfs_class *me, struct vfs_s_fh *fh, char *name, char *loc (unsigned long) s.st_size); } close(h); + mhl_mem_free(quoted_name); if ((fish_get_reply (me, SUP.sockr, NULL, 0) != COMPLETE) || was_error) ERRNOR (E_REMOTE, -1); return 0; error_return: close(h); fish_get_reply(me, SUP.sockr, NULL, 0); + mhl_mem_free(quoted_name); return -1; } @@ -625,9 +747,7 @@ fish_linear_start (struct vfs_class *me, struct vfs_s_fh *fh, off_t offset) name = vfs_s_fullpath (me, fh->ino); if (!name) return 0; - quoted_name = name_quote (name, 0); - g_free (name); - name = quoted_name; + quoted_name = mhl_shell_escape_dup(name); fh->u.fish.append = 0; /* @@ -642,16 +762,16 @@ fish_linear_start (struct vfs_class *me, struct vfs_s_fh *fh, off_t offset) "then\n" "ls -ln /%s 2>/dev/null | (\n" "read p l u g s r\n" - "echo \"$s\"\n" + "echo $s\n" ")\n" "echo '### 100'\n" "cat /%s\n" "echo '### 200'\n" "else\n" - "echo '### 500'\n" + "echo '### 500'\n" "fi\n", - name, name, name, name ); - g_free (name); + quoted_name, quoted_name, quoted_name, quoted_name ); + g_free (quoted_name); if (offset != PRELIM) ERRNOR (E_REMOTE, 0); fh->linear = LS_LINEAR_OPEN; fh->u.fish.got = 0; @@ -766,7 +886,7 @@ fish_send_command(struct vfs_class *me, struct vfs_s_super *super, const char *c g_free (mpath); \ return -1; \ } \ - rpath = name_quote (crpath, 0); \ + rpath = mhl_shell_escape_dup(crpath); \ g_free (mpath); #define POSTFIX(flags) \ @@ -778,7 +898,7 @@ fish_chmod (struct vfs_class *me, const char *path, int mode) { PREFIX g_snprintf(buf, sizeof(buf), "#CHMOD %4.4o /%s\n" - "chmod %4.4o \"/%s\" 2>/dev/null\n" + "chmod %4.4o /%s 2>/dev/null\n" "echo '### 000'\n", mode & 07777, rpath, mode & 07777, rpath); @@ -801,13 +921,13 @@ static int fish_##name (struct vfs_class *me, const char *path1, const char *pat g_free (mpath2); \ return -1; \ } \ - rpath1 = name_quote (crpath1, 0); \ + rpath1 = mhl_shell_escape_dup (crpath1); \ g_free (mpath1); \ - rpath2 = name_quote (crpath2, 0); \ + rpath2 = mhl_shell_escape_dup (crpath2); \ g_free (mpath2); \ g_snprintf(buf, sizeof(buf), string "\n", rpath1, rpath2, rpath1, rpath2); \ - g_free (rpath1); \ - g_free (rpath2); \ + mhl_mem_free (rpath1); \ + mhl_mem_free (rpath2); \ return fish_send_command(me, super2, buf, OPT_FLUSH); \ } @@ -822,13 +942,13 @@ static int fish_symlink (struct vfs_class *me, const char *setto, const char *pa { char *qsetto; PREFIX - qsetto = name_quote (setto, 0); + qsetto = mhl_shell_escape_dup (setto); g_snprintf(buf, sizeof(buf), "#SYMLINK %s /%s\n" "ln -s %s /%s 2>/dev/null\n" "echo '### 000'\n", qsetto, rpath, qsetto, rpath); - g_free (qsetto); + mhl_mem_free (qsetto); POSTFIX(OPT_FLUSH); } @@ -850,7 +970,7 @@ fish_chown (struct vfs_class *me, const char *path, int owner, int group) { PREFIX g_snprintf (buf, sizeof(buf), - "#CHOWN /%s /%s\n" + "#CHOWN %s /%s\n" "chown %s /%s 2>/dev/null\n" "echo '### 000'\n", sowner, rpath, @@ -858,8 +978,8 @@ fish_chown (struct vfs_class *me, const char *path, int owner, int group) fish_send_command (me, super, buf, OPT_FLUSH); /* FIXME: what should we report if chgrp succeeds but chown fails? */ g_snprintf (buf, sizeof(buf), - "#CHGRP /%s /%s\n" - "chgrp %s /%s 2>/dev/null\n" + "#CHGRP /%s \"/%s\"\n" + "chgrp %s \"/%s\" 2>/dev/null\n" "echo '### 000'\n", sgroup, rpath, sgroup, rpath); diff --git a/vfs/utilvfs.c b/vfs/utilvfs.c index 42927275a..7bb5ca2a9 100644 --- a/vfs/utilvfs.c +++ b/vfs/utilvfs.c @@ -539,6 +539,64 @@ vfs_parse_filemode (const char *s, size_t *ret_skipped, return TRUE; } +gboolean +vfs_parse_raw_filemode (const char *s, size_t *ret_skipped, + mode_t *ret_mode) +{ + const char *p; + mode_t remote_type = 0, local_type, perms = 0; + + p = s; + + // isoctal + while(*p >= '0' && *p <= '7') + { + perms *= 010; + perms += (*p - '0'); + ++p; + } + + if (*p++ != ' ') + return FALSE; + + while(*p >= '0' && *p <= '7') + { + remote_type *= 010; + remote_type += (*p - '0'); + ++p; + } + + if (*p++ != ' ') + return FALSE; + + /* generated with: + $ perl -e 'use Fcntl ":mode"; + my @modes = (S_IFDIR, S_IFBLK, S_IFCHR, S_IFLNK, S_IFREG); + foreach $t (@modes) { printf ("%o\n", $t); };' + TODO: S_IFDOOR, S_IFIFO, S_IFSOCK (if supported by os) + (see vfs_parse_filetype) + */ + + switch (remote_type) + { + case 020000: + local_type = S_IFCHR; break; + case 040000: + local_type = S_IFDIR; break; + case 060000: + local_type = S_IFBLK; break; + case 0120000: + local_type = S_IFLNK; break; + case 0100000: + default:// don't know what is it + local_type = S_IFREG; break; + } + + *ret_skipped = p - s; + *ret_mode = local_type | perms; + return TRUE; +} + /* This function parses from idx in the columns[] array */ int vfs_parse_filedate (int idx, time_t *t) diff --git a/vfs/utilvfs.h b/vfs/utilvfs.h index c92061006..d0255036e 100644 --- a/vfs/utilvfs.h +++ b/vfs/utilvfs.h @@ -24,6 +24,8 @@ gboolean vfs_parse_fileperms (const char *s, size_t *ret_skipped, mode_t *ret_perms); gboolean vfs_parse_filemode (const char *s, size_t *ret_skipped, mode_t *ret_mode); +gboolean vfs_parse_raw_filemode (const char *s, size_t *ret_skipped, + mode_t *ret_mode); int vfs_parse_ls_lga (const char *p, struct stat *s, char **filename, char **linkname);