1 /***************************************************************************
2 fish.cpp - a FISH kioslave
4 begin : Thu Oct 4 17:09:14 CEST 2001
5 copyright : (C) 2001-2003 by Jörg Walter
6 email : jwalt-kde@garni.ch
7 ***************************************************************************/
9 /***************************************************************************
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation, version 2 of the License *
15 ***************************************************************************/
18 This code contains fragments and ideas from the ftp kioslave
19 done by David Faure <faure@kde.org>.
21 Structure is a bit complicated, since I made the mistake to use
22 KProcess... now there is a lightweight homebrew async IO system
23 inside, but if signals/slots become available for ioslaves, switching
24 back to KProcess should be easy.
27 #include <config-runtime.h>
28 #include "config-fish.h"
47 #include <sys/socket.h>
48 #include <netinet/in.h>
50 #include <sys/types.h>
56 #ifdef HAVE_SYS_IOCTL_H
57 #include <sys/ioctl.h>
69 #include <kmessagebox.h>
70 #include <kcomponentdata.h>
72 #include <kstandarddirs.h>
74 #include <kremoteencoding.h>
79 #include <kmimetype.h>
82 #include <sys/resource.h>
89 #define myDebug(x) kDebug(7127) << __LINE__ << ": " x
90 #define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0)
91 #define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0)
92 #define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0)
93 #define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0)
94 #define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.count()<<")" << endl); statEntry(x);}while(0)
95 #define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0)
96 #define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0)
97 #define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0)
98 #define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0)
99 #define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0)
100 #define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0)
101 #define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0)
102 #define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0)
103 #define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0)
104 #define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0)
107 #define sendmimeType(x) mimeType(x)
110 static char *sshPath
= NULL
;
111 static char *suPath
= NULL
;
112 // disabled: currently not needed. Didn't work reliably.
113 // static int isOpenSSH = 0;
115 #define E(x) ((const char*)remoteEncoding()->encode(x).data())
120 static void ripper(int)
122 while (waitpid(-1,0,WNOHANG
) > 0) {
127 int KDE_EXPORT
kdemain( int argc
, char **argv
)
129 KComponentData
componentData("fish", "kio_fish");
131 myDebug( << "*** Starting fish " << endl
);
133 myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl
);
137 setenv("TZ", "UTC", true);
139 struct sigaction act
;
140 memset(&act
,0,sizeof(act
));
141 act
.sa_handler
= ripper
;
150 sigaction(SIGCHLD
,&act
,NULL
);
152 fishProtocol
slave(argv
[2], argv
[3]);
153 slave
.dispatchLoop();
155 myDebug( << "*** fish Done" << endl
);
161 const struct fishProtocol::fish_info
fishProtocol::fishInfo
[] = {
163 ("echo; /bin/sh -c start_fish_server > /dev/null 2>/dev/null; perl .fishsrv.pl " CHECKSUM
" 2>/dev/null; perl -e '$|=1; print \"### 100 transfer fish server\\n\"; while(<STDIN>) { last if /^__END__/; $code.=$_; } exit(eval($code));' 2>/dev/null;"),
165 { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0,
166 ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"),
172 ("echo `ls -Lla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -Lla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
173 "ls -Lla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
176 ("echo `ls -dLla %1 2> /dev/null | grep '^[-dsplcb]' | wc -l`; ls -dLla %1 2>/dev/null | grep '^[-dspl]' | ( while read -r p x u g s m d y n; do file -b -i $n 2>/dev/null | sed -e '\\,^[^/]*$,d;s/^/M/;s,/.*[ \t],/,'; FILE=%1; if [ -e %1\"/$n\" ]; then FILE=%1\"/$n\"; fi; if [ -L \"$FILE\" ]; then echo \":$n\"; ls -lad \"$FILE\" | sed -e 's/.* -> /L/'; else echo \":$n\" | sed -e 's/ -> /\\\nL/'; fi; echo \"P$p $u.$g\nS$s\nd$m $d $y\n\"; done; );"
177 "ls -dLla %1 2>/dev/null | grep '^[cb]' | ( while read -r p x u g a i m d y n; do echo \"P$p $u.$g\nE$a$i\nd$m $d $y\n:$n\n\"; done; )"),
180 ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"),
183 ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;"
184 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"),
217 ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;"
218 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;"
219 "dd bs=%2 count=1; ) 2>/dev/null;"),
221 // Yes, this is "ibs=1", since dd "count" is input blocks.
222 // On network connections, read() may not fill the buffer
223 // completely (no more data immediately available), but dd
224 // does ignore that fact by design. Sorry, writes are slow.
225 // OTOH, WRITE is not used by the current ioslave methods,
228 (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | "
229 "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"),
232 ("if [ -L %1 ]; then if cp -pdf %1 %2 2>/dev/null; then :; else LINK=\"`readlink %1`\"; ln -sf $LINK %2; fi; else cp -pf %1 %2; fi"),
235 (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"),
238 ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"),
242 fishProtocol::fishProtocol(const QByteArray
&pool_socket
, const QByteArray
&app_socket
)
243 : SlaveBase("fish", pool_socket
, app_socket
), mimeBuffer(1024, '\0'),
246 myDebug( << "fishProtocol::fishProtocol()" << endl
);
247 if (sshPath
== NULL
) {
248 // disabled: currently not needed. Didn't work reliably.
249 // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null");
250 sshPath
= strdup(QFile::encodeName(KStandardDirs::findExe("ssh")));
252 if (suPath
== NULL
) {
253 suPath
= strdup(QFile::encodeName(KStandardDirs::findExe("su")));
266 connectionAuth
.keepPassword
= true;
267 connectionAuth
.url
.setProtocol("fish");
276 isStat
= false; // FIXME: just a workaround for konq deficiencies
277 redirectUser
= ""; // FIXME: just a workaround for konq deficiencies
278 redirectPass
= ""; // FIXME: just a workaround for konq deficiencies
279 fishCodeLen
= strlen(fishCode
);
281 /* ---------------------------------------------------------------------------------- */
284 fishProtocol::~fishProtocol()
286 myDebug( << "fishProtocol::~fishProtocol()" << endl
);
287 shutdownConnection(true);
290 /* --------------------------------------------------------------------------- */
293 Connects to a server and logs us in via SSH. Then starts FISH protocol.
295 void fishProtocol::openConnection() {
296 if (childPid
) return;
298 if (connectionHost
.isEmpty())
300 error( KIO::ERR_UNKNOWN_HOST
, QString() );
304 infoMessage(i18n("Connecting..."));
306 myDebug( << "connecting to: " << connectionUser
<< "@" << connectionHost
<< ":" << connectionPort
<< endl
);
307 sendCommand(FISH_FISH
);
308 sendCommand(FISH_VER
);
309 if (connectionStart()) {
310 error(ERR_COULD_NOT_CONNECT
,connectionHost
);
311 shutdownConnection();
314 myDebug( << "subprocess is running" << endl
);
318 static int open_pty_pair(int fd
[2])
320 #if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY)
321 /** with kind regards to The GNU C Library
322 Reference Manual for Version 2.2.x of the GNU C Library */
326 memset(&ti
,0,sizeof(ti
));
328 ti
.c_cflag
= CLOCAL
|CREAD
|CS8
;
334 master
= open("/dev/ptmx", O_RDWR
);
336 if (master
< 0) return 0;
338 if (grantpt(master
) < 0 || unlockpt(master
) < 0) goto close_master
;
340 name
= ptsname(master
);
341 if (name
== NULL
) goto close_master
;
343 slave
= open(name
, O_RDWR
);
344 if (slave
== -1) goto close_master
;
346 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
347 if (isastream(slave
) &&
348 (ioctl(slave
, I_PUSH
, "ptem") < 0 ||
349 ioctl(slave
, I_PUSH
, "ldterm") < 0))
353 tcsetattr(slave
, TCSANOW
, &ti
);
358 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
369 memset(&ti
,0,sizeof(ti
));
371 ti
.c_cflag
= CLOCAL
|CREAD
|CS8
;
374 return openpty(fd
,fd
+1,NULL
,&ti
,NULL
);
377 #warning "No tty support available. Password dialog won't work."
379 return socketpair(PF_UNIX
,SOCK_STREAM
,0,fd
);
384 creates the subprocess
386 bool fishProtocol::connectionStart() {
391 rc
= open_pty_pair(fd
);
393 myDebug( << "socketpair failed, error: " << strerror(errno
) << endl
);
397 if (!requestNetwork()) return true;
398 myDebug( << "Exec: " << (local
? suPath
: sshPath
) << " Port: " << connectionPort
<< " User: " << connectionUser
<< endl
);
400 if (childPid
== -1) {
401 myDebug( << "fork failed, error: " << strerror(errno
) << endl
);
409 // taken from konsole, see TEPty.C for details
410 // note: if we're running on socket pairs,
411 // this will fail, but thats what we expect
413 for (int sig
= 1; sig
< NSIG
; sig
++) signal(sig
,SIG_DFL
);
416 getrlimit(RLIMIT_NOFILE
, &rlp
);
417 for (int i
= 0; i
< (int)rlp
.rlim_cur
; i
++)
418 if (i
!= fd
[1]) ::close(i
);
423 if (fd
[1] > 2) ::close(fd
[1]);
427 #if defined(TIOCSCTTY)
428 ioctl(0, TIOCSCTTY
, 0);
432 #if defined( _AIX) || defined( __hpux)
435 ioctl(0, TIOCSPGRP
, (char *)&pgrp
);
438 const char *dev
= ttyname(0);
440 if (dev
) ::close(::open(dev
, O_WRONLY
, 0));
444 execl(suPath
, "su", "-", connectionUser
.toLatin1().constData(), "-c", "cd ~;echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0);
446 #define common_args "-l", connectionUser.toLatin1().constData(), "-x", "-e", "none", \
447 "-q", connectionHost.toLatin1().constData(), \
448 "echo FISH:;exec /bin/sh -c \"if env true 2>/dev/null; then env PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; else PS1= PS2= TZ=UTC LANG=C LC_ALL=C LOCALE=C /bin/sh; fi\"", (void *)0
449 // disabled: leave compression up to the client.
450 // (isOpenSSH?"-C":"+C"),
453 execl(sshPath
, "ssh", "-p", qPrintable(QString::number(connectionPort
)), common_args
);
455 execl(sshPath
, "ssh", common_args
);
458 myDebug( << "could not exec! " << strerror(errno
) << endl
);
462 rc
= fcntl(fd
[0],F_GETFL
,&flags
);
463 rc
= fcntl(fd
[0],F_SETFL
,flags
|O_NONBLOCK
);
471 while (!isLoggedIn
) {
472 FD_SET(childFd
,&rfds
);
474 if (outBufPos
>= 0) FD_SET(childFd
,&wfds
);
475 struct timeval timeout
;
477 timeout
.tv_usec
= 1000;
478 rc
= select(childFd
+1, &rfds
, &wfds
, NULL
, &timeout
);
482 myDebug( << "select failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
485 if (FD_ISSET(childFd
,&wfds
) && outBufPos
>= 0) {
486 if (outBuf
) rc
= ::write(childFd
,outBuf
+outBufPos
,outBufLen
-outBufPos
);
488 if (rc
>= 0) outBufPos
+= rc
;
492 myDebug( << "write failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
496 if (outBufPos
>= outBufLen
) {
502 if (FD_ISSET(childFd
,&rfds
)) {
503 rc
= ::read(childFd
,buf
+offset
,32768-offset
);
505 int noff
= establishConnection(buf
,rc
+offset
);
506 if (noff
< 0) return false;
507 if (noff
> 0) memmove(buf
,buf
+offset
+rc
-noff
,noff
);
512 myDebug( << "read failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
521 writes one chunk of data to stdin of child process
523 void fishProtocol::writeChild(const char *buf
, KIO::fileoffset_t len
) {
524 if (outBufPos
>= 0 && outBuf
) {
527 debug
.setLatin1(outBuf
,outBufLen
);
528 if (len
> 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen
<< "," << outBufPos
<< "," << debug
.left(10) << "...)" << endl
);
538 manages initial communication setup including password queries
540 int fishProtocol::establishConnection(char *buffer
, KIO::fileoffset_t len
) {
541 QString buf
= QString::fromLatin1(buffer
,len
);
543 // Strip trailing whitespace
544 while (buf
.length() && (buf
[buf
.length()-1] == ' '))
545 buf
.truncate(buf
.length()-1);
547 myDebug( << "establishing: got " << buf
<< endl
);
548 while (childPid
&& ((pos
= buf
.indexOf('\n')) >= 0 ||
549 buf
.endsWith(':') || buf
.endsWith('?'))) {
551 QString str
= buf
.left(pos
);
555 if (str
== "FISH:\n") {
557 infoMessage(i18n("Initiating protocol..."));
558 if (!connectionAuth
.password
.isEmpty()) {
559 connectionAuth
.password
= connectionAuth
.password
.left(connectionAuth
.password
.length()-1);
560 cacheAuthentication(connectionAuth
);
564 } else if (!str
.isEmpty()) {
566 } else if (buf
.endsWith(':')) {
567 if (!redirectUser
.isEmpty() && connectionUser
!= redirectUser
) {
569 dest
.setUser(redirectUser
);
570 dest
.setPass(redirectPass
);
573 commandCodes
.clear();
578 } else if (!connectionPassword
.isEmpty()) {
579 myDebug( << "sending cpass" << endl
);
580 connectionAuth
.password
= connectionPassword
+'\n';
581 connectionPassword
.clear();
582 // su does not like receiving a password directly after sending
583 // the password prompt so we wait a while.
586 writeChild(connectionAuth
.password
.toLatin1(),connectionAuth
.password
.length());
588 myDebug( << "sending mpass" << endl
);
589 connectionAuth
.prompt
= thisFn
+buf
;
591 connectionAuth
.caption
= i18n("Local Login");
593 connectionAuth
.caption
= i18n("SSH Authorization");
594 if ((!firstLogin
|| !checkCachedAuthentication(connectionAuth
))) {
595 connectionAuth
.password
.clear(); // don't prefill
596 if ( !openPasswordDialog(connectionAuth
)) {
597 error(ERR_USER_CANCELED
,connectionHost
);
598 shutdownConnection();
603 connectionAuth
.password
+= '\n';
604 if (connectionAuth
.username
!= connectionUser
) {
606 dest
.setUser(connectionAuth
.username
);
607 dest
.setPass(connectionAuth
.password
);
609 if (isStat
) { // FIXME: just a workaround for konq deficiencies
610 redirectUser
= connectionAuth
.username
;
611 redirectPass
= connectionAuth
.password
;
614 commandCodes
.clear();
618 myDebug( << "sending pass" << endl
);
621 writeChild(connectionAuth
.password
.toLatin1(),connectionAuth
.password
.length());
625 } else if (buf
.endsWith('?')) {
626 int rc
= messageBox(QuestionYesNo
,thisFn
+buf
);
627 if (rc
== KMessageBox::Yes
) {
628 writeChild("yes\n",4);
630 writeChild("no\n",3);
635 myDebug( << "unmatched case in initial handling! should not happen!" << endl
);
641 void fishProtocol::setHostInternal(const KUrl
& u
){
643 if(port
<= 0 ) // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that.
645 setHost(u
.host(),port
,u
.user(),u
.pass());
649 sets connection information for subsequent commands
651 void fishProtocol::setHost(const QString
& host
, quint16 port
, const QString
& u
, const QString
& pass
){
654 local
= (host
== "localhost" && port
== 0);
655 if (user
.isEmpty()) user
= getenv("LOGNAME");
657 if (host
== connectionHost
&& port
== connectionPort
&& user
== connectionUser
)
659 myDebug( << "setHost " << u
<< "@" << host
<< endl
);
661 if (childPid
) shutdownConnection();
663 connectionHost
= host
;
664 connectionAuth
.url
.setHost(host
);
666 connectionUser
= user
;
667 connectionAuth
.username
= user
;
668 connectionAuth
.url
.setUser(user
);
670 connectionPort
= port
;
671 connectionPassword
= pass
;
676 Forced close of the connection
678 This function gets called from the application side of the universe,
679 it shouldn't send any response.
681 void fishProtocol::closeConnection(){
682 myDebug( << "closeConnection()" << endl
);
683 shutdownConnection(true);
687 Closes the connection
689 void fishProtocol::shutdownConnection(bool forced
){
691 kill(childPid
,SIGTERM
); // We may not have permission...
693 ::close(childFd
); // ...in which case this should do the trick
698 infoMessage(i18n("Disconnected."));
706 commandCodes
.clear();
716 builds each FISH request and sets the error counter
718 bool fishProtocol::sendCommand(fish_command_type cmd
, ...) {
719 const fish_info
&info
= fishInfo
[cmd
];
720 myDebug( << "queuing: cmd="<< cmd
<< "['" << info
.command
<< "'](" << info
.params
<<"), alt=['" << info
.alt
<< "'], lines=" << info
.lines
<< endl
);
724 QString realCmd
= info
.command
;
725 QString realAlt
= info
.alt
;
726 static QRegExp
rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
727 for (int i
= 0; i
< info
.params
; i
++) {
728 QString
arg(va_arg(list
, const char *));
730 while ((pos
= rx
.indexIn(arg
,pos
+2)) >= 0) {
731 arg
.replace(pos
,0,QString("\\"));
733 //myDebug( << "arg " << i << ": " << arg << endl);
734 realCmd
.append(" ").append(arg
);
735 realAlt
.replace(QRegExp('%'+QString::number(i
+1)),arg
);
738 s
.append(realCmd
).append("\n ").append(realAlt
).append(" 2>&1;echo '### 000'\n");
739 if (realCmd
== "FISH")
741 commandList
.append(s
);
742 commandCodes
.append(cmd
);
747 checks response string for result code, converting 000 and 001 appropriately
749 int fishProtocol::handleResponse(const QString
&str
){
750 myDebug( << "handling: " << str
<< endl
);
751 if (str
.startsWith("### ")) {
753 int result
= str
.mid(4,3).toInt(&isOk
);
754 if (!isOk
) result
= 500;
755 if (result
== 0) result
= (errorCount
!= 0?500:200);
756 if (result
== 1) result
= (errorCount
!= 0?500:100);
757 myDebug( << "result: " << result
<< ", errorCount: " << errorCount
<< endl
);
765 int fishProtocol::makeTimeFromLs(const QString
&monthStr
, const QString
&dayStr
, const QString
&timeyearStr
)
767 QDateTime
dt(QDateTime::currentDateTime().toUTC());
768 int year
= dt
.date().year();
769 int month
= dt
.date().month();
770 int currentMonth
= month
;
771 int day
= dayStr
.toInt();
773 static const char * const monthNames
[12] = {
774 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
775 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
778 for (int i
=0; i
< 12; i
++) if (monthStr
.startsWith(monthNames
[i
])) {
783 int pos
= timeyearStr
.indexOf(':');
784 if (timeyearStr
.length() == 4 && pos
== -1) {
785 year
= timeyearStr
.toInt();
786 } else if (pos
== -1) {
789 if (month
> currentMonth
+ 1) year
--;
790 dt
.time().setHMS(timeyearStr
.left(pos
).toInt(),timeyearStr
.mid(pos
+1).toInt(),0);
792 dt
.date().setYMD(year
,month
,day
);
794 return dt
.toTime_t();
798 parses response from server and acts accordingly
800 void fishProtocol::manageConnection(const QString
&l
) {
802 int rc
= handleResponse(line
);
804 long pos
, pos2
, pos3
;
807 switch (fishCommand
) {
809 if (line
.startsWith("VER 0.0.3")) {
811 hasAppend
= line
.contains(" append ");
813 error(ERR_UNSUPPORTED_PROTOCOL
,line
);
814 shutdownConnection();
822 myDebug( << "listReason: " << static_cast<int>(listReason
) << endl
);
825 if (line
.length() > 0) {
826 switch (line
[0].cell()) {
838 long long val
= line
.toLongLong(&isOk
);
839 if (val
> 0 && isOk
) errorCount
--;
840 if ((fishCommand
== FISH_LIST
) && (listReason
== LIST
))
848 if (line
[1] == 'd') {
849 udsMime
= "inode/directory";
852 if (line
[1] == '-') {
854 } else if (line
[1] == 'l') {
856 } else if (line
[1] == 'c') {
858 } else if (line
[1] == 'b') {
860 } else if (line
[1] == 's') {
862 } else if (line
[1] == 'p') {
865 myDebug( << "unknown file type: " << line
[1].cell() << endl
);
870 //myDebug( << "file type: " << udsType << endl);
872 long long accessVal
= 0;
873 if (line
[2] == 'r') accessVal
|= S_IRUSR
;
874 if (line
[3] == 'w') accessVal
|= S_IWUSR
;
875 if (line
[4] == 'x' || line
[4] == 's') accessVal
|= S_IXUSR
;
876 if (line
[4] == 'S' || line
[4] == 's') accessVal
|= S_ISUID
;
877 if (line
[5] == 'r') accessVal
|= S_IRGRP
;
878 if (line
[6] == 'w') accessVal
|= S_IWGRP
;
879 if (line
[7] == 'x' || line
[7] == 's') accessVal
|= S_IXGRP
;
880 if (line
[7] == 'S' || line
[7] == 's') accessVal
|= S_ISGID
;
881 if (line
[8] == 'r') accessVal
|= S_IROTH
;
882 if (line
[9] == 'w') accessVal
|= S_IWOTH
;
883 if (line
[10] == 'x' || line
[10] == 't') accessVal
|= S_IXOTH
;
884 if (line
[10] == 'T' || line
[10] == 't') accessVal
|= S_ISVTX
;
885 udsEntry
.insert(KIO::UDSEntry::UDS_ACCESS
, accessVal
);
887 pos
= line
.indexOf('.',12);
892 udsEntry
.insert(KIO::UDSEntry::UDS_USER
, line
.mid(12,pos
-12));
893 udsEntry
.insert(KIO::UDSEntry::UDS_GROUP
, line
.mid(pos
+1));
898 pos
= line
.indexOf(' ');
899 pos2
= line
.indexOf(' ',pos
+1);
900 if (pos
< 0 || pos2
< 0) break;
902 udsEntry
.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME
,
903 makeTimeFromLs(line
.mid(1,pos
-1), line
.mid(pos
+1,pos2
-pos
), line
.mid(pos2
+1)));
907 pos
= line
.indexOf(' ');
908 pos2
= line
.indexOf(' ',pos
+1);
909 pos3
= line
.indexOf(' ',pos2
+1);
910 if (pos
< 0 || pos2
< 0 || pos3
< 0) break;
911 dt
.setDate(QDate(line
.mid(1,pos
-1).toInt(),line
.mid(pos
+1,pos2
-pos
-1).toInt(),line
.mid(pos2
+1,pos3
-pos2
-1).toInt()));
913 pos2
= line
.indexOf(' ',pos
+1);
914 pos3
= line
.indexOf(' ',pos2
+1);
915 if (pos
< 0 || pos2
< 0 || pos3
< 0) break;
916 dt
.setTime(QTime(line
.mid(pos
+1,pos2
-pos
-1).toInt(),line
.mid(pos2
+1,pos3
-pos2
-1).toInt(),line
.mid(pos3
+1).toInt()));
918 udsEntry
.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME
, dt
.toTime_t());
923 long long sizeVal
= line
.mid(1).toLongLong(&isOk
);
926 udsEntry
.insert(KIO::UDSEntry::UDS_SIZE
, sizeVal
);
935 pos
= line
.lastIndexOf('/');
936 thisFn
= line
.mid(pos
< 0?1:pos
+1);
937 if (fishCommand
== FISH_LIST
) {
938 udsEntry
.insert(KIO::UDSEntry::UDS_NAME
, thisFn
);
940 // By default, the mimetype comes from the extension
941 // We'll use the file(1) result only as fallback [like the rest of KDE does]
943 KUrl
kurl("fish://host/");
944 kurl
.setFileName(thisFn
); // properly encode special chars
945 KMimeType::Ptr mime
= KMimeType::findByUrl(kurl
);
946 if ( mime
->name() != KMimeType::defaultMimeType() )
947 udsMime
= mime
->name();
953 // This is getting ugly. file(1) makes some uneducated
954 // guesses, so we must try to ignore them (#51274)
955 if (udsMime
.isEmpty() && line
.right(8) != "/unknown" &&
956 (thisFn
.indexOf('.') < 0 || (line
.left(8) != "Mtext/x-"
957 && line
!= "Mtext/plain"))) {
958 udsMime
= line
.mid(1);
959 if ( udsMime
== "inode/directory" ) // a symlink to a dir is a dir
966 udsEntry
.insert(KIO::UDSEntry::UDS_LINK_DEST
, line
.mid(1));
967 if (!udsType
) udsType
= S_IFLNK
;
972 if (!udsMime
.isNull())
973 udsEntry
.insert(KIO::UDSEntry::UDS_MIME_TYPE
, udsMime
);
976 udsEntry
.insert( KIO::UDSEntry::UDS_FILE_TYPE
, udsType
);
979 if (fishCommand
== FISH_STAT
)
980 udsStatEntry
= udsEntry
;
981 else if (listReason
== LIST
) {
982 listEntry(udsEntry
, false); //1
983 } else if (listReason
== CHECK
) checkExist
= true; //0
990 if (line
.length() == 0) {
991 error(ERR_IS_DIRECTORY
,url
.prettyUrl());
995 recvLen
= line
.toLongLong(&isOk
);
997 error(ERR_COULD_NOT_READ
,url
.prettyUrl());
998 shutdownConnection();
1005 } else if (rc
== 100) {
1006 switch (fishCommand
) {
1008 writeChild(fishCode
, fishCodeLen
);
1014 myDebug( << "reading " << recvLen
<< endl
);
1015 if (recvLen
== -1) {
1016 error(ERR_COULD_NOT_READ
,url
.prettyUrl());
1017 shutdownConnection();
1021 mimeTypeSent
= false;
1024 mimeType("application/x-zerosize");
1025 mimeTypeSent
= true;
1033 //myDebug( << "sending " << sendLen << endl);
1038 } else if (rc
/100 != 2) {
1039 switch (fishCommand
) {
1043 error(ERR_COULD_NOT_WRITE
,url
.prettyUrl());
1044 shutdownConnection();
1047 error(ERR_COULD_NOT_READ
,url
.prettyUrl());
1048 shutdownConnection();
1053 mimeType("inode/directory");
1054 mimeTypeSent
= true;
1060 error(ERR_COULD_NOT_READ
,url
.prettyUrl());
1061 shutdownConnection();
1066 error(ERR_SLAVE_DEFINED
,line
);
1067 shutdownConnection();
1071 error(ERR_CANNOT_ENTER_DIRECTORY
,url
.prettyUrl());
1074 myDebug( << "list error. reason: " << static_cast<int>(listReason
) << endl
);
1075 if (listReason
== LIST
) error(ERR_CANNOT_ENTER_DIRECTORY
,url
.prettyUrl());
1076 else if (listReason
== CHECK
) {
1082 error(ERR_DOES_NOT_EXIST
,url
.prettyUrl());
1083 udsStatEntry
.clear();
1086 error(ERR_CANNOT_CHMOD
,url
.prettyUrl());
1090 error(ERR_ACCESS_DENIED
,url
.prettyUrl());
1094 error(ERR_DIR_ALREADY_EXIST
,url
.prettyUrl());
1096 error(ERR_COULD_NOT_MKDIR
,url
.prettyUrl());
1099 error(ERR_COULD_NOT_RMDIR
,url
.prettyUrl());
1102 error(ERR_CANNOT_DELETE
,url
.prettyUrl());
1105 error(ERR_CANNOT_RENAME
,url
.prettyUrl());
1110 error(ERR_COULD_NOT_WRITE
,url
.prettyUrl());
1115 if (fishCommand
== FISH_STOR
) fishCommand
= (hasAppend
?FISH_APPEND
:FISH_WRITE
);
1116 if (fishCommand
== FISH_FISH
) {
1118 } else if (fishCommand
== FISH_LIST
) {
1119 if (listReason
== LIST
) {
1120 listEntry(UDSEntry(),true);
1121 } else if (listReason
== CHECK
) {
1122 if (!checkOverwrite
&& checkExist
)
1124 error(ERR_FILE_ALREADY_EXIST
,url
.prettyUrl());
1125 return; // Don't call finished!
1128 } else if (fishCommand
== FISH_STAT
) {
1129 udsStatEntry
.insert( KIO::UDSEntry::UDS_NAME
, url
.fileName() );
1130 statEntry(udsStatEntry
);
1131 } else if (fishCommand
== FISH_APPEND
) {
1133 if (readData(rawData
) > 0) sendCommand(FISH_APPEND
,E(QString::number(rawData
.size())),E(url
.path()));
1134 else if (!checkExist
&& putPerm
> -1) sendCommand(FISH_CHMOD
,E(QString::number(putPerm
,8)),E(url
.path()));
1135 sendLen
= rawData
.size();
1136 } else if (fishCommand
== FISH_WRITE
) {
1138 if (readData(rawData
) > 0) sendCommand(FISH_WRITE
,E(QString::number(putPos
)),E(QString::number(rawData
.size())),E(url
.path()));
1139 else if (!checkExist
&& putPerm
> -1) sendCommand(FISH_CHMOD
,E(QString::number(putPerm
,8)),E(url
.path()));
1140 putPos
+= rawData
.size();
1141 sendLen
= rawData
.size();
1142 } else if (fishCommand
== FISH_RETR
) {
1149 void fishProtocol::writeStdin(const QString
&line
)
1155 //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')) << endl);
1156 myDebug( << "Writing: " << qlist
.first() << endl
);
1157 myDebug( << "---------" << endl
);
1158 writeChild((const char *)qlist
.first().toLatin1(), qlist
.first().length());
1162 void fishProtocol::sent()
1165 myDebug( << "writing raw: " << rawData
.size() << "/" << rawWrite
<< endl
);
1166 writeChild(rawData
.data(),(rawWrite
> rawData
.size()?rawData
.size():rawWrite
));
1167 rawWrite
-= rawData
.size();
1170 if (readData(rawData
) <= 0) {
1171 shutdownConnection();
1175 } else if (rawWrite
== 0) {
1176 // workaround: some dd's insist in reading multiples of
1177 // 8 bytes, swallowing up to seven bytes. Sending
1178 // newlines is safe even when a sane dd is used
1179 writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15);
1183 if (qlist
.count() > 0) qlist
.erase(qlist
.begin());
1184 if (qlist
.count() == 0) {
1187 //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')) << endl);
1188 myDebug( << "Writing: " << qlist
.first() << endl
);
1189 myDebug( << "---------" << endl
);
1190 writeChild((const char *)qlist
.first().toLatin1(),qlist
.first().length());
1194 int fishProtocol::received(const char *buffer
, KIO::fileoffset_t buflen
)
1198 if (buflen
<= 0) break;
1201 myDebug( << "processedSize " << dataRead
<< ", len " << buflen
<< "/" << rawRead
<< endl
);
1202 int dataSize
= (rawRead
> buflen
?buflen
:rawRead
);
1205 int mimeSize
= qMin(dataSize
, (int)(mimeBuffer
.size()-dataRead
));
1206 memcpy(mimeBuffer
.data()+dataRead
,buffer
,mimeSize
);
1207 dataRead
+= mimeSize
;
1208 rawRead
-= mimeSize
;
1211 if (rawRead
== 0) // End of data
1212 mimeBuffer
.resize(dataRead
);
1213 if (dataRead
< (int)mimeBuffer
.size())
1215 myDebug( << "wait for more" << endl
);
1218 sendmimeType(KMimeType::findByNameAndContent(url
.path(), mimeBuffer
)->name());
1219 mimeTypeSent
= true;
1220 if (fishCommand
!= FISH_READ
) {
1221 totalSize(dataRead
+ rawRead
);
1223 processedSize(dataRead
);
1225 mimeBuffer
.resize(1024);
1227 continue; // Process rest of buffer/buflen
1230 QByteArray
bdata(buffer
,dataSize
);
1233 dataRead
+= dataSize
;
1234 rawRead
-= dataSize
;
1235 processedSize(dataRead
);
1244 if (buflen
<= 0) break;
1248 while((pos
< buflen
) && (buffer
[pos
] != '\n'))
1253 QString s
= remoteEncoding()->decode(QByteArray(buffer
,pos
));
1258 manageConnection(s
);
1261 // Find next newline
1262 while((pos
< buflen
) && (buffer
[pos
] != '\n'))
1265 } while (childPid
&& buflen
&& (rawRead
> 0 || pos
< buflen
));
1269 void fishProtocol::get(const KUrl
& u
){
1270 myDebug( << "@@@@@@@@@ get " << u
<< endl
);
1274 if (!isLoggedIn
) return;
1276 if (!url
.hasPath()) {
1277 sendCommand(FISH_PWD
);
1280 sendCommand(FISH_RETR
,E(url
.path()));
1286 void fishProtocol::put(const KUrl
& u
, int permissions
, KIO::JobFlags flags
) {
1287 myDebug( << "@@@@@@@@@ put " << u
<< " " << permissions
<< " " << (flags
& KIO::Overwrite
) << " " /* << resume */ << endl
);
1292 if (!isLoggedIn
) return;
1294 if (!url
.hasPath()) {
1295 sendCommand(FISH_PWD
);
1297 putPerm
= permissions
;
1299 checkOverwrite
= flags
& KIO::Overwrite
;
1303 sendCommand(FISH_LIST
,E(url
.path()));
1304 sendCommand(FISH_STOR
,"0",E(url
.path()));
1306 const QString mtimeStr
= metaData( "modified" );
1307 if ( !mtimeStr
.isEmpty() ) {
1308 QDateTime dt
= QDateTime::fromString( mtimeStr
, Qt::ISODate
);
1309 // TODO set modification time on url.path() somehow
1310 // see FileProtocol::put if using utime() to do that.
1315 /** executes next command in sequence or calls finished() if all is done */
1316 void fishProtocol::finished() {
1317 if (commandList
.count() > 0) {
1318 fishCommand
= (fish_command_type
)commandCodes
.first();
1319 errorCount
= -fishInfo
[fishCommand
].lines
;
1323 udsStatEntry
.clear();
1324 writeStdin(commandList
.first());
1325 //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().indexOf("\n")-1))+"...");
1326 commandList
.erase(commandList
.begin());
1327 commandCodes
.erase(commandCodes
.begin());
1329 myDebug( << "_______ emitting finished()" << endl
);
1330 SlaveBase::finished();
1334 /** aborts command sequence and calls error() */
1335 void fishProtocol::error(int type
, const QString
&detail
) {
1336 commandList
.clear();
1337 commandCodes
.clear();
1338 myDebug( << "ERROR: " << type
<< " - " << detail
<< endl
);
1339 SlaveBase::error(type
,detail
);
1342 /** executes a chain of commands */
1343 void fishProtocol::run() {
1353 FD_SET(childFd
,&rfds
);
1355 if (outBufPos
>= 0) FD_SET(childFd
,&wfds
);
1356 struct timeval timeout
;
1358 timeout
.tv_usec
= 1000;
1359 rc
= select(childFd
+1, &rfds
, &wfds
, NULL
, &timeout
);
1363 myDebug( << "select failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
1364 error(ERR_CONNECTION_BROKEN
,connectionHost
);
1365 shutdownConnection();
1368 if (FD_ISSET(childFd
,&wfds
) && outBufPos
>= 0) {
1371 debug
.setLatin1(outBuf
+outBufPos
,outBufLen
-outBufPos
);
1372 myDebug( << "now writing " << (outBufLen
-outBufPos
) << " " << debug
.left(40) << "..." << endl
);
1374 if (outBufLen
-outBufPos
> 0) rc
= ::write(childFd
,outBuf
+outBufPos
,outBufLen
-outBufPos
);
1376 if (rc
>= 0) outBufPos
+= rc
;
1380 myDebug( << "write failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
1381 error(ERR_CONNECTION_BROKEN
,connectionHost
);
1382 shutdownConnection();
1385 if (outBufPos
>= outBufLen
) {
1391 if (FD_ISSET(childFd
,&rfds
)) {
1392 rc
= ::read(childFd
,buf
+offset
,32768-offset
);
1393 //myDebug( << "read " << rc << " bytes" << endl);
1395 int noff
= received(buf
,rc
+offset
);
1396 if (noff
> 0) memmove(buf
,buf
+offset
+rc
-noff
,noff
);
1397 //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset) << endl);
1402 myDebug( << "read failed, rc: " << rc
<< ", error: " << strerror(errno
) << endl
);
1403 error(ERR_CONNECTION_BROKEN
,connectionHost
);
1404 shutdownConnection();
1414 void fishProtocol::stat(const KUrl
& u
){
1415 myDebug( << "@@@@@@@@@ stat " << u
<< endl
);
1418 isStat
= true; // FIXME: just a workaround for konq deficiencies
1420 isStat
= false; // FIXME: just a workaround for konq deficiencies
1421 if (!isLoggedIn
) return;
1423 if (!url
.hasPath()) {
1424 sendCommand(FISH_PWD
);
1426 sendCommand(FISH_STAT
,E(url
.path(KUrl::RemoveTrailingSlash
)));
1430 /** find mimetype for a file */
1431 void fishProtocol::mimetype(const KUrl
& u
){
1432 myDebug( << "@@@@@@@@@ mimetype " << u
<< endl
);
1436 if (!isLoggedIn
) return;
1438 if (!url
.hasPath()) {
1439 sendCommand(FISH_PWD
);
1442 sendCommand(FISH_READ
,"0","1024",E(url
.path()));
1446 /** list a directory */
1447 void fishProtocol::listDir(const KUrl
& u
){
1448 myDebug( << "@@@@@@@@@ listDir " << u
<< endl
);
1452 if (!isLoggedIn
) return;
1454 if (!url
.hasPath()) {
1455 sendCommand(FISH_PWD
);
1458 sendCommand(FISH_LIST
,E(url
.path()));
1462 /** create a directory */
1463 void fishProtocol::mkdir(const KUrl
& u
, int permissions
) {
1464 myDebug( << "@@@@@@@@@ mkdir " << u
<< " " << permissions
<< endl
);
1468 if (!isLoggedIn
) return;
1470 if (!url
.hasPath()) {
1471 sendCommand(FISH_PWD
);
1473 sendCommand(FISH_MKD
,E(url
.path()));
1474 if (permissions
> -1) sendCommand(FISH_CHMOD
,E(QString::number(permissions
,8)),E(url
.path()));
1478 /** rename a file */
1479 void fishProtocol::rename(const KUrl
& s
, const KUrl
& d
, KIO::JobFlags flags
) {
1480 myDebug( << "@@@@@@@@@ rename " << s
<< " " << d
<< " " << (flags
& KIO::Overwrite
) << endl
);
1481 if (s
.host() != d
.host() || s
.port() != d
.port() || s
.user() != d
.user()) {
1482 error(ERR_UNSUPPORTED_ACTION
,s
.prettyUrl());
1488 if (!isLoggedIn
) return;
1492 if (!url
.hasPath()) {
1493 sendCommand(FISH_PWD
);
1495 if (!(flags
& KIO::Overwrite
)) {
1497 checkOverwrite
= false;
1498 sendCommand(FISH_LIST
,E(url
.path()));
1500 sendCommand(FISH_RENAME
,E(src
.path()),E(url
.path()));
1504 /** create a symlink */
1505 void fishProtocol::symlink(const QString
& target
, const KUrl
& u
, KIO::JobFlags flags
) {
1506 myDebug( << "@@@@@@@@@ symlink " << target
<< " " << u
<< " " << (flags
& KIO::Overwrite
) << endl
);
1510 if (!isLoggedIn
) return;
1512 if (!url
.hasPath()) {
1513 sendCommand(FISH_PWD
);
1515 if (!(flags
& KIO::Overwrite
)) {
1517 checkOverwrite
= false;
1518 sendCommand(FISH_LIST
,E(url
.path()));
1520 sendCommand(FISH_SYMLINK
,E(target
),E(url
.path()));
1524 /** change file permissions */
1525 void fishProtocol::chmod(const KUrl
& u
, int permissions
){
1526 myDebug( << "@@@@@@@@@ chmod " << u
<< " " << permissions
<< endl
);
1530 if (!isLoggedIn
) return;
1532 if (!url
.hasPath()) {
1533 sendCommand(FISH_PWD
);
1535 if (permissions
> -1) sendCommand(FISH_CHMOD
,E(QString::number(permissions
,8)),E(url
.path()));
1539 /** copies a file */
1540 void fishProtocol::copy(const KUrl
&s
, const KUrl
&d
, int permissions
, KIO::JobFlags flags
) {
1541 myDebug( << "@@@@@@@@@ copy " << s
<< " " << d
<< " " << permissions
<< " " << (flags
& KIO::Overwrite
) << endl
);
1542 if (s
.host() != d
.host() || s
.port() != d
.port() || s
.user() != d
.user()) {
1543 error(ERR_UNSUPPORTED_ACTION
,s
.prettyUrl());
1546 //myDebug( << s << endl << d << endl);
1550 if (!isLoggedIn
) return;
1554 if (!src
.hasPath()) {
1555 sendCommand(FISH_PWD
);
1557 if (!(flags
& KIO::Overwrite
)) {
1559 checkOverwrite
= false;
1560 sendCommand(FISH_LIST
,E(url
.path()));
1562 sendCommand(FISH_COPY
,E(src
.path()),E(url
.path()));
1563 if (permissions
> -1) sendCommand(FISH_CHMOD
,E(QString::number(permissions
,8)),E(url
.path()));
1567 /** removes a file or directory */
1568 void fishProtocol::del(const KUrl
&u
, bool isFile
){
1569 myDebug( << "@@@@@@@@@ del " << u
<< " " << isFile
<< endl
);
1573 if (!isLoggedIn
) return;
1575 if (!url
.hasPath()) {
1576 sendCommand(FISH_PWD
);
1578 sendCommand((isFile
?FISH_DELE
:FISH_RMD
),E(url
.path()));
1582 /** special like background execute */
1583 void fishProtocol::special( const QByteArray
&data
){
1586 QDataStream
stream(data
);
1590 case FISH_EXEC_CMD
: // SSH EXEC
1597 myDebug( << "@@@@@@@@@ exec " << u
<< " " << command
<< endl
);
1601 if (!isLoggedIn
) return;
1602 sendCommand(FISH_EXEC
,E(command
),E(url
.path()));
1607 // Some command we don't understand.
1608 error(ERR_UNSUPPORTED_ACTION
,QString().setNum(tmp
));
1612 /** report status */
1613 void fishProtocol::slave_status() {
1614 myDebug( << "@@@@@@@@@ slave_status" << endl
);
1616 slaveStatus(connectionHost
,isLoggedIn
);
1618 slaveStatus(QString(),false);