Bug 143791

Summary: crash if a second ktorrent instance is run with a different $DISPLAY
Product: ktorrent Reporter: Ademar de Souza Reis Jr. <ade>
Component: generalAssignee: Joris Guisson <joris.guisson>
Status: RESOLVED FIXED    
Severity: crash    
Priority: NOR    
Version: unspecified   
Target Milestone: ---   
Platform: Mandriva RPMs   
OS: Linux   
Latest Commit: Version Fixed In:

Description Ademar de Souza Reis Jr. 2007-04-03 15:46:35 UTC
Version:           2.1.2 (using KDE KDE 3.5.4)
Installed from:    Mandriva RPMs
OS:                Linux

If ktorrent is started a second time by the same user, but using a different $DISPLAY (e.g. through a ssh connection, vnc, etc), ktorrent reports that the ports are in use and keeps its normal execution path, trying to read/write on files used by the first instance and eventually crashing and/or corrupting files.

To reproduce:

$ ktorrent
$ export DISPLAY=localhost:0.0
$ ktorrent

(assuming your X server is configured to listen to tcp connections... otherwise you can use ssh -X localhost to get a new $DISPLAY env)
Comment 1 Joris Guisson 2007-04-10 20:33:57 UTC
KTorrent uses KUniqueApplication to achieve the only one instance functionality, so this is a bug in kdelibs.
Comment 2 Stephan Kulow 2007-04-12 11:39:03 UTC
I'm afraid it's not. KUniqueApp only guarantees one instance per KDE session - I'll make sure this is added to the documentation for KDE4
Comment 3 Joris Guisson 2007-04-12 18:46:30 UTC
To me it would make more sense that this functionality would be implemented in KUniqueApplication, KTorrent is probably not the only app which will have problems under these circumstances.

Anyway, it seems that we will have to come up with something ourselves.
Comment 4 Joris Guisson 2007-04-30 21:22:08 UTC
SVN commit 659769 by guisson:

Make sure only one instance of KT can run for each user (regardless of the current $DISPLAY), this fixes bug 143791.

This is implemented by a hidden lock file in the users's home directory.

BUG: 143791



 M  +19 -28    ktorrentapp.cpp  
 M  +45 -0     main.cpp  


--- trunk/extragear/network/ktorrent/apps/ktorrent/ktorrentapp.cpp #659768:659769
@@ -45,40 +45,31 @@
 	if (!dcopClient()->isRegistered() )
 		dcopClient()->registerAs(name(), false);
 
-	// see if we are starting with session management
-/*	if (restoringSession())
+	KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+	bt::Globals::instance().setDebugMode(args->isSet("debug"));
+
+	QString data_dir = KGlobal::dirs()->saveLocation("data","ktorrent");
+	if (!data_dir.endsWith(bt::DirSeparator()))
+		data_dir += bt::DirSeparator();
+	bt::Globals::instance().initLog(data_dir + "log");
+
+	if (!mainWidget())
 	{
-		RESTORE(KTorrent);
+		KTorrent *widget = new KTorrent();
+		setMainWidget(widget);
 	}
-	else*/
-	{
-		// no session.. just start up normally
-		KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
-		bt::Globals::instance().setDebugMode(args->isSet("debug"));
+	else
+		KStartupInfo::setNewStartupId( mainWidget(), kapp->startupId());
 
-		QString data_dir = KGlobal::dirs()->saveLocation("data","ktorrent");
-		if (!data_dir.endsWith(bt::DirSeparator()))
-			data_dir += bt::DirSeparator();
-		bt::Globals::instance().initLog(data_dir + "log");
 
-		if (!mainWidget())
-		{
-			KTorrent *widget = new KTorrent();
-			setMainWidget(widget);
-		}
-		else
-			KStartupInfo::setNewStartupId( mainWidget(), kapp->startupId());
+	KTorrent *widget = ::qt_cast<KTorrent*>( mainWidget() );
 
-
-		KTorrent *widget = ::qt_cast<KTorrent*>( mainWidget() );
-
-		for (int i = 0; i < args->count(); i++)
-		{
-			widget->load(args->url(i));
-		}
-
-		args->clear();
+	for (int i = 0; i < args->count(); i++)
+	{
+		widget->load(args->url(i));
 	}
+
+	args->clear();
 	return 0;
 }
 
--- trunk/extragear/network/ktorrent/apps/ktorrent/main.cpp #659768:659769
@@ -34,14 +34,25 @@
 #include <qapplication.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <util/error.h>
 #include <util/log.h>
 #include <torrent/globals.h>
+#include <util/fileops.h>
 #include <ktversion.h>
+#include <functions.h>
+#include <qfile.h>
+#include <qdir.h>
 
 using namespace bt;
 
 
+
 void StupidWarningMessagesFromQt( QtMsgType type, const char *msg )
 {
 	switch ( type ) 
@@ -60,11 +71,38 @@
 }
 
 
+
 static const char description[] =
     I18N_NOOP("A BitTorrent program for KDE");
 
 
+bool GrabPIDLock()
+{
+	// open the PID file in the users ktorrent directory and attempt to lock it
+	QString pid_file = QDir::homeDirPath() + "/.ktorrent.lock";
+		
+	int fd = open(QFile::encodeName(pid_file),O_RDWR|O_CREAT,0640);
+	if (fd < 0)
+	{
+		fprintf(stderr,"Failed to open KT lock file %s : %s\n",pid_file.ascii(),strerror(errno));
+		return false;
+	}
 
+	if (lockf(fd,F_TLOCK,0)<0) 
+	{
+		fprintf(stderr,"Failed to get lock on %s : %s\n",pid_file.ascii(),strerror(errno));
+		return false;
+	}
+		
+	char str[20];
+	sprintf(str,"%d\n",getpid());
+	write(fd,str,strlen(str)); /* record pid to lockfile */
+
+	// leave file open, so nobody else can lock it until KT exists
+	return true;
+}
+
+
 static KCmdLineOptions options[] =
 {
 	{ "debug", I18N_NOOP("Debug mode"), 0 },
@@ -120,6 +158,13 @@
 		fprintf(stderr, "ktorrent is already running!\n");
 		return 0;
 	}
+
+	// need to grab lock after the fork call in start, otherwise this will not work properly
+	if (!GrabPIDLock())
+	{
+		fprintf(stderr, "ktorrent is already running!\n");
+		return 0;
+	}
 	
 	try
 	{