Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / runtime / kioslave / fish / fish.cpp
blobb2ab2a3bc0485a3661952f09ef130819d3885fa1
1 /***************************************************************************
2 fish.cpp - a FISH kioslave
3 -------------------
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 /***************************************************************************
10 * *
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 *
14 * *
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"
29 #include <QFile>
30 #include <QDateTime>
31 #include <QBitArray>
32 #include <QRegExp>
34 #include <stdlib.h>
35 #ifdef HAVE_PTY_H
36 #include <pty.h>
37 #endif
39 #ifdef HAVE_TERMIOS_H
40 #include <termios.h>
41 #endif
43 #include <math.h>
44 #include <unistd.h>
45 #include <signal.h>
46 #include <sys/wait.h>
47 #include <sys/socket.h>
48 #include <netinet/in.h>
49 #include <netdb.h>
50 #include <sys/types.h>
52 #ifdef HAVE_STROPTS
53 #include <stropts.h>
54 #endif
56 #ifdef HAVE_SYS_IOCTL_H
57 #include <sys/ioctl.h>
58 #endif
60 #ifdef HAVE_LIBUTIL_H
61 #include <libutil.h>
62 #endif
64 #ifdef HAVE_UTIL_H
65 #include <util.h>
66 #endif
68 #include <kdebug.h>
69 #include <kmessagebox.h>
70 #include <kcomponentdata.h>
71 #include <kglobal.h>
72 #include <kstandarddirs.h>
73 #include <klocale.h>
74 #include <kremoteencoding.h>
75 #include <kurl.h>
76 #include <stdarg.h>
77 #include <time.h>
78 #include <sys/stat.h>
79 #include <kmimetype.h>
80 #include <fcntl.h>
81 #include <errno.h>
82 #include <sys/resource.h>
83 #include <kdefakes.h>
85 #include "fish.h"
86 #include "fishcode.h"
88 #ifndef NDEBUG
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)
105 #else
106 #define myDebug(x)
107 #define sendmimeType(x) mimeType(x)
108 #endif
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())
117 using namespace KIO;
118 extern "C" {
120 static void ripper(int)
122 while (waitpid(-1,0,WNOHANG) > 0) {
123 // do nothing, go on
127 int KDE_EXPORT kdemain( int argc, char **argv )
129 KComponentData componentData("fish", "kio_fish");
131 myDebug( << "*** Starting fish " << endl);
132 if (argc != 4) {
133 myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl);
134 exit(-1);
137 setenv("TZ", "UTC", true);
139 struct sigaction act;
140 memset(&act,0,sizeof(act));
141 act.sa_handler = ripper;
142 act.sa_flags = 0
143 #ifdef SA_NOCLDSTOP
144 | SA_NOCLDSTOP
145 #endif
146 #ifdef SA_RESTART
147 | SA_RESTART
148 #endif
150 sigaction(SIGCHLD,&act,NULL);
152 fishProtocol slave(argv[2], argv[3]);
153 slave.dispatchLoop();
155 myDebug( << "*** fish Done" << endl);
156 return 0;
161 const struct fishProtocol::fish_info fishProtocol::fishInfo[] = {
162 { ("FISH"), 0,
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;"),
164 1 },
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'"),
167 1 },
168 { ("PWD"), 0,
169 ("pwd"),
170 1 },
171 { ("LIST"), 1,
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; )"),
174 0 },
175 { ("STAT"), 1,
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; )"),
178 0 },
179 { ("RETR"), 1,
180 ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"),
181 1 },
182 { ("STOR"), 2,
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 )"),
185 0 },
186 { ("CWD"), 1,
187 ("cd %1"),
188 0 },
189 { ("CHMOD"), 2,
190 ("chmod %1 %2"),
191 0 },
192 { ("DELE"), 1,
193 ("rm -f %1"),
194 0 },
195 { ("MKD"), 1,
196 ("mkdir %1"),
197 0 },
198 { ("RMD"), 1,
199 ("rmdir %1"),
200 0 },
201 { ("RENAME"), 2,
202 ("mv -f %1 %2"),
203 0 },
204 { ("LINK"), 2,
205 ("ln -f %1 %2"),
206 0 },
207 { ("SYMLINK"), 2,
208 ("ln -sf %1 %2"),
209 0 },
210 { ("CHOWN"), 2,
211 ("chown %1 %2"),
212 0 },
213 { ("CHGRP"), 2,
214 ("chgrp %1 %2"),
215 0 },
216 { ("READ"), 3,
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;"),
220 0 },
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,
226 // we use APPEND.
227 { ("WRITE"), 3,
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; )"),
230 0 },
231 { ("COPY"), 2,
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"),
233 0 },
234 { ("APPEND"), 2,
235 (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"),
236 0 },
237 { ("EXEC"), 2,
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'),
244 mimeTypeSent(false)
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")));
255 childPid = 0;
256 connectionPort = 0;
257 isLoggedIn = false;
258 writeReady = true;
259 isRunning = false;
260 firstLogin = true;
261 errorCount = 0;
262 rawRead = 0;
263 rawWrite = -1;
264 recvLen = -1;
265 sendLen = -1;
266 connectionAuth.keepPassword = true;
267 connectionAuth.url.setProtocol("fish");
268 outBufPos = -1;
269 outBuf = NULL;
270 outBufLen = 0;
272 udsType = 0;
274 hasAppend = false;
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() );
301 return;
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();
312 return;
314 myDebug( << "subprocess is running" << endl);
317 // XXX Use KPty! XXX
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 */
323 int master, slave;
324 char *name;
325 struct ::termios ti;
326 memset(&ti,0,sizeof(ti));
328 ti.c_cflag = CLOCAL|CREAD|CS8;
329 ti.c_cc[VMIN] = 1;
331 #ifdef HAVE_GETPT
332 master = getpt();
333 #else
334 master = open("/dev/ptmx", O_RDWR);
335 #endif
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))
350 goto close_slave;
351 #endif
353 tcsetattr(slave, TCSANOW, &ti);
354 fd[0] = master;
355 fd[1] = slave;
356 return 0;
358 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
359 close_slave:
360 #endif
361 close(slave);
363 close_master:
364 close(master);
365 return -1;
366 #else
367 #ifdef HAVE_OPENPTY
368 struct ::termios ti;
369 memset(&ti,0,sizeof(ti));
371 ti.c_cflag = CLOCAL|CREAD|CS8;
372 ti.c_cc[VMIN] = 1;
374 return openpty(fd,fd+1,NULL,&ti,NULL);
375 #else
376 #ifdef __GNUC__
377 #warning "No tty support available. Password dialog won't work."
378 #endif
379 return socketpair(PF_UNIX,SOCK_STREAM,0,fd);
380 #endif
381 #endif
384 creates the subprocess
386 bool fishProtocol::connectionStart() {
387 int fd[2];
388 int rc, flags;
389 thisFn.clear();
391 rc = open_pty_pair(fd);
392 if (rc == -1) {
393 myDebug( << "socketpair failed, error: " << strerror(errno) << endl);
394 return true;
397 if (!requestNetwork()) return true;
398 myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl);
399 childPid = fork();
400 if (childPid == -1) {
401 myDebug( << "fork failed, error: " << strerror(errno) << endl);
402 ::close(fd[0]);
403 ::close(fd[1]);
404 childPid = 0;
405 dropNetwork();
406 return true;
408 if (childPid == 0) {
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);
415 struct rlimit rlp;
416 getrlimit(RLIMIT_NOFILE, &rlp);
417 for (int i = 0; i < (int)rlp.rlim_cur; i++)
418 if (i != fd[1]) ::close(i);
420 dup2(fd[1],0);
421 dup2(fd[1],1);
422 dup2(fd[1],2);
423 if (fd[1] > 2) ::close(fd[1]);
425 setsid();
427 #if defined(TIOCSCTTY)
428 ioctl(0, TIOCSCTTY, 0);
429 #endif
431 int pgrp = getpid();
432 #if defined( _AIX) || defined( __hpux)
433 tcsetpgrp(0, pgrp);
434 #else
435 ioctl(0, TIOCSPGRP, (char *)&pgrp);
436 #endif
438 const char *dev = ttyname(0);
439 setpgid(0,0);
440 if (dev) ::close(::open(dev, O_WRONLY, 0));
441 setpgid(0,0);
443 if (local) {
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);
445 } else {
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"),
452 if (connectionPort)
453 execl(sshPath, "ssh", "-p", qPrintable(QString::number(connectionPort)), common_args);
454 else
455 execl(sshPath, "ssh", common_args);
456 #undef common_args
458 myDebug( << "could not exec! " << strerror(errno) << endl);
459 ::exit(-1);
461 ::close(fd[1]);
462 rc = fcntl(fd[0],F_GETFL,&flags);
463 rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK);
464 childFd = fd[0];
466 fd_set rfds, wfds;
467 FD_ZERO(&rfds);
468 FD_ZERO(&wfds);
469 char buf[32768];
470 int offset = 0;
471 while (!isLoggedIn) {
472 FD_SET(childFd,&rfds);
473 FD_ZERO(&wfds);
474 if (outBufPos >= 0) FD_SET(childFd,&wfds);
475 struct timeval timeout;
476 timeout.tv_sec = 0;
477 timeout.tv_usec = 1000;
478 rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
479 if (rc < 0) {
480 if (errno == EINTR)
481 continue;
482 myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
483 return true;
485 if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
486 if (outBuf) rc = ::write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
487 else rc = 0;
488 if (rc >= 0) outBufPos += rc;
489 else {
490 if (errno == EINTR)
491 continue;
492 myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
493 outBufPos = -1;
494 //return true;
496 if (outBufPos >= outBufLen) {
497 outBufPos = -1;
498 outBuf = NULL;
499 outBufLen = 0;
502 if (FD_ISSET(childFd,&rfds)) {
503 rc = ::read(childFd,buf+offset,32768-offset);
504 if (rc > 0) {
505 int noff = establishConnection(buf,rc+offset);
506 if (noff < 0) return false;
507 if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
508 offset = noff;
509 } else {
510 if (errno == EINTR)
511 continue;
512 myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
513 return true;
517 return false;
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) {
525 #if 0
526 QString debug;
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);
529 #endif
530 return;
532 outBuf = buf;
533 outBufPos = 0;
534 outBufLen = len;
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);
542 int pos=0;
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('?'))) {
550 pos++;
551 QString str = buf.left(pos);
552 buf = buf.mid(pos);
553 if (str == "\n")
554 continue;
555 if (str == "FISH:\n") {
556 thisFn.clear();
557 infoMessage(i18n("Initiating protocol..."));
558 if (!connectionAuth.password.isEmpty()) {
559 connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1);
560 cacheAuthentication(connectionAuth);
562 isLoggedIn = true;
563 return 0;
564 } else if (!str.isEmpty()) {
565 thisFn += str;
566 } else if (buf.endsWith(':')) {
567 if (!redirectUser.isEmpty() && connectionUser != redirectUser) {
568 KUrl dest = url;
569 dest.setUser(redirectUser);
570 dest.setPass(redirectPass);
571 redirection(dest);
572 commandList.clear();
573 commandCodes.clear();
574 finished();
575 redirectUser = "";
576 redirectPass = "";
577 return -1;
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.
584 if (local)
585 sleep(1);
586 writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length());
587 } else {
588 myDebug( << "sending mpass" << endl);
589 connectionAuth.prompt = thisFn+buf;
590 if (local)
591 connectionAuth.caption = i18n("Local Login");
592 else
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();
599 return -1;
602 firstLogin = false;
603 connectionAuth.password += '\n';
604 if (connectionAuth.username != connectionUser) {
605 KUrl dest = url;
606 dest.setUser(connectionAuth.username);
607 dest.setPass(connectionAuth.password);
608 redirection(dest);
609 if (isStat) { // FIXME: just a workaround for konq deficiencies
610 redirectUser = connectionAuth.username;
611 redirectPass = connectionAuth.password;
613 commandList.clear();
614 commandCodes.clear();
615 finished();
616 return -1;
618 myDebug( << "sending pass" << endl);
619 if (local)
620 sleep(1);
621 writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length());
623 thisFn.clear();
624 return 0;
625 } else if (buf.endsWith('?')) {
626 int rc = messageBox(QuestionYesNo,thisFn+buf);
627 if (rc == KMessageBox::Yes) {
628 writeChild("yes\n",4);
629 } else {
630 writeChild("no\n",3);
632 thisFn.clear();
633 return 0;
634 } else {
635 myDebug( << "unmatched case in initial handling! should not happen!" << endl);
638 return buf.length();
641 void fishProtocol::setHostInternal(const KUrl & u){
642 int port = u.port();
643 if(port <= 0 ) // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that.
644 port = 0;
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){
652 QString user(u);
654 local = (host == "localhost" && port == 0);
655 if (user.isEmpty()) user = getenv("LOGNAME");
657 if (host == connectionHost && port == connectionPort && user == connectionUser)
658 return;
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;
672 firstLogin = true;
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){
690 if (childPid) {
691 kill(childPid,SIGTERM); // We may not have permission...
692 childPid = 0;
693 ::close(childFd); // ...in which case this should do the trick
694 childFd = -1;
695 if (!forced)
697 dropNetwork();
698 infoMessage(i18n("Disconnected."));
701 outBufPos = -1;
702 outBuf = NULL;
703 outBufLen = 0;
704 qlist.clear();
705 commandList.clear();
706 commandCodes.clear();
707 isLoggedIn = false;
708 writeReady = true;
709 isRunning = false;
710 rawRead = 0;
711 rawWrite = -1;
712 recvLen = -1;
713 sendLen = -1;
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);
722 va_list list;
723 va_start(list, cmd);
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 *));
729 int pos = -2;
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);
737 QString s("#");
738 s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n");
739 if (realCmd == "FISH")
740 s.prepend(" ");
741 commandList.append(s);
742 commandCodes.append(cmd);
743 return true;
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("### ")) {
752 bool isOk = false;
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);
758 return result;
759 } else {
760 errorCount++;
761 return 0;
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])) {
779 month = i+1;
780 break;
783 int pos = timeyearStr.indexOf(':');
784 if (timeyearStr.length() == 4 && pos == -1) {
785 year = timeyearStr.toInt();
786 } else if (pos == -1) {
787 return 0;
788 } else {
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) {
801 QString line(l);
802 int rc = handleResponse(line);
803 QDateTime dt;
804 long pos, pos2, pos3;
805 bool isOk = false;
806 if (!rc) {
807 switch (fishCommand) {
808 case FISH_VER:
809 if (line.startsWith("VER 0.0.3")) {
810 line.append(" ");
811 hasAppend = line.contains(" append ");
812 } else {
813 error(ERR_UNSUPPORTED_PROTOCOL,line);
814 shutdownConnection();
816 break;
817 case FISH_PWD:
818 url.setPath(line);
819 redirection(url);
820 break;
821 case FISH_LIST:
822 myDebug( << "listReason: " << static_cast<int>(listReason) << endl);
823 /* Fall through */
824 case FISH_STAT:
825 if (line.length() > 0) {
826 switch (line[0].cell()) {
827 case '0':
828 case '1':
829 case '2':
830 case '3':
831 case '4':
832 case '5':
833 case '6':
834 case '7':
835 case '8':
836 case '9':
838 long long val = line.toLongLong(&isOk);
839 if (val > 0 && isOk) errorCount--;
840 if ((fishCommand == FISH_LIST) && (listReason == LIST))
841 totalSize(val);
843 break;
845 case 'P':
847 errorCount--;
848 if (line[1] == 'd') {
849 udsMime = "inode/directory";
850 udsType = S_IFDIR;
851 } else {
852 if (line[1] == '-') {
853 udsType = S_IFREG;
854 } else if (line[1] == 'l') {
855 udsType = S_IFLNK;
856 } else if (line[1] == 'c') {
857 udsType = S_IFCHR;
858 } else if (line[1] == 'b') {
859 udsType = S_IFBLK;
860 } else if (line[1] == 's') {
861 udsType = S_IFSOCK;
862 } else if (line[1] == 'p') {
863 udsType = S_IFIFO;
864 } else {
865 myDebug( << "unknown file type: " << line[1].cell() << endl);
866 errorCount++;
867 break;
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);
888 if (pos < 0) {
889 errorCount++;
890 break;
892 udsEntry.insert(KIO::UDSEntry::UDS_USER, line.mid(12,pos-12));
893 udsEntry.insert(KIO::UDSEntry::UDS_GROUP, line.mid(pos+1));
895 break;
897 case 'd':
898 pos = line.indexOf(' ');
899 pos2 = line.indexOf(' ',pos+1);
900 if (pos < 0 || pos2 < 0) break;
901 errorCount--;
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)));
904 break;
906 case 'D':
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()));
912 pos = pos3;
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()));
917 errorCount--;
918 udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toTime_t());
919 break;
921 case 'S':
923 long long sizeVal = line.mid(1).toLongLong(&isOk);
924 if (!isOk) break;
925 errorCount--;
926 udsEntry.insert(KIO::UDSEntry::UDS_SIZE, sizeVal);
928 break;
930 case 'E':
931 errorCount--;
932 break;
934 case ':':
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();
949 errorCount--;
950 break;
952 case 'M':
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
960 udsType = S_IFDIR;
962 errorCount--;
963 break;
965 case 'L':
966 udsEntry.insert(KIO::UDSEntry::UDS_LINK_DEST, line.mid(1));
967 if (!udsType) udsType = S_IFLNK;
968 errorCount--;
969 break;
971 } else {
972 if (!udsMime.isNull())
973 udsEntry.insert(KIO::UDSEntry::UDS_MIME_TYPE, udsMime);
974 udsMime.clear();
976 udsEntry.insert( KIO::UDSEntry::UDS_FILE_TYPE, udsType );
977 udsType = 0;
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
984 errorCount--;
985 udsEntry.clear();
987 break;
989 case FISH_RETR:
990 if (line.length() == 0) {
991 error(ERR_IS_DIRECTORY,url.prettyUrl());
992 recvLen = 0;
993 break;
995 recvLen = line.toLongLong(&isOk);
996 if (!isOk) {
997 error(ERR_COULD_NOT_READ,url.prettyUrl());
998 shutdownConnection();
999 break;
1001 break;
1002 default : break;
1005 } else if (rc == 100) {
1006 switch (fishCommand) {
1007 case FISH_FISH:
1008 writeChild(fishCode, fishCodeLen);
1009 break;
1010 case FISH_READ:
1011 recvLen = 1024;
1012 /* fall through */
1013 case FISH_RETR:
1014 myDebug( << "reading " << recvLen << endl);
1015 if (recvLen == -1) {
1016 error(ERR_COULD_NOT_READ,url.prettyUrl());
1017 shutdownConnection();
1018 } else {
1019 rawRead = recvLen;
1020 dataRead = 0;
1021 mimeTypeSent = false;
1022 if (recvLen == 0)
1024 mimeType("application/x-zerosize");
1025 mimeTypeSent = true;
1028 break;
1029 case FISH_STOR:
1030 case FISH_WRITE:
1031 case FISH_APPEND:
1032 rawWrite = sendLen;
1033 //myDebug( << "sending " << sendLen << endl);
1034 writeChild(NULL,0);
1035 break;
1036 default : break;
1038 } else if (rc/100 != 2) {
1039 switch (fishCommand) {
1040 case FISH_STOR:
1041 case FISH_WRITE:
1042 case FISH_APPEND:
1043 error(ERR_COULD_NOT_WRITE,url.prettyUrl());
1044 shutdownConnection();
1045 break;
1046 case FISH_RETR:
1047 error(ERR_COULD_NOT_READ,url.prettyUrl());
1048 shutdownConnection();
1049 break;
1050 case FISH_READ:
1051 if ( rc == 501 )
1053 mimeType("inode/directory");
1054 mimeTypeSent = true;
1055 recvLen = 0;
1056 finished();
1058 else
1060 error(ERR_COULD_NOT_READ,url.prettyUrl());
1061 shutdownConnection();
1063 break;
1064 case FISH_FISH:
1065 case FISH_VER:
1066 error(ERR_SLAVE_DEFINED,line);
1067 shutdownConnection();
1068 break;
1069 case FISH_PWD:
1070 case FISH_CWD:
1071 error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyUrl());
1072 break;
1073 case FISH_LIST:
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) {
1077 checkExist = false;
1078 finished();
1080 break;
1081 case FISH_STAT:
1082 error(ERR_DOES_NOT_EXIST,url.prettyUrl());
1083 udsStatEntry.clear();
1084 break;
1085 case FISH_CHMOD:
1086 error(ERR_CANNOT_CHMOD,url.prettyUrl());
1087 break;
1088 case FISH_CHOWN:
1089 case FISH_CHGRP:
1090 error(ERR_ACCESS_DENIED,url.prettyUrl());
1091 break;
1092 case FISH_MKD:
1093 if ( rc == 501 )
1094 error(ERR_DIR_ALREADY_EXIST,url.prettyUrl());
1095 else
1096 error(ERR_COULD_NOT_MKDIR,url.prettyUrl());
1097 break;
1098 case FISH_RMD:
1099 error(ERR_COULD_NOT_RMDIR,url.prettyUrl());
1100 break;
1101 case FISH_DELE:
1102 error(ERR_CANNOT_DELETE,url.prettyUrl());
1103 break;
1104 case FISH_RENAME:
1105 error(ERR_CANNOT_RENAME,url.prettyUrl());
1106 break;
1107 case FISH_COPY:
1108 case FISH_LINK:
1109 case FISH_SYMLINK:
1110 error(ERR_COULD_NOT_WRITE,url.prettyUrl());
1111 break;
1112 default : break;
1114 } else {
1115 if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE);
1116 if (fishCommand == FISH_FISH) {
1117 connected();
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) {
1132 dataReq();
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) {
1137 dataReq();
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) {
1143 data(QByteArray());
1145 finished();
1149 void fishProtocol::writeStdin(const QString &line)
1151 qlist.append(line);
1153 if (writeReady) {
1154 writeReady = false;
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()
1164 if (rawWrite > 0) {
1165 myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl);
1166 writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite));
1167 rawWrite -= rawData.size();
1168 if (rawWrite > 0) {
1169 dataReq();
1170 if (readData(rawData) <= 0) {
1171 shutdownConnection();
1174 return;
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);
1180 rawWrite = -1;
1181 return;
1183 if (qlist.count() > 0) qlist.erase(qlist.begin());
1184 if (qlist.count() == 0) {
1185 writeReady = true;
1186 } else {
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)
1196 int pos = 0;
1197 do {
1198 if (buflen <= 0) break;
1200 if (rawRead > 0) {
1201 myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl);
1202 int dataSize = (rawRead > buflen?buflen:rawRead);
1203 if (!mimeTypeSent)
1205 int mimeSize = qMin(dataSize, (int)(mimeBuffer.size()-dataRead));
1206 memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize);
1207 dataRead += mimeSize;
1208 rawRead -= mimeSize;
1209 buffer += mimeSize;
1210 buflen -= mimeSize;
1211 if (rawRead == 0) // End of data
1212 mimeBuffer.resize(dataRead);
1213 if (dataRead < (int)mimeBuffer.size())
1215 myDebug( << "wait for more" << endl);
1216 break;
1218 sendmimeType(KMimeType::findByNameAndContent(url.path(), mimeBuffer)->name());
1219 mimeTypeSent = true;
1220 if (fishCommand != FISH_READ) {
1221 totalSize(dataRead + rawRead);
1222 data(mimeBuffer);
1223 processedSize(dataRead);
1225 mimeBuffer.resize(1024);
1226 pos = 0;
1227 continue; // Process rest of buffer/buflen
1230 QByteArray bdata(buffer,dataSize);
1231 data(bdata);
1233 dataRead += dataSize;
1234 rawRead -= dataSize;
1235 processedSize(dataRead);
1236 if (rawRead <= 0) {
1237 buffer += dataSize;
1238 buflen -= dataSize;
1239 } else {
1240 return 0;
1244 if (buflen <= 0) break;
1246 pos = 0;
1247 // Find newline
1248 while((pos < buflen) && (buffer[pos] != '\n'))
1249 ++pos;
1251 if (pos < buflen)
1253 QString s = remoteEncoding()->decode(QByteArray(buffer,pos));
1255 buffer += pos+1;
1256 buflen -= pos+1;
1258 manageConnection(s);
1260 pos = 0;
1261 // Find next newline
1262 while((pos < buflen) && (buffer[pos] != '\n'))
1263 ++pos;
1265 } while (childPid && buflen && (rawRead > 0 || pos < buflen));
1266 return buflen;
1268 /** get a file */
1269 void fishProtocol::get(const KUrl& u){
1270 myDebug( << "@@@@@@@@@ get " << u << endl);
1271 setHostInternal(u);
1272 url = u;
1273 openConnection();
1274 if (!isLoggedIn) return;
1275 url.cleanPath();
1276 if (!url.hasPath()) {
1277 sendCommand(FISH_PWD);
1278 } else {
1279 recvLen = -1;
1280 sendCommand(FISH_RETR,E(url.path()));
1282 run();
1285 /** put a file */
1286 void fishProtocol::put(const KUrl& u, int permissions, KIO::JobFlags flags) {
1287 myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << (flags & KIO::Overwrite) << " " /* << resume */ << endl);
1288 setHostInternal(u);
1290 url = u;
1291 openConnection();
1292 if (!isLoggedIn) return;
1293 url.cleanPath();
1294 if (!url.hasPath()) {
1295 sendCommand(FISH_PWD);
1296 } else {
1297 putPerm = permissions;
1299 checkOverwrite = flags & KIO::Overwrite;
1300 checkExist = false;
1301 putPos = 0;
1302 listReason = CHECK;
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.
1313 run();
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;
1320 rawRead = 0;
1321 rawWrite = -1;
1322 udsEntry.clear();
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());
1328 } else {
1329 myDebug( << "_______ emitting finished()" << endl);
1330 SlaveBase::finished();
1331 isRunning = false;
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);
1340 isRunning = false;
1342 /** executes a chain of commands */
1343 void fishProtocol::run() {
1344 if (!isRunning) {
1345 int rc;
1346 isRunning = true;
1347 finished();
1348 fd_set rfds, wfds;
1349 FD_ZERO(&rfds);
1350 char buf[32768];
1351 int offset = 0;
1352 while (isRunning) {
1353 FD_SET(childFd,&rfds);
1354 FD_ZERO(&wfds);
1355 if (outBufPos >= 0) FD_SET(childFd,&wfds);
1356 struct timeval timeout;
1357 timeout.tv_sec = 0;
1358 timeout.tv_usec = 1000;
1359 rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
1360 if (rc < 0) {
1361 if (errno == EINTR)
1362 continue;
1363 myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1364 error(ERR_CONNECTION_BROKEN,connectionHost);
1365 shutdownConnection();
1366 return;
1368 if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
1369 #if 0
1370 QString debug;
1371 debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos);
1372 myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl);
1373 #endif
1374 if (outBufLen-outBufPos > 0) rc = ::write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
1375 else rc = 0;
1376 if (rc >= 0) outBufPos += rc;
1377 else {
1378 if (errno == EINTR)
1379 continue;
1380 myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1381 error(ERR_CONNECTION_BROKEN,connectionHost);
1382 shutdownConnection();
1383 return;
1385 if (outBufPos >= outBufLen) {
1386 outBufPos = -1;
1387 outBuf = NULL;
1388 sent();
1391 if (FD_ISSET(childFd,&rfds)) {
1392 rc = ::read(childFd,buf+offset,32768-offset);
1393 //myDebug( << "read " << rc << " bytes" << endl);
1394 if (rc > 0) {
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);
1398 offset = noff;
1399 } else {
1400 if (errno == EINTR)
1401 continue;
1402 myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1403 error(ERR_CONNECTION_BROKEN,connectionHost);
1404 shutdownConnection();
1405 return;
1408 if (wasKilled())
1409 return;
1413 /** stat a file */
1414 void fishProtocol::stat(const KUrl& u){
1415 myDebug( << "@@@@@@@@@ stat " << u << endl);
1416 setHostInternal(u);
1417 url = u;
1418 isStat = true; // FIXME: just a workaround for konq deficiencies
1419 openConnection();
1420 isStat = false; // FIXME: just a workaround for konq deficiencies
1421 if (!isLoggedIn) return;
1422 url.cleanPath();
1423 if (!url.hasPath()) {
1424 sendCommand(FISH_PWD);
1425 } else {
1426 sendCommand(FISH_STAT,E(url.path(KUrl::RemoveTrailingSlash)));
1428 run();
1430 /** find mimetype for a file */
1431 void fishProtocol::mimetype(const KUrl& u){
1432 myDebug( << "@@@@@@@@@ mimetype " << u << endl);
1433 setHostInternal(u);
1434 url = u;
1435 openConnection();
1436 if (!isLoggedIn) return;
1437 url.cleanPath();
1438 if (!url.hasPath()) {
1439 sendCommand(FISH_PWD);
1440 } else {
1441 recvLen = 1024;
1442 sendCommand(FISH_READ,"0","1024",E(url.path()));
1444 run();
1446 /** list a directory */
1447 void fishProtocol::listDir(const KUrl& u){
1448 myDebug( << "@@@@@@@@@ listDir " << u << endl);
1449 setHostInternal(u);
1450 url = u;
1451 openConnection();
1452 if (!isLoggedIn) return;
1453 url.cleanPath();
1454 if (!url.hasPath()) {
1455 sendCommand(FISH_PWD);
1456 } else {
1457 listReason = LIST;
1458 sendCommand(FISH_LIST,E(url.path()));
1460 run();
1462 /** create a directory */
1463 void fishProtocol::mkdir(const KUrl& u, int permissions) {
1464 myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl);
1465 setHostInternal(u);
1466 url = u;
1467 openConnection();
1468 if (!isLoggedIn) return;
1469 url.cleanPath();
1470 if (!url.hasPath()) {
1471 sendCommand(FISH_PWD);
1472 } else {
1473 sendCommand(FISH_MKD,E(url.path()));
1474 if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
1476 run();
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());
1483 return;
1485 setHostInternal(s);
1486 url = d;
1487 openConnection();
1488 if (!isLoggedIn) return;
1489 KUrl src = s;
1490 url.cleanPath();
1491 src.cleanPath();
1492 if (!url.hasPath()) {
1493 sendCommand(FISH_PWD);
1494 } else {
1495 if (!(flags & KIO::Overwrite)) {
1496 listReason = CHECK;
1497 checkOverwrite = false;
1498 sendCommand(FISH_LIST,E(url.path()));
1500 sendCommand(FISH_RENAME,E(src.path()),E(url.path()));
1502 run();
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);
1507 setHostInternal(u);
1508 url = u;
1509 openConnection();
1510 if (!isLoggedIn) return;
1511 url.cleanPath();
1512 if (!url.hasPath()) {
1513 sendCommand(FISH_PWD);
1514 } else {
1515 if (!(flags & KIO::Overwrite)) {
1516 listReason = CHECK;
1517 checkOverwrite = false;
1518 sendCommand(FISH_LIST,E(url.path()));
1520 sendCommand(FISH_SYMLINK,E(target),E(url.path()));
1522 run();
1524 /** change file permissions */
1525 void fishProtocol::chmod(const KUrl& u, int permissions){
1526 myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl);
1527 setHostInternal(u);
1528 url = u;
1529 openConnection();
1530 if (!isLoggedIn) return;
1531 url.cleanPath();
1532 if (!url.hasPath()) {
1533 sendCommand(FISH_PWD);
1534 } else {
1535 if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
1537 run();
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());
1544 return;
1546 //myDebug( << s << endl << d << endl);
1547 setHostInternal(s);
1548 url = d;
1549 openConnection();
1550 if (!isLoggedIn) return;
1551 KUrl src = s;
1552 url.cleanPath();
1553 src.cleanPath();
1554 if (!src.hasPath()) {
1555 sendCommand(FISH_PWD);
1556 } else {
1557 if (!(flags & KIO::Overwrite)) {
1558 listReason = CHECK;
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()));
1565 run();
1567 /** removes a file or directory */
1568 void fishProtocol::del(const KUrl &u, bool isFile){
1569 myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl);
1570 setHostInternal(u);
1571 url = u;
1572 openConnection();
1573 if (!isLoggedIn) return;
1574 url.cleanPath();
1575 if (!url.hasPath()) {
1576 sendCommand(FISH_PWD);
1577 } else {
1578 sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path()));
1580 run();
1582 /** special like background execute */
1583 void fishProtocol::special( const QByteArray &data ){
1584 int tmp;
1586 QDataStream stream(data);
1588 stream >> tmp;
1589 switch (tmp) {
1590 case FISH_EXEC_CMD: // SSH EXEC
1592 KUrl u;
1593 QString command;
1594 QString tempfile;
1595 stream >> u;
1596 stream >> command;
1597 myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl);
1598 setHostInternal(u);
1599 url = u;
1600 openConnection();
1601 if (!isLoggedIn) return;
1602 sendCommand(FISH_EXEC,E(command),E(url.path()));
1603 run();
1604 break;
1606 default:
1607 // Some command we don't understand.
1608 error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp));
1609 break;
1612 /** report status */
1613 void fishProtocol::slave_status() {
1614 myDebug( << "@@@@@@@@@ slave_status" << endl);
1615 if (childPid > 0)
1616 slaveStatus(connectionHost,isLoggedIn);
1617 else
1618 slaveStatus(QString(),false);