delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / kioslave / fish / fish.cpp
blobf6296e618cd7167f1df6a6797b9520c237e80390
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 "fish.h"
29 #include <config-runtime.h>
30 #include "config-fish.h"
31 #include <QFile>
32 #include <QDateTime>
33 #include <QBitArray>
34 #include <QRegExp>
36 #include <stdlib.h>
37 #ifdef HAVE_PTY_H
38 #include <pty.h>
39 #endif
41 #ifdef HAVE_TERMIOS_H
42 #include <termios.h>
43 #endif
45 #include <math.h>
46 #include <unistd.h>
47 #include <signal.h>
48 #include <sys/wait.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <netdb.h>
52 #include <sys/types.h>
54 #ifdef HAVE_STROPTS
55 #include <stropts.h>
56 #endif
58 #ifdef HAVE_SYS_IOCTL_H
59 #include <sys/ioctl.h>
60 #endif
62 #ifdef HAVE_LIBUTIL_H
63 #include <libutil.h>
64 #endif
66 #ifdef HAVE_UTIL_H
67 #include <util.h>
68 #endif
70 #include <kdebug.h>
71 #include <kmessagebox.h>
72 #include <kcomponentdata.h>
73 #include <kglobal.h>
74 #include <kstandarddirs.h>
75 #include <klocale.h>
76 #include <kremoteencoding.h>
77 #include <kurl.h>
78 #include <stdarg.h>
79 #include <time.h>
80 #include <sys/stat.h>
81 #include <kmimetype.h>
82 #include <fcntl.h>
83 #include <errno.h>
84 #include <sys/resource.h>
85 #include <kdefakes.h>
87 #include "fishcode.h"
89 #ifndef NDEBUG
90 #define myDebug(x) kDebug(7127) << __LINE__ << ": " x
91 #define connected() do{myDebug( << "_______ emitting connected()" << endl); connected();}while(0)
92 #define dataReq() do{myDebug( << "_______ emitting dataReq()" << endl); dataReq();}while(0)
93 #define needSubURLData() do{myDebug( << "_______ emitting needSubURLData()" << endl); needSubURLData();}while(0)
94 #define slaveStatus(x,y) do{myDebug( << "_______ emitting slaveStatus(" << x << ", " << y << ")" << endl); slaveStatus(x,y);}while(0)
95 #define statEntry(x) do{myDebug( << "_______ emitting statEntry("<<x.count()<<")" << endl); statEntry(x);}while(0)
96 #define listEntries(x) do{myDebug( << "_______ emitting listEntries(...)" << endl); listEntries(x);}while(0)
97 #define canResume(x) do{myDebug( << "_______ emitting canResume("<<(int)x<<")" << endl); canResume(x);}while(0)
98 #define totalSize(x) do{myDebug( << "_______ emitting totalSize("<<(int)x<<")" << endl); totalSize(x);}while(0)
99 #define processedSize(x) do{myDebug( << "_______ emitting processedSize("<<x<<")" << endl); processedSize(x);}while(0)
100 #define speed(x) do{myDebug( << "_______ emitting speed("<<(int)x<<")" << endl); speed(x);}while(0)
101 #define redirection(x) do{myDebug( << "_______ emitting redirection("<<x<<")" << endl); redirection(x);}while(0)
102 #define errorPage() do{myDebug( << "_______ emitting errorPage()" << endl); errorPage();}while(0)
103 #define sendmimeType(x) do{myDebug( << "_______ emitting mimeType("<<x<<")" << endl); mimeType(x);}while(0)
104 #define warning(x) do{myDebug( << "_______ emitting warning("<<x<<")" << endl); warning(x);}while(0)
105 #define infoMessage(x) do{myDebug( << "_______ emitting infoMessage("<<x<<")" << endl); infoMessage(x);}while(0)
106 #else
107 #define myDebug(x)
108 #define sendmimeType(x) mimeType(x)
109 #endif
111 #ifdef Q_WS_WIN
112 #define ENDLINE "\r\n"
113 #else
114 #define ENDLINE '\n'
115 #endif
117 static char *sshPath = NULL;
118 static char *suPath = NULL;
119 // disabled: currently not needed. Didn't work reliably.
120 // static int isOpenSSH = 0;
122 /** the SSH process used to communicate with the remote end */
123 #ifndef Q_WS_WIN
124 static pid_t childPid;
125 #else
126 static KProcess *childPid = 0;
127 #endif
129 #define E(x) ((const char*)remoteEncoding()->encode(x).data())
131 using namespace KIO;
132 extern "C" {
134 int KDE_EXPORT kdemain( int argc, char **argv )
136 KComponentData componentData("fish", "kio_fish");
138 myDebug( << "*** Starting fish " << endl);
139 if (argc != 4) {
140 myDebug( << "Usage: fish protocol domain-socket1 domain-socket2" << endl);
141 exit(-1);
144 setenv("TZ", "UTC", true);
146 fishProtocol slave(argv[2], argv[3]);
147 slave.dispatchLoop();
149 myDebug( << "*** fish Done" << endl);
150 return 0;
155 const struct fishProtocol::fish_info fishProtocol::fishInfo[] = {
156 { ("FISH"), 0,
157 ("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;"),
158 1 },
159 { ("VER 0.0.3 copy append lscount lslinks lsmime exec stat"), 0,
160 ("echo 'VER 0.0.3 copy append lscount lslinks lsmime exec stat'"),
161 1 },
162 { ("PWD"), 0,
163 ("pwd"),
164 1 },
165 { ("LIST"), 1,
166 ("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; );"
167 "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; )"),
168 0 },
169 { ("STAT"), 1,
170 ("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; );"
171 "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; )"),
172 0 },
173 { ("RETR"), 1,
174 ("ls -l %1 2>&1 | ( read -r a b c d x e; echo $x ) 2>&1; echo '### 001'; cat %1"),
175 1 },
176 { ("STOR"), 2,
177 ("> %2; echo '### 001'; ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` 2>/dev/null;"
178 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 2>/dev/null; ) | ( cat > %2 || echo Error $?; cat > /dev/null )"),
179 0 },
180 { ("CWD"), 1,
181 ("cd %1"),
182 0 },
183 { ("CHMOD"), 2,
184 ("chmod %1 %2"),
185 0 },
186 { ("DELE"), 1,
187 ("rm -f %1"),
188 0 },
189 { ("MKD"), 1,
190 ("mkdir %1"),
191 0 },
192 { ("RMD"), 1,
193 ("rmdir %1"),
194 0 },
195 { ("RENAME"), 2,
196 ("mv -f %1 %2"),
197 0 },
198 { ("LINK"), 2,
199 ("ln -f %1 %2"),
200 0 },
201 { ("SYMLINK"), 2,
202 ("ln -sf %1 %2"),
203 0 },
204 { ("CHOWN"), 2,
205 ("chown %1 %2"),
206 0 },
207 { ("CHGRP"), 2,
208 ("chgrp %1 %2"),
209 0 },
210 { ("READ"), 3,
211 ("echo '### 100';cat %3 /dev/zero | ( [ \"`expr %1 / 4096`\" -gt 0 ] && dd bs=4096 count=`expr %1 / 4096` >/dev/null;"
212 "[ \"`expr %1 % 4096`\" -gt 0 ] && dd bs=`expr %1 % 4096` count=1 >/dev/null;"
213 "dd bs=%2 count=1; ) 2>/dev/null;"),
214 0 },
215 // Yes, this is "ibs=1", since dd "count" is input blocks.
216 // On network connections, read() may not fill the buffer
217 // completely (no more data immediately available), but dd
218 // does ignore that fact by design. Sorry, writes are slow.
219 // OTOH, WRITE is not used by the current ioslave methods,
220 // we use APPEND.
221 { ("WRITE"), 3,
222 (">> %3; echo '### 001'; ( [ %2 -gt 0 ] && dd ibs=1 obs=%2 count=%2 2>/dev/null ) | "
223 "( dd ibs=32768 obs=%1 seek=1 of=%3 2>/dev/null || echo Error $?; cat >/dev/null; )"),
224 0 },
225 { ("COPY"), 2,
226 ("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"),
227 0 },
228 { ("APPEND"), 2,
229 (">> %2; echo '### 001'; ( [ %1 -gt 0 ] && dd ibs=1 obs=%1 count=%1 2> /dev/null; ) | ( cat >> %2 || echo Error $?; cat >/dev/null; )"),
230 0 },
231 { ("EXEC"), 2,
232 ("UMASK=`umask`; umask 077; touch %2; umask $UMASK; eval %1 < /dev/null > %2 2>&1; echo \"###RESULT: $?\" >> %2"),
236 fishProtocol::fishProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
237 : SlaveBase("fish", pool_socket, app_socket), mimeBuffer(1024, '\0'),
238 mimeTypeSent(false)
240 myDebug( << "fishProtocol::fishProtocol()" << endl);
241 if (sshPath == NULL) {
242 // disabled: currently not needed. Didn't work reliably.
243 // isOpenSSH = !system("ssh -V 2>&1 | grep OpenSSH > /dev/null");
244 #ifdef Q_WS_WIN
245 sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("plink")));
246 #else
247 sshPath = strdup(QFile::encodeName(KStandardDirs::findExe("ssh")));
248 #endif
250 if (suPath == NULL) {
251 suPath = strdup(QFile::encodeName(KStandardDirs::findExe("su")));
253 childPid = 0;
254 connectionPort = 0;
255 isLoggedIn = false;
256 writeReady = true;
257 isRunning = false;
258 firstLogin = true;
259 errorCount = 0;
260 rawRead = 0;
261 rawWrite = -1;
262 recvLen = -1;
263 sendLen = -1;
264 connectionAuth.keepPassword = true;
265 connectionAuth.url.setProtocol("fish");
266 outBufPos = -1;
267 outBuf = NULL;
268 outBufLen = 0;
270 udsType = 0;
272 hasAppend = false;
274 isStat = false; // FIXME: just a workaround for konq deficiencies
275 redirectUser = ""; // FIXME: just a workaround for konq deficiencies
276 redirectPass = ""; // FIXME: just a workaround for konq deficiencies
277 fishCodeLen = strlen(fishCode);
279 /* ---------------------------------------------------------------------------------- */
282 fishProtocol::~fishProtocol()
284 myDebug( << "fishProtocol::~fishProtocol()" << endl);
285 shutdownConnection(true);
288 /* --------------------------------------------------------------------------- */
291 Connects to a server and logs us in via SSH. Then starts FISH protocol.
293 void fishProtocol::openConnection() {
294 if (childPid) return;
296 if (connectionHost.isEmpty())
298 error( KIO::ERR_UNKNOWN_HOST, QString() );
299 return;
302 infoMessage(i18n("Connecting..."));
304 myDebug( << "connecting to: " << connectionUser << "@" << connectionHost << ":" << connectionPort << endl);
305 sendCommand(FISH_FISH);
306 sendCommand(FISH_VER);
307 if (connectionStart()) {
308 error(ERR_COULD_NOT_CONNECT,connectionHost);
309 shutdownConnection();
310 return;
312 myDebug( << "subprocess is running" << endl);
315 // XXX Use KPty! XXX
316 #ifndef Q_WS_WIN
317 static int open_pty_pair(int fd[2])
319 #if defined(HAVE_TERMIOS_H) && defined(HAVE_GRANTPT) && !defined(HAVE_OPENPTY)
320 /** with kind regards to The GNU C Library
321 Reference Manual for Version 2.2.x of the GNU C Library */
322 int master, slave;
323 char *name;
324 struct ::termios ti;
325 memset(&ti,0,sizeof(ti));
327 ti.c_cflag = CLOCAL|CREAD|CS8;
328 ti.c_cc[VMIN] = 1;
330 #ifdef HAVE_GETPT
331 master = getpt();
332 #else
333 master = open("/dev/ptmx", O_RDWR);
334 #endif
335 if (master < 0) return 0;
337 if (grantpt(master) < 0 || unlockpt(master) < 0) goto close_master;
339 name = ptsname(master);
340 if (name == NULL) goto close_master;
342 slave = open(name, O_RDWR);
343 if (slave == -1) goto close_master;
345 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
346 if (isastream(slave) &&
347 (ioctl(slave, I_PUSH, "ptem") < 0 ||
348 ioctl(slave, I_PUSH, "ldterm") < 0))
349 goto close_slave;
350 #endif
352 tcsetattr(slave, TCSANOW, &ti);
353 fd[0] = master;
354 fd[1] = slave;
355 return 0;
357 #if (defined(HAVE_ISASTREAM) || defined(isastream)) && defined(I_PUSH)
358 close_slave:
359 #endif
360 close(slave);
362 close_master:
363 close(master);
364 return -1;
365 #else
366 #ifdef HAVE_OPENPTY
367 struct ::termios ti;
368 memset(&ti,0,sizeof(ti));
370 ti.c_cflag = CLOCAL|CREAD|CS8;
371 ti.c_cc[VMIN] = 1;
373 return openpty(fd,fd+1,NULL,&ti,NULL);
374 #else
375 #ifdef __GNUC__
376 #warning "No tty support available. Password dialog won't work."
377 #endif
378 return socketpair(PF_UNIX,SOCK_STREAM,0,fd);
379 #endif
380 #endif
382 #endif
384 creates the subprocess
386 bool fishProtocol::connectionStart() {
387 int fd[2];
388 int rc, flags;
389 thisFn.clear();
391 #ifndef Q_WS_WIN
392 rc = open_pty_pair(fd);
393 if (rc == -1) {
394 myDebug( << "socketpair failed, error: " << strerror(errno) << endl);
395 return true;
397 #endif
399 if (!requestNetwork()) return true;
400 myDebug( << "Exec: " << (local ? suPath : sshPath) << " Port: " << connectionPort << " User: " << connectionUser << endl);
401 #ifdef Q_WS_WIN
402 childPid = new KProcess();
403 childPid->setOutputChannelMode(KProcess::MergedChannels);
404 QStringList common_args;
405 common_args << "-l" << connectionUser.toLatin1().constData() << "-x" << connectionHost.toLatin1().constData();
406 common_args << "echo;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\"";
408 childPid->setProgram(sshPath, common_args);
409 childPid->start();
411 QByteArray buf;
412 int offset = 0;
413 while (!isLoggedIn) {
414 if (outBuf.size()) {
415 rc = childPid->write(outBuf);
416 outBuf.clear();
418 else rc = 0;
420 if(rc < 0) {
421 myDebug( << "write failed, rc: " << rc);
422 outBufPos = -1;
423 //return true;
426 if (childPid->waitForReadyRead(1000)) {
427 QByteArray buf2 = childPid->readAll();
428 buf += buf2;
430 int noff = establishConnection(buf);
431 if (noff < 0) return false;
432 if (noff > 0) buf = buf.mid(/*offset+*/noff);
433 // offset = noff;
436 #else
437 childPid = fork();
438 if (childPid == -1) {
439 myDebug( << "fork failed, error: " << strerror(errno) << endl);
440 ::close(fd[0]);
441 ::close(fd[1]);
442 childPid = 0;
443 dropNetwork();
444 return true;
446 if (childPid == 0) {
447 // taken from konsole, see TEPty.C for details
448 // note: if we're running on socket pairs,
449 // this will fail, but thats what we expect
451 for (int sig = 1; sig < NSIG; sig++) signal(sig,SIG_DFL);
453 struct rlimit rlp;
454 getrlimit(RLIMIT_NOFILE, &rlp);
455 for (int i = 0; i < (int)rlp.rlim_cur; i++)
456 if (i != fd[1]) ::close(i);
458 dup2(fd[1],0);
459 dup2(fd[1],1);
460 dup2(fd[1],2);
461 if (fd[1] > 2) ::close(fd[1]);
463 setsid();
465 #if defined(TIOCSCTTY)
466 ioctl(0, TIOCSCTTY, 0);
467 #endif
469 int pgrp = getpid();
470 #if defined( _AIX) || defined( __hpux)
471 tcsetpgrp(0, pgrp);
472 #else
473 ioctl(0, TIOCSPGRP, (char *)&pgrp);
474 #endif
476 const char *dev = ttyname(0);
477 setpgid(0,0);
478 if (dev) ::close(::open(dev, O_WRONLY, 0));
479 setpgid(0,0);
481 if (local) {
482 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);
483 } else {
484 #define common_args "-l", connectionUser.toLatin1().constData(), "-x", "-e", "none", \
485 "-q", connectionHost.toLatin1().constData(), \
486 "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
487 // disabled: leave compression up to the client.
488 // (isOpenSSH?"-C":"+C"),
490 if (connectionPort)
491 execl(sshPath, "ssh", "-p", qPrintable(QString::number(connectionPort)), common_args);
492 else
493 execl(sshPath, "ssh", common_args);
494 #undef common_args
496 myDebug( << "could not exec! " << strerror(errno) << endl);
497 ::exit(-1);
499 ::close(fd[1]);
500 rc = fcntl(fd[0],F_GETFL,&flags);
501 rc = fcntl(fd[0],F_SETFL,flags|O_NONBLOCK);
502 childFd = fd[0];
504 fd_set rfds, wfds;
505 FD_ZERO(&rfds);
506 FD_ZERO(&wfds);
507 char buf[32768];
508 int offset = 0;
509 while (!isLoggedIn) {
510 FD_SET(childFd,&rfds);
511 FD_ZERO(&wfds);
512 if (outBufPos >= 0) FD_SET(childFd,&wfds);
513 struct timeval timeout;
514 timeout.tv_sec = 0;
515 timeout.tv_usec = 1000;
516 rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
517 if (rc < 0) {
518 if (errno == EINTR)
519 continue;
520 myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
521 return true;
523 if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
524 if (outBuf) rc = ::write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
525 else rc = 0;
526 if (rc >= 0) outBufPos += rc;
527 else {
528 if (errno == EINTR)
529 continue;
530 myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
531 outBufPos = -1;
532 //return true;
534 if (outBufPos >= outBufLen) {
535 outBufPos = -1;
536 outBuf = NULL;
537 outBufLen = 0;
540 if (FD_ISSET(childFd,&rfds)) {
541 rc = ::read(childFd,buf+offset,32768-offset);
542 if (rc > 0) {
543 int noff = establishConnection(buf,rc+offset);
544 if (noff < 0) return false;
545 if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
546 offset = noff;
547 } else {
548 if (errno == EINTR)
549 continue;
550 myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
551 return true;
555 #endif
556 return false;
560 writes one chunk of data to stdin of child process
562 #ifndef Q_WS_WIN
563 void fishProtocol::writeChild(const char *buf, KIO::fileoffset_t len) {
564 if (outBufPos >= 0 && outBuf) {
565 #else
566 void fishProtocol::writeChild(const QByteArray &buf, KIO::fileoffset_t len) {
567 if (outBufPos >= 0 && outBuf.size()) {
568 #endif
569 #if 0
570 QString debug;
571 debug.setLatin1(outBuf,outBufLen);
572 if (len > 0) myDebug( << "write request while old one is pending, throwing away input (" << outBufLen << "," << outBufPos << "," << debug.left(10) << "...)" << endl);
573 #endif
574 return;
576 outBuf = buf;
577 outBufPos = 0;
578 outBufLen = len;
582 manages initial communication setup including password queries
584 #ifndef Q_WS_WIN
585 int fishProtocol::establishConnection(char *buffer, KIO::fileoffset_t len) {
586 QString buf = QString::fromLatin1(buffer,len);
587 #else
588 int fishProtocol::establishConnection(const QByteArray &buffer) {
589 QString buf = buffer;
590 #endif
591 int pos=0;
592 // Strip trailing whitespace
593 while (buf.length() && (buf[buf.length()-1] == ' '))
594 buf.truncate(buf.length()-1);
596 myDebug( << "establishing: got " << buf << endl);
597 while (childPid && ((pos = buf.indexOf('\n')) >= 0 ||
598 buf.endsWith(':') || buf.endsWith('?'))) {
599 pos++;
600 QString str = buf.left(pos);
601 buf = buf.mid(pos);
602 if (str == "\n")
603 continue;
604 if (str == "FISH:\n") {
605 thisFn.clear();
606 infoMessage(i18n("Initiating protocol..."));
607 if (!connectionAuth.password.isEmpty()) {
608 connectionAuth.password = connectionAuth.password.left(connectionAuth.password.length()-1);
609 cacheAuthentication(connectionAuth);
611 isLoggedIn = true;
612 return 0;
613 } else if (!str.isEmpty()) {
614 thisFn += str;
615 } else if (buf.endsWith(':')) {
616 if (!redirectUser.isEmpty() && connectionUser != redirectUser) {
617 KUrl dest = url;
618 dest.setUser(redirectUser);
619 dest.setPass(redirectPass);
620 redirection(dest);
621 commandList.clear();
622 commandCodes.clear();
623 finished();
624 redirectUser = "";
625 redirectPass = "";
626 return -1;
627 } else if (!connectionPassword.isEmpty()) {
628 myDebug( << "sending cpass" << endl);
629 connectionAuth.password = connectionPassword+ENDLINE;
630 connectionPassword.clear();
631 // su does not like receiving a password directly after sending
632 // the password prompt so we wait a while.
633 if (local)
634 sleep(1);
635 writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length());
636 } else {
637 myDebug( << "sending mpass" << endl);
638 connectionAuth.prompt = thisFn+buf;
639 if (local)
640 connectionAuth.caption = i18n("Local Login");
641 else
642 connectionAuth.caption = i18n("SSH Authorization");
643 if ((!firstLogin || !checkCachedAuthentication(connectionAuth))) {
644 connectionAuth.password.clear(); // don't prefill
645 if ( !openPasswordDialog(connectionAuth)) {
646 error(ERR_USER_CANCELED,connectionHost);
647 shutdownConnection();
648 return -1;
651 firstLogin = false;
652 connectionAuth.password += ENDLINE;
653 if (connectionAuth.username != connectionUser) {
654 KUrl dest = url;
655 dest.setUser(connectionAuth.username);
656 dest.setPass(connectionAuth.password);
657 redirection(dest);
658 if (isStat) { // FIXME: just a workaround for konq deficiencies
659 redirectUser = connectionAuth.username;
660 redirectPass = connectionAuth.password;
662 commandList.clear();
663 commandCodes.clear();
664 finished();
665 return -1;
667 myDebug( << "sending pass" << endl);
668 if (local)
669 sleep(1);
670 writeChild(connectionAuth.password.toLatin1(),connectionAuth.password.length());
672 thisFn.clear();
673 #ifdef Q_WS_WIN
674 return buf.length();
676 #else
677 return 0;
678 } else if (buf.endsWith('?')) {
679 int rc = messageBox(QuestionYesNo,thisFn+buf);
680 if (rc == KMessageBox::Yes) {
681 writeChild("yes\n",4);
682 } else {
683 writeChild("no\n",3);
685 thisFn.clear();
686 return 0;
688 #endif
689 else {
690 myDebug( << "unmatched case in initial handling! should not happen!" << endl);
692 #ifdef Q_WS_WIN
693 if (buf.endsWith("(y/n)")) {
694 int rc = messageBox(QuestionYesNo,thisFn+buf);
695 if (rc == KMessageBox::Yes) {
696 writeChild("y\n",2);
697 } else {
698 writeChild("n\n",2);
700 thisFn.clear();
701 return 0;
703 #endif
705 return buf.length();
708 void fishProtocol::setHostInternal(const KUrl & u){
709 int port = u.port();
710 if(port <= 0 ) // no port is -1 in QUrl, but in kde3 we used 0 and the kioslaves assume that.
711 port = 0;
712 setHost(u.host(),port,u.user(),u.pass());
716 sets connection information for subsequent commands
718 void fishProtocol::setHost(const QString & host, quint16 port, const QString & u, const QString & pass){
719 QString user(u);
721 local = (host == "localhost" && port == 0);
722 if (user.isEmpty()) user = getenv("LOGNAME");
724 if (host == connectionHost && port == connectionPort && user == connectionUser)
725 return;
726 myDebug( << "setHost " << u << "@" << host << endl);
728 if (childPid) shutdownConnection();
730 connectionHost = host;
731 connectionAuth.url.setHost(host);
733 connectionUser = user;
734 connectionAuth.username = user;
735 connectionAuth.url.setUser(user);
737 connectionPort = port;
738 connectionPassword = pass;
739 firstLogin = true;
743 Forced close of the connection
745 This function gets called from the application side of the universe,
746 it shouldn't send any response.
748 void fishProtocol::closeConnection(){
749 myDebug( << "closeConnection()" << endl);
750 shutdownConnection(true);
754 Closes the connection
756 void fishProtocol::shutdownConnection(bool forced){
757 if (childPid) {
758 #ifdef Q_WS_WIN
759 childPid->terminate();
760 #else
761 int killStatus = kill(childPid,SIGTERM); // We may not have permission...
762 if (killStatus == 0) waitpid(childPid, 0, 0);
763 #endif
764 childPid = 0;
765 #ifndef Q_WS_WIN
766 ::close(childFd); // ...in which case this should do the trick
767 childFd = -1;
768 #endif
769 if (!forced)
771 dropNetwork();
772 infoMessage(i18n("Disconnected."));
775 outBufPos = -1;
776 outBuf = NULL;
777 outBufLen = 0;
778 qlist.clear();
779 commandList.clear();
780 commandCodes.clear();
781 isLoggedIn = false;
782 writeReady = true;
783 isRunning = false;
784 rawRead = 0;
785 rawWrite = -1;
786 recvLen = -1;
787 sendLen = -1;
790 builds each FISH request and sets the error counter
792 bool fishProtocol::sendCommand(fish_command_type cmd, ...) {
793 const fish_info &info = fishInfo[cmd];
794 myDebug( << "queuing: cmd="<< cmd << "['" << info.command << "'](" << info.params <<"), alt=['" << info.alt << "'], lines=" << info.lines << endl);
796 va_list list;
797 va_start(list, cmd);
798 QString realCmd = info.command;
799 QString realAlt = info.alt;
800 static QRegExp rx("[][\\\\\n $`#!()*?{}~&<>;'\"%^@|\t]");
801 for (int i = 0; i < info.params; i++) {
802 QString arg(va_arg(list, const char *));
803 int pos = -2;
804 while ((pos = rx.indexIn(arg,pos+2)) >= 0) {
805 arg.replace(pos,0,QString("\\"));
807 //myDebug( << "arg " << i << ": " << arg << endl);
808 realCmd.append(" ").append(arg);
809 realAlt.replace(QRegExp('%'+QString::number(i+1)),arg);
811 QString s("#");
812 s.append(realCmd).append("\n ").append(realAlt).append(" 2>&1;echo '### 000'\n");
813 if (realCmd == "FISH")
814 s.prepend(" ");
815 commandList.append(s);
816 commandCodes.append(cmd);
817 return true;
821 checks response string for result code, converting 000 and 001 appropriately
823 int fishProtocol::handleResponse(const QString &str){
824 myDebug( << "handling: " << str << endl);
825 if (str.startsWith("### ")) {
826 bool isOk = false;
827 int result = str.mid(4,3).toInt(&isOk);
828 if (!isOk) result = 500;
829 if (result == 0) result = (errorCount != 0?500:200);
830 if (result == 1) result = (errorCount != 0?500:100);
831 myDebug( << "result: " << result << ", errorCount: " << errorCount << endl);
832 return result;
833 } else {
834 errorCount++;
835 return 0;
839 int fishProtocol::makeTimeFromLs(const QString &monthStr, const QString &dayStr, const QString &timeyearStr)
841 QDateTime dt(QDateTime::currentDateTime().toUTC());
842 int year = dt.date().year();
843 int month = dt.date().month();
844 int currentMonth = month;
845 int day = dayStr.toInt();
847 static const char * const monthNames[12] = {
848 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
849 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
852 for (int i=0; i < 12; i++) if (monthStr.startsWith(monthNames[i])) {
853 month = i+1;
854 break;
857 int pos = timeyearStr.indexOf(':');
858 if (timeyearStr.length() == 4 && pos == -1) {
859 year = timeyearStr.toInt();
860 } else if (pos == -1) {
861 return 0;
862 } else {
863 if (month > currentMonth + 1) year--;
864 dt.time().setHMS(timeyearStr.left(pos).toInt(),timeyearStr.mid(pos+1).toInt(),0);
866 dt.date().setYMD(year,month,day);
868 return dt.toTime_t();
872 parses response from server and acts accordingly
874 void fishProtocol::manageConnection(const QString &l) {
875 QString line(l);
876 int rc = handleResponse(line);
877 QDateTime dt;
878 long pos, pos2, pos3;
879 bool isOk = false;
880 if (!rc) {
881 switch (fishCommand) {
882 case FISH_VER:
883 if (line.startsWith("VER 0.0.3")) {
884 line.append(" ");
885 hasAppend = line.contains(" append ");
886 } else {
887 error(ERR_UNSUPPORTED_PROTOCOL,line);
888 shutdownConnection();
890 break;
891 case FISH_PWD:
892 url.setPath(line);
893 redirection(url);
894 break;
895 case FISH_LIST:
896 myDebug( << "listReason: " << static_cast<int>(listReason) << endl);
897 /* Fall through */
898 case FISH_STAT:
899 if (line.length() > 0) {
900 switch (line[0].cell()) {
901 case '0':
902 case '1':
903 case '2':
904 case '3':
905 case '4':
906 case '5':
907 case '6':
908 case '7':
909 case '8':
910 case '9':
912 long long val = line.toLongLong(&isOk);
913 if (val > 0 && isOk) errorCount--;
914 if ((fishCommand == FISH_LIST) && (listReason == LIST))
915 totalSize(val);
917 break;
919 case 'P':
921 errorCount--;
922 if (line[1] == 'd') {
923 udsMime = "inode/directory";
924 udsType = S_IFDIR;
925 } else {
926 if (line[1] == '-') {
927 udsType = S_IFREG;
928 } else if (line[1] == 'l') {
929 udsType = S_IFLNK;
930 } else if (line[1] == 'c') {
931 udsType = S_IFCHR;
932 } else if (line[1] == 'b') {
933 udsType = S_IFBLK;
934 } else if (line[1] == 's') {
935 udsType = S_IFSOCK;
936 } else if (line[1] == 'p') {
937 udsType = S_IFIFO;
938 } else {
939 myDebug( << "unknown file type: " << line[1].cell() << endl);
940 errorCount++;
941 break;
944 //myDebug( << "file type: " << udsType << endl);
946 long long accessVal = 0;
947 if (line[2] == 'r') accessVal |= S_IRUSR;
948 if (line[3] == 'w') accessVal |= S_IWUSR;
949 if (line[4] == 'x' || line[4] == 's') accessVal |= S_IXUSR;
950 if (line[4] == 'S' || line[4] == 's') accessVal |= S_ISUID;
951 if (line[5] == 'r') accessVal |= S_IRGRP;
952 if (line[6] == 'w') accessVal |= S_IWGRP;
953 if (line[7] == 'x' || line[7] == 's') accessVal |= S_IXGRP;
954 if (line[7] == 'S' || line[7] == 's') accessVal |= S_ISGID;
955 if (line[8] == 'r') accessVal |= S_IROTH;
956 if (line[9] == 'w') accessVal |= S_IWOTH;
957 if (line[10] == 'x' || line[10] == 't') accessVal |= S_IXOTH;
958 if (line[10] == 'T' || line[10] == 't') accessVal |= S_ISVTX;
959 udsEntry.insert(KIO::UDSEntry::UDS_ACCESS, accessVal);
961 pos = line.indexOf('.',12);
962 if (pos < 0) {
963 errorCount++;
964 break;
966 udsEntry.insert(KIO::UDSEntry::UDS_USER, line.mid(12,pos-12));
967 udsEntry.insert(KIO::UDSEntry::UDS_GROUP, line.mid(pos+1));
969 break;
971 case 'd':
972 pos = line.indexOf(' ');
973 pos2 = line.indexOf(' ',pos+1);
974 if (pos < 0 || pos2 < 0) break;
975 errorCount--;
976 udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME,
977 makeTimeFromLs(line.mid(1,pos-1), line.mid(pos+1,pos2-pos), line.mid(pos2+1)));
978 break;
980 case 'D':
981 pos = line.indexOf(' ');
982 pos2 = line.indexOf(' ',pos+1);
983 pos3 = line.indexOf(' ',pos2+1);
984 if (pos < 0 || pos2 < 0 || pos3 < 0) break;
985 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()));
986 pos = pos3;
987 pos2 = line.indexOf(' ',pos+1);
988 pos3 = line.indexOf(' ',pos2+1);
989 if (pos < 0 || pos2 < 0 || pos3 < 0) break;
990 dt.setTime(QTime(line.mid(pos+1,pos2-pos-1).toInt(),line.mid(pos2+1,pos3-pos2-1).toInt(),line.mid(pos3+1).toInt()));
991 errorCount--;
992 udsEntry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, dt.toTime_t());
993 break;
995 case 'S':
997 long long sizeVal = line.mid(1).toLongLong(&isOk);
998 if (!isOk) break;
999 errorCount--;
1000 udsEntry.insert(KIO::UDSEntry::UDS_SIZE, sizeVal);
1002 break;
1004 case 'E':
1005 errorCount--;
1006 break;
1008 case ':':
1009 pos = line.lastIndexOf('/');
1010 thisFn = line.mid(pos < 0?1:pos+1);
1011 if (fishCommand == FISH_LIST) {
1012 udsEntry.insert(KIO::UDSEntry::UDS_NAME, thisFn);
1014 // By default, the mimetype comes from the extension
1015 // We'll use the file(1) result only as fallback [like the rest of KDE does]
1017 KUrl kurl("fish://host/");
1018 kurl.setFileName(thisFn); // properly encode special chars
1019 KMimeType::Ptr mime = KMimeType::findByUrl(kurl, udsType);
1020 if ( mime->name() != KMimeType::defaultMimeType() )
1021 udsMime = mime->name();
1023 errorCount--;
1024 break;
1026 case 'M':
1027 // This is getting ugly. file(1) makes some uneducated
1028 // guesses, so we must try to ignore them (#51274)
1029 if (udsMime.isEmpty() && line.right(8) != "/unknown" &&
1030 (thisFn.indexOf('.') < 0 || (line.left(8) != "Mtext/x-"
1031 && line != "Mtext/plain"))) {
1032 udsMime = line.mid(1);
1033 if ( udsMime == "inode/directory" ) // a symlink to a dir is a dir
1034 udsType = S_IFDIR;
1036 errorCount--;
1037 break;
1039 case 'L':
1040 udsEntry.insert(KIO::UDSEntry::UDS_LINK_DEST, line.mid(1));
1041 if (!udsType) udsType = S_IFLNK;
1042 errorCount--;
1043 break;
1045 } else {
1046 if (!udsMime.isNull())
1047 udsEntry.insert(KIO::UDSEntry::UDS_MIME_TYPE, udsMime);
1048 udsMime.clear();
1050 udsEntry.insert( KIO::UDSEntry::UDS_FILE_TYPE, udsType );
1051 udsType = 0;
1053 if (fishCommand == FISH_STAT)
1054 udsStatEntry = udsEntry;
1055 else if (listReason == LIST) {
1056 listEntry(udsEntry, false); //1
1057 } else if (listReason == CHECK) checkExist = true; //0
1058 errorCount--;
1059 udsEntry.clear();
1061 break;
1063 case FISH_RETR:
1064 if (line.length() == 0) {
1065 error(ERR_IS_DIRECTORY,url.prettyUrl());
1066 recvLen = 0;
1067 break;
1069 recvLen = line.toLongLong(&isOk);
1070 if (!isOk) {
1071 error(ERR_COULD_NOT_READ,url.prettyUrl());
1072 shutdownConnection();
1073 break;
1075 break;
1076 default : break;
1079 } else if (rc == 100) {
1080 switch (fishCommand) {
1081 case FISH_FISH:
1082 writeChild(fishCode, fishCodeLen);
1083 break;
1084 case FISH_READ:
1085 recvLen = 1024;
1086 /* fall through */
1087 case FISH_RETR:
1088 myDebug( << "reading " << recvLen << endl);
1089 if (recvLen == -1) {
1090 error(ERR_COULD_NOT_READ,url.prettyUrl());
1091 shutdownConnection();
1092 } else {
1093 rawRead = recvLen;
1094 dataRead = 0;
1095 mimeTypeSent = false;
1096 if (recvLen == 0)
1098 mimeType("application/x-zerosize");
1099 mimeTypeSent = true;
1102 break;
1103 case FISH_STOR:
1104 case FISH_WRITE:
1105 case FISH_APPEND:
1106 rawWrite = sendLen;
1107 //myDebug( << "sending " << sendLen << endl);
1108 writeChild(NULL,0);
1109 break;
1110 default : break;
1112 } else if (rc/100 != 2) {
1113 switch (fishCommand) {
1114 case FISH_STOR:
1115 case FISH_WRITE:
1116 case FISH_APPEND:
1117 error(ERR_COULD_NOT_WRITE,url.prettyUrl());
1118 shutdownConnection();
1119 break;
1120 case FISH_RETR:
1121 error(ERR_COULD_NOT_READ,url.prettyUrl());
1122 shutdownConnection();
1123 break;
1124 case FISH_READ:
1125 if ( rc == 501 )
1127 mimeType("inode/directory");
1128 mimeTypeSent = true;
1129 recvLen = 0;
1130 finished();
1132 else
1134 error(ERR_COULD_NOT_READ,url.prettyUrl());
1135 shutdownConnection();
1137 break;
1138 case FISH_FISH:
1139 case FISH_VER:
1140 error(ERR_SLAVE_DEFINED,line);
1141 shutdownConnection();
1142 break;
1143 case FISH_PWD:
1144 case FISH_CWD:
1145 error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyUrl());
1146 break;
1147 case FISH_LIST:
1148 myDebug( << "list error. reason: " << static_cast<int>(listReason) << endl);
1149 if (listReason == LIST) error(ERR_CANNOT_ENTER_DIRECTORY,url.prettyUrl());
1150 else if (listReason == CHECK) {
1151 checkExist = false;
1152 finished();
1154 break;
1155 case FISH_STAT:
1156 error(ERR_DOES_NOT_EXIST,url.prettyUrl());
1157 udsStatEntry.clear();
1158 break;
1159 case FISH_CHMOD:
1160 error(ERR_CANNOT_CHMOD,url.prettyUrl());
1161 break;
1162 case FISH_CHOWN:
1163 case FISH_CHGRP:
1164 error(ERR_ACCESS_DENIED,url.prettyUrl());
1165 break;
1166 case FISH_MKD:
1167 if ( rc == 501 )
1168 error(ERR_DIR_ALREADY_EXIST,url.prettyUrl());
1169 else
1170 error(ERR_COULD_NOT_MKDIR,url.prettyUrl());
1171 break;
1172 case FISH_RMD:
1173 error(ERR_COULD_NOT_RMDIR,url.prettyUrl());
1174 break;
1175 case FISH_DELE:
1176 error(ERR_CANNOT_DELETE,url.prettyUrl());
1177 break;
1178 case FISH_RENAME:
1179 error(ERR_CANNOT_RENAME,url.prettyUrl());
1180 break;
1181 case FISH_COPY:
1182 case FISH_LINK:
1183 case FISH_SYMLINK:
1184 error(ERR_COULD_NOT_WRITE,url.prettyUrl());
1185 break;
1186 default : break;
1188 } else {
1189 if (fishCommand == FISH_STOR) fishCommand = (hasAppend?FISH_APPEND:FISH_WRITE);
1190 if (fishCommand == FISH_FISH) {
1191 connected();
1192 } else if (fishCommand == FISH_LIST) {
1193 if (listReason == LIST) {
1194 listEntry(UDSEntry(),true);
1195 } else if (listReason == CHECK) {
1196 if (!checkOverwrite && checkExist)
1198 error(ERR_FILE_ALREADY_EXIST,url.prettyUrl());
1199 return; // Don't call finished!
1202 } else if (fishCommand == FISH_STAT) {
1203 udsStatEntry.insert( KIO::UDSEntry::UDS_NAME, url.fileName() );
1204 statEntry(udsStatEntry);
1205 } else if (fishCommand == FISH_APPEND) {
1206 dataReq();
1207 if (readData(rawData) > 0) sendCommand(FISH_APPEND,E(QString::number(rawData.size())),E(url.path()));
1208 else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path()));
1209 sendLen = rawData.size();
1210 } else if (fishCommand == FISH_WRITE) {
1211 dataReq();
1212 if (readData(rawData) > 0) sendCommand(FISH_WRITE,E(QString::number(putPos)),E(QString::number(rawData.size())),E(url.path()));
1213 else if (!checkExist && putPerm > -1) sendCommand(FISH_CHMOD,E(QString::number(putPerm,8)),E(url.path()));
1214 putPos += rawData.size();
1215 sendLen = rawData.size();
1216 } else if (fishCommand == FISH_RETR) {
1217 data(QByteArray());
1219 finished();
1223 void fishProtocol::writeStdin(const QString &line)
1225 qlist.append(line.toLatin1());
1227 if (writeReady) {
1228 writeReady = false;
1229 //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')) << endl);
1230 myDebug( << "Writing: " << qlist.first() << endl);
1231 myDebug( << "---------" << endl);
1232 writeChild((const char *)qlist.first(), qlist.first().length());
1236 void fishProtocol::sent()
1238 if (rawWrite > 0) {
1239 myDebug( << "writing raw: " << rawData.size() << "/" << rawWrite << endl);
1240 writeChild(rawData.data(),(rawWrite > rawData.size()?rawData.size():rawWrite));
1241 rawWrite -= rawData.size();
1242 if (rawWrite > 0) {
1243 dataReq();
1244 if (readData(rawData) <= 0) {
1245 shutdownConnection();
1248 return;
1249 } else if (rawWrite == 0) {
1250 // workaround: some dd's insist in reading multiples of
1251 // 8 bytes, swallowing up to seven bytes. Sending
1252 // newlines is safe even when a sane dd is used
1253 writeChild("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",15);
1254 rawWrite = -1;
1255 return;
1257 if (qlist.count() > 0) qlist.erase(qlist.begin());
1258 if (qlist.count() == 0) {
1259 writeReady = true;
1260 } else {
1261 //myDebug( << "Writing: " << qlist.first().mid(0,qlist.first().indexOf('\n')) << endl);
1262 myDebug( << "Writing: " << qlist.first() << endl);
1263 myDebug( << "---------" << endl);
1264 writeChild((const char *)qlist.first(),qlist.first().length());
1268 int fishProtocol::received(const char *buffer, KIO::fileoffset_t buflen)
1270 int pos = 0;
1271 do {
1272 if (buflen <= 0) break;
1274 if (rawRead > 0) {
1275 myDebug( << "processedSize " << dataRead << ", len " << buflen << "/" << rawRead << endl);
1276 int dataSize = (rawRead > buflen?buflen:rawRead);
1277 if (!mimeTypeSent)
1279 int mimeSize = qMin(dataSize, (int)(mimeBuffer.size()-dataRead));
1280 memcpy(mimeBuffer.data()+dataRead,buffer,mimeSize);
1281 dataRead += mimeSize;
1282 rawRead -= mimeSize;
1283 buffer += mimeSize;
1284 buflen -= mimeSize;
1285 if (rawRead == 0) // End of data
1286 mimeBuffer.resize(dataRead);
1287 if (dataRead < (int)mimeBuffer.size())
1289 myDebug( << "wait for more" << endl);
1290 break;
1292 sendmimeType(KMimeType::findByNameAndContent(url.path(), mimeBuffer)->name());
1293 mimeTypeSent = true;
1294 if (fishCommand != FISH_READ) {
1295 totalSize(dataRead + rawRead);
1296 data(mimeBuffer);
1297 processedSize(dataRead);
1299 mimeBuffer.resize(1024);
1300 pos = 0;
1301 continue; // Process rest of buffer/buflen
1304 QByteArray bdata(buffer,dataSize);
1305 data(bdata);
1307 dataRead += dataSize;
1308 rawRead -= dataSize;
1309 processedSize(dataRead);
1310 if (rawRead <= 0) {
1311 buffer += dataSize;
1312 buflen -= dataSize;
1313 } else {
1314 return 0;
1318 if (buflen <= 0) break;
1320 pos = 0;
1321 // Find newline
1322 while((pos < buflen) && (buffer[pos] != '\n'))
1323 ++pos;
1325 if (pos < buflen)
1327 QString s = remoteEncoding()->decode(QByteArray(buffer,pos));
1329 buffer += pos+1;
1330 buflen -= pos+1;
1332 manageConnection(s);
1334 pos = 0;
1335 // Find next newline
1336 while((pos < buflen) && (buffer[pos] != '\n'))
1337 ++pos;
1339 } while (childPid && buflen && (rawRead > 0 || pos < buflen));
1340 return buflen;
1342 /** get a file */
1343 void fishProtocol::get(const KUrl& u){
1344 myDebug( << "@@@@@@@@@ get " << u << endl);
1345 setHostInternal(u);
1346 url = u;
1347 openConnection();
1348 if (!isLoggedIn) return;
1349 url.cleanPath();
1350 if (!url.hasPath()) {
1351 sendCommand(FISH_PWD);
1352 } else {
1353 recvLen = -1;
1354 sendCommand(FISH_RETR,E(url.path()));
1356 run();
1359 /** put a file */
1360 void fishProtocol::put(const KUrl& u, int permissions, KIO::JobFlags flags) {
1361 myDebug( << "@@@@@@@@@ put " << u << " " << permissions << " " << (flags & KIO::Overwrite) << " " /* << resume */ << endl);
1362 setHostInternal(u);
1364 url = u;
1365 openConnection();
1366 if (!isLoggedIn) return;
1367 url.cleanPath();
1368 if (!url.hasPath()) {
1369 sendCommand(FISH_PWD);
1370 } else {
1371 putPerm = permissions;
1373 checkOverwrite = flags & KIO::Overwrite;
1374 checkExist = false;
1375 putPos = 0;
1376 listReason = CHECK;
1377 sendCommand(FISH_LIST,E(url.path()));
1378 sendCommand(FISH_STOR,"0",E(url.path()));
1380 const QString mtimeStr = metaData( "modified" );
1381 if ( !mtimeStr.isEmpty() ) {
1382 QDateTime dt = QDateTime::fromString( mtimeStr, Qt::ISODate );
1383 // TODO set modification time on url.path() somehow
1384 // see FileProtocol::put if using utime() to do that.
1387 run();
1389 /** executes next command in sequence or calls finished() if all is done */
1390 void fishProtocol::finished() {
1391 if (commandList.count() > 0) {
1392 fishCommand = (fish_command_type)commandCodes.first();
1393 errorCount = -fishInfo[fishCommand].lines;
1394 rawRead = 0;
1395 rawWrite = -1;
1396 udsEntry.clear();
1397 udsStatEntry.clear();
1398 writeStdin(commandList.first());
1399 //if (fishCommand != FISH_APPEND && fishCommand != FISH_WRITE) infoMessage("Sending "+(commandList.first().mid(1,commandList.first().indexOf("\n")-1))+"...");
1400 commandList.erase(commandList.begin());
1401 commandCodes.erase(commandCodes.begin());
1402 } else {
1403 myDebug( << "_______ emitting finished()" << endl);
1404 SlaveBase::finished();
1405 isRunning = false;
1408 /** aborts command sequence and calls error() */
1409 void fishProtocol::error(int type, const QString &detail) {
1410 commandList.clear();
1411 commandCodes.clear();
1412 myDebug( << "ERROR: " << type << " - " << detail << endl);
1413 SlaveBase::error(type,detail);
1414 isRunning = false;
1416 /** executes a chain of commands */
1417 void fishProtocol::run() {
1418 if (!isRunning) {
1419 int rc;
1420 isRunning = true;
1421 finished();
1422 #ifndef Q_WS_WIN
1423 fd_set rfds, wfds;
1424 FD_ZERO(&rfds);
1425 #endif
1426 char buf[32768];
1427 int offset = 0;
1428 while (isRunning) {
1429 #ifndef Q_WS_WIN
1430 FD_SET(childFd,&rfds);
1431 FD_ZERO(&wfds);
1432 if (outBufPos >= 0) FD_SET(childFd,&wfds);
1433 struct timeval timeout;
1434 timeout.tv_sec = 0;
1435 timeout.tv_usec = 1000;
1436 rc = select(childFd+1, &rfds, &wfds, NULL, &timeout);
1437 if (rc < 0) {
1438 if (errno == EINTR)
1439 continue;
1440 myDebug( << "select failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1441 error(ERR_CONNECTION_BROKEN,connectionHost);
1442 shutdownConnection();
1443 return;
1445 if (FD_ISSET(childFd,&wfds) && outBufPos >= 0) {
1446 #else
1447 if (outBufPos >= 0) {
1448 #endif
1449 #if 0
1450 QString debug;
1451 debug.setLatin1(outBuf+outBufPos,outBufLen-outBufPos);
1452 myDebug( << "now writing " << (outBufLen-outBufPos) << " " << debug.left(40) << "..." << endl);
1453 #endif
1454 #ifndef Q_WS_WIN
1455 if (outBufLen-outBufPos > 0) rc = ::write(childFd,outBuf+outBufPos,outBufLen-outBufPos);
1456 #else
1457 if (outBufLen-outBufPos > 0) {
1458 rc = childPid->write(outBuf);
1460 #endif
1461 else rc = 0;
1462 if (rc >= 0) outBufPos += rc;
1463 else {
1464 #ifndef Q_WS_WIN
1465 if (errno == EINTR)
1466 continue;
1467 myDebug( << "write failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1468 #else
1469 myDebug( << "write failed, rc: " << rc);
1470 #endif
1471 error(ERR_CONNECTION_BROKEN,connectionHost);
1472 shutdownConnection();
1473 return;
1475 if (outBufPos >= outBufLen) {
1476 outBufPos = -1;
1477 outBuf = NULL;
1478 sent();
1481 #ifndef Q_WS_WIN
1482 else if (FD_ISSET(childFd,&rfds)) {
1483 rc = ::read(childFd,buf+offset,32768-offset);
1484 #else
1485 else if (childPid->waitForReadyRead(1000)) {
1486 rc = childPid->read(buf+offset,32768-offset);
1487 #endif
1488 //myDebug( << "read " << rc << " bytes" << endl);
1489 if (rc > 0) {
1490 int noff = received(buf,rc+offset);
1491 if (noff > 0) memmove(buf,buf+offset+rc-noff,noff);
1492 //myDebug( << "left " << noff << " bytes: " << QString::fromLatin1(buf,offset) << endl);
1493 offset = noff;
1494 } else {
1495 #ifndef Q_WS_WIN
1496 if (errno == EINTR)
1497 continue;
1498 myDebug( << "read failed, rc: " << rc << ", error: " << strerror(errno) << endl);
1499 #else
1500 myDebug( << "read failed, rc: " << rc );
1501 #endif
1502 error(ERR_CONNECTION_BROKEN,connectionHost);
1503 shutdownConnection();
1504 return;
1507 if (wasKilled())
1508 return;
1512 /** stat a file */
1513 void fishProtocol::stat(const KUrl& u){
1514 myDebug( << "@@@@@@@@@ stat " << u << endl);
1515 setHostInternal(u);
1516 url = u;
1517 isStat = true; // FIXME: just a workaround for konq deficiencies
1518 openConnection();
1519 isStat = false; // FIXME: just a workaround for konq deficiencies
1520 if (!isLoggedIn) return;
1521 url.cleanPath();
1522 if (!url.hasPath()) {
1523 sendCommand(FISH_PWD);
1524 } else {
1525 sendCommand(FISH_STAT,E(url.path(KUrl::RemoveTrailingSlash)));
1527 run();
1529 /** find mimetype for a file */
1530 void fishProtocol::mimetype(const KUrl& u){
1531 myDebug( << "@@@@@@@@@ mimetype " << u << endl);
1532 setHostInternal(u);
1533 url = u;
1534 openConnection();
1535 if (!isLoggedIn) return;
1536 url.cleanPath();
1537 if (!url.hasPath()) {
1538 sendCommand(FISH_PWD);
1539 } else {
1540 recvLen = 1024;
1541 sendCommand(FISH_READ,"0","1024",E(url.path()));
1543 run();
1545 /** list a directory */
1546 void fishProtocol::listDir(const KUrl& u){
1547 myDebug( << "@@@@@@@@@ listDir " << u << endl);
1548 setHostInternal(u);
1549 url = u;
1550 openConnection();
1551 if (!isLoggedIn) return;
1552 url.cleanPath();
1553 if (!url.hasPath()) {
1554 sendCommand(FISH_PWD);
1555 } else {
1556 listReason = LIST;
1557 sendCommand(FISH_LIST,E(url.path()));
1559 run();
1561 /** create a directory */
1562 void fishProtocol::mkdir(const KUrl& u, int permissions) {
1563 myDebug( << "@@@@@@@@@ mkdir " << u << " " << permissions << endl);
1564 setHostInternal(u);
1565 url = u;
1566 openConnection();
1567 if (!isLoggedIn) return;
1568 url.cleanPath();
1569 if (!url.hasPath()) {
1570 sendCommand(FISH_PWD);
1571 } else {
1572 sendCommand(FISH_MKD,E(url.path()));
1573 if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
1575 run();
1577 /** rename a file */
1578 void fishProtocol::rename(const KUrl& s, const KUrl& d, KIO::JobFlags flags) {
1579 myDebug( << "@@@@@@@@@ rename " << s << " " << d << " " << (flags & KIO::Overwrite) << endl);
1580 if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
1581 error(ERR_UNSUPPORTED_ACTION,s.prettyUrl());
1582 return;
1584 setHostInternal(s);
1585 url = d;
1586 openConnection();
1587 if (!isLoggedIn) return;
1588 KUrl src = s;
1589 url.cleanPath();
1590 src.cleanPath();
1591 if (!url.hasPath()) {
1592 sendCommand(FISH_PWD);
1593 } else {
1594 if (!(flags & KIO::Overwrite)) {
1595 listReason = CHECK;
1596 checkOverwrite = false;
1597 sendCommand(FISH_LIST,E(url.path()));
1599 sendCommand(FISH_RENAME,E(src.path()),E(url.path()));
1601 run();
1603 /** create a symlink */
1604 void fishProtocol::symlink(const QString& target, const KUrl& u, KIO::JobFlags flags) {
1605 myDebug( << "@@@@@@@@@ symlink " << target << " " << u << " " << (flags & KIO::Overwrite) << endl);
1606 setHostInternal(u);
1607 url = u;
1608 openConnection();
1609 if (!isLoggedIn) return;
1610 url.cleanPath();
1611 if (!url.hasPath()) {
1612 sendCommand(FISH_PWD);
1613 } else {
1614 if (!(flags & KIO::Overwrite)) {
1615 listReason = CHECK;
1616 checkOverwrite = false;
1617 sendCommand(FISH_LIST,E(url.path()));
1619 sendCommand(FISH_SYMLINK,E(target),E(url.path()));
1621 run();
1623 /** change file permissions */
1624 void fishProtocol::chmod(const KUrl& u, int permissions){
1625 myDebug( << "@@@@@@@@@ chmod " << u << " " << permissions << endl);
1626 setHostInternal(u);
1627 url = u;
1628 openConnection();
1629 if (!isLoggedIn) return;
1630 url.cleanPath();
1631 if (!url.hasPath()) {
1632 sendCommand(FISH_PWD);
1633 } else {
1634 if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
1636 run();
1638 /** copies a file */
1639 void fishProtocol::copy(const KUrl &s, const KUrl &d, int permissions, KIO::JobFlags flags) {
1640 myDebug( << "@@@@@@@@@ copy " << s << " " << d << " " << permissions << " " << (flags & KIO::Overwrite) << endl);
1641 if (s.host() != d.host() || s.port() != d.port() || s.user() != d.user()) {
1642 error(ERR_UNSUPPORTED_ACTION,s.prettyUrl());
1643 return;
1645 //myDebug( << s << endl << d << endl);
1646 setHostInternal(s);
1647 url = d;
1648 openConnection();
1649 if (!isLoggedIn) return;
1650 KUrl src = s;
1651 url.cleanPath();
1652 src.cleanPath();
1653 if (!src.hasPath()) {
1654 sendCommand(FISH_PWD);
1655 } else {
1656 if (!(flags & KIO::Overwrite)) {
1657 listReason = CHECK;
1658 checkOverwrite = false;
1659 sendCommand(FISH_LIST,E(url.path()));
1661 sendCommand(FISH_COPY,E(src.path()),E(url.path()));
1662 if (permissions > -1) sendCommand(FISH_CHMOD,E(QString::number(permissions,8)),E(url.path()));
1664 run();
1666 /** removes a file or directory */
1667 void fishProtocol::del(const KUrl &u, bool isFile){
1668 myDebug( << "@@@@@@@@@ del " << u << " " << isFile << endl);
1669 setHostInternal(u);
1670 url = u;
1671 openConnection();
1672 if (!isLoggedIn) return;
1673 url.cleanPath();
1674 if (!url.hasPath()) {
1675 sendCommand(FISH_PWD);
1676 } else {
1677 sendCommand((isFile?FISH_DELE:FISH_RMD),E(url.path()));
1679 run();
1681 /** special like background execute */
1682 void fishProtocol::special( const QByteArray &data ){
1683 int tmp;
1685 QDataStream stream(data);
1687 stream >> tmp;
1688 switch (tmp) {
1689 case FISH_EXEC_CMD: // SSH EXEC
1691 KUrl u;
1692 QString command;
1693 QString tempfile;
1694 stream >> u;
1695 stream >> command;
1696 myDebug( << "@@@@@@@@@ exec " << u << " " << command << endl);
1697 setHostInternal(u);
1698 url = u;
1699 openConnection();
1700 if (!isLoggedIn) return;
1701 sendCommand(FISH_EXEC,E(command),E(url.path()));
1702 run();
1703 break;
1705 default:
1706 // Some command we don't understand.
1707 error(ERR_UNSUPPORTED_ACTION,QString().setNum(tmp));
1708 break;
1711 /** report status */
1712 void fishProtocol::slave_status() {
1713 myDebug( << "@@@@@@@@@ slave_status" << endl);
1714 if (childPid > 0)
1715 slaveStatus(connectionHost,isLoggedIn);
1716 else
1717 slaveStatus(QString(),false);