qBittorrent
main.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2014 Vladimir Golovnev <[email protected]>
4  * Copyright (C) 2006 Christophe Dumez <[email protected]>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give permission to
21  * link this program with the OpenSSL project's "OpenSSL" library (or with
22  * modified versions of it that use the same license as the "OpenSSL" library),
23  * and distribute the linked executables. You must obey the GNU General Public
24  * License in all respects for all of the code used other than "OpenSSL". If you
25  * modify file(s), you may extend this exception to your version of the file(s),
26  * but you are not obligated to do so. If you do not wish to do so, delete this
27  * exception statement from your version.
28  */
29 
30 #include <QtGlobal>
31 
32 #include <csignal>
33 #include <cstdlib>
34 #include <memory>
35 
36 #if defined(Q_OS_UNIX)
37 #include <sys/resource.h>
38 #endif
39 #if !defined Q_OS_WIN && !defined Q_OS_HAIKU
40 #include <unistd.h>
41 #elif defined Q_OS_WIN && defined DISABLE_GUI
42 #include <io.h>
43 #endif
44 
45 #include <QDebug>
46 #include <QThread>
47 
48 #ifndef DISABLE_GUI
49 // GUI-only includes
50 #include <QFont>
51 #include <QMessageBox>
52 #include <QPainter>
53 #include <QPen>
54 #include <QPushButton>
55 #include <QSplashScreen>
56 #include <QTimer>
57 
58 #ifdef QBT_STATIC_QT
59 #include <QtPlugin>
60 Q_IMPORT_PLUGIN(QICOPlugin)
61 #endif // QBT_STATIC_QT
62 
63 #else
64 // NoGUI-only includes
65 #include <cstdio>
66 #endif // DISABLE_GUI
67 
68 #ifdef STACKTRACE
69 #ifdef Q_OS_UNIX
70 #include "stacktrace.h"
71 #else
72 #include "stacktrace_win.h"
73 #ifndef DISABLE_GUI
74 #include "stacktracedialog.h"
75 #endif // DISABLE_GUI
76 #endif // Q_OS_UNIX
77 #endif //STACKTRACE
78 
79 #include "base/preferences.h"
80 #include "base/profile.h"
81 #include "base/version.h"
82 #include "application.h"
83 #include "cmdoptions.h"
84 #include "upgrade.h"
85 
86 #ifndef DISABLE_GUI
87 #include "gui/utils.h"
88 #endif
89 
90 // Signal handlers
91 void sigNormalHandler(int signum);
92 #ifdef STACKTRACE
93 void sigAbnormalHandler(int signum);
94 #endif
95 // sys_signame[] is only defined in BSD
96 const char *const sysSigName[] =
97 {
98 #if defined(Q_OS_WIN)
99  "", "", "SIGINT", "", "SIGILL", "", "SIGABRT_COMPAT", "", "SIGFPE", "",
100  "", "SIGSEGV", "", "", "", "SIGTERM", "", "", "", "",
101  "", "SIGBREAK", "SIGABRT", "", "", "", "", "", "", "",
102  "", ""
103 #else
104  "", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
105  "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
106  "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO",
107  "SIGPWR", "SIGUNUSED"
108 #endif
109 };
110 
111 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
112 void reportToUser(const char *str);
113 #endif
114 
115 void displayVersion();
117 void displayBadArgMessage(const QString &message);
118 
119 #if !defined(DISABLE_GUI)
120 void showSplashScreen();
121 #endif // DISABLE_GUI
122 
123 #if defined(Q_OS_UNIX)
124 void adjustFileDescriptorLimit();
125 #endif
126 
127 // Main
128 int main(int argc, char *argv[])
129 {
130 #if defined(Q_OS_UNIX)
131  adjustFileDescriptorLimit();
132 #endif
133 
134  // We must save it here because QApplication constructor may change it
135  bool isOneArg = (argc == 2);
136 
137 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && !defined(DISABLE_GUI)
138  // Attribute Qt::AA_EnableHighDpiScaling must be set before QCoreApplication is created
139  if (qgetenv("QT_ENABLE_HIGHDPI_SCALING").isEmpty() && qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR").isEmpty())
140  Application::setAttribute(Qt::AA_EnableHighDpiScaling, true);
141  // HighDPI scale factor policy must be set before QGuiApplication is created
142  if (qgetenv("QT_SCALE_FACTOR_ROUNDING_POLICY").isEmpty())
143  Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
144 #endif
145 
146  try
147  {
148  // Create Application
149  auto app = std::make_unique<Application>(argc, argv);
150 
151  const QBtCommandLineParameters params = app->commandLineArgs();
152  if (!params.unknownParameter.isEmpty())
153  {
154  throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
155  "--random-parameter is an unknown command line parameter.")
156  .arg(params.unknownParameter));
157  }
158 #if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
159  if (params.showVersion)
160  {
161  if (isOneArg)
162  {
163  displayVersion();
164  return EXIT_SUCCESS;
165  }
166  throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
167  .arg(QLatin1String("-v (or --version)")));
168  }
169 #endif
170  if (params.showHelp)
171  {
172  if (isOneArg)
173  {
174  displayUsage(argv[0]);
175  return EXIT_SUCCESS;
176  }
177  throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
178  .arg(QLatin1String("-h (or --help)")));
179  }
180 
181  // Set environment variable
182  if (!qputenv("QBITTORRENT", QBT_VERSION))
183  fprintf(stderr, "Couldn't set environment variable...\n");
184 
185  const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
186  if (firstTimeUser)
187  {
188 #ifndef DISABLE_GUI
190  return EXIT_SUCCESS;
191 #elif defined(Q_OS_WIN)
192  if (_isatty(_fileno(stdin))
193  && _isatty(_fileno(stdout))
195  return EXIT_SUCCESS;
196 #else
197  if (!params.shouldDaemonize
198  && isatty(fileno(stdin))
199  && isatty(fileno(stdout))
201  return EXIT_SUCCESS;
202 #endif
203 
205  }
206 
207  // Check if qBittorrent is already running for this user
208  if (app->isRunning())
209  {
210 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
211  if (params.shouldDaemonize)
212  {
213  throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
214  .arg(QLatin1String("-d (or --daemon)")));
215  }
216  else
217 #endif
218  qDebug("qBittorrent is already running for this user.");
219 
220  QThread::msleep(300);
221  app->sendParams(params.paramList());
222 
223  return EXIT_SUCCESS;
224  }
225 
226 #if defined(Q_OS_WIN)
227  // This affects only Windows apparently and Qt5.
228  // When QNetworkAccessManager is instantiated it regularly starts polling
229  // the network interfaces to see what's available and their status.
230  // This polling creates jitter and high ping with wifi interfaces.
231  // So here we disable it for lack of better measure.
232  // It will also spew this message in the console: QObject::startTimer: Timers cannot have negative intervals
233  // For more info see:
234  // 1. https://github.com/qbittorrent/qBittorrent/issues/4209
235  // 2. https://bugreports.qt.io/browse/QTBUG-40332
236  // 3. https://bugreports.qt.io/browse/QTBUG-46015
237 
238  qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
239 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && !defined(DISABLE_GUI)
240  // this is the default in Qt6
241  app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
242 #endif
243 #endif // Q_OS_WIN
244 
245 #if defined(Q_OS_MACOS)
246  // Since Apple made difficult for users to set PATH, we set here for convenience.
247  // Users are supposed to install Homebrew Python for search function.
248  // For more info see issue #5571.
249  QByteArray path = "/usr/local/bin:";
250  path += qgetenv("PATH");
251  qputenv("PATH", path.constData());
252 
253  // On OS X the standard is to not show icons in the menus
254  app->setAttribute(Qt::AA_DontShowIconsInMenus);
255 #else
256  if (!Preferences::instance()->iconsInMenusEnabled())
257  app->setAttribute(Qt::AA_DontShowIconsInMenus);
258 #endif
259 
260  if (!firstTimeUser)
261  {
263 
264 #ifndef DISABLE_GUI
265  if (!upgrade()) return EXIT_FAILURE;
266 #elif defined(Q_OS_WIN)
267  if (!upgrade(_isatty(_fileno(stdin))
268  && _isatty(_fileno(stdout)))) return EXIT_FAILURE;
269 #else
270  if (!upgrade(!params.shouldDaemonize
271  && isatty(fileno(stdin))
272  && isatty(fileno(stdout)))) return EXIT_FAILURE;
273 #endif
274  }
275  else
276  {
278  }
279 
280 #if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
281  if (params.shouldDaemonize)
282  {
283  app.reset(); // Destroy current application
284  if (daemon(1, 0) == 0)
285  {
286  app = std::make_unique<Application>(argc, argv);
287  if (app->isRunning())
288  {
289  // Another instance had time to start.
290  return EXIT_FAILURE;
291  }
292  }
293  else
294  {
295  qCritical("Something went wrong while daemonizing, exiting...");
296  return EXIT_FAILURE;
297  }
298  }
299 #elif !defined(DISABLE_GUI)
302 #endif
303 
304  signal(SIGINT, sigNormalHandler);
305  signal(SIGTERM, sigNormalHandler);
306 #ifdef STACKTRACE
307  signal(SIGABRT, sigAbnormalHandler);
308  signal(SIGSEGV, sigAbnormalHandler);
309 #endif
310 
311  return app->exec(params.paramList());
312  }
313  catch (const CommandLineParameterError &er)
314  {
316  return EXIT_FAILURE;
317  }
318 }
319 
320 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
321 void reportToUser(const char *str)
322 {
323  const size_t strLen = strlen(str);
324 #ifndef Q_OS_WIN
325  if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
326  {
327  const auto dummy = write(STDOUT_FILENO, str, strLen);
328 #else
329  if (_write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
330  {
331  const auto dummy = _write(STDOUT_FILENO, str, strLen);
332 #endif
333  Q_UNUSED(dummy);
334  }
335 }
336 #endif
337 
338 void sigNormalHandler(int signum)
339 {
340 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
341  const char msg1[] = "Catching signal: ";
342  const char msg2[] = "\nExiting cleanly\n";
343  reportToUser(msg1);
344  reportToUser(sysSigName[signum]);
345  reportToUser(msg2);
346 #endif // !defined Q_OS_WIN && !defined Q_OS_HAIKU
347  signal(signum, SIG_DFL);
348  qApp->exit(); // unsafe, but exit anyway
349 }
350 
351 #ifdef STACKTRACE
352 void sigAbnormalHandler(int signum)
353 {
354  const char *sigName = sysSigName[signum];
355 #if !(defined Q_OS_WIN && !defined DISABLE_GUI) && !defined Q_OS_HAIKU
356  const char msg[] = "\n\n*************************************************************\n"
357  "Please file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
358  "qBittorrent version: " QBT_VERSION "\n\n"
359  "Caught signal: ";
360  reportToUser(msg);
361  reportToUser(sigName);
362  reportToUser("\n");
363  print_stacktrace(); // unsafe
364 #endif
365 
366 #if defined Q_OS_WIN && !defined DISABLE_GUI
367  StacktraceDialog dlg; // unsafe
368  dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
369  dlg.exec();
370 #endif
371 
372  signal(signum, SIG_DFL);
373  raise(signum);
374 }
375 #endif // STACKTRACE
376 
377 #if !defined(DISABLE_GUI)
379 {
380  QPixmap splashImg(":/icons/splash.png");
381  QPainter painter(&splashImg);
382  const QString version = QBT_VERSION;
383  painter.setPen(QPen(Qt::white));
384  painter.setFont(QFont("Arial", 22, QFont::Black));
385  painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
386  QSplashScreen *splash = new QSplashScreen(splashImg);
387  splash->show();
388  QTimer::singleShot(1500, splash, &QObject::deleteLater);
389  qApp->processEvents();
390 }
391 #endif // DISABLE_GUI
392 
394 {
395  printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
396 }
397 
398 void displayBadArgMessage(const QString &message)
399 {
400  const QString help = QObject::tr("Run application with -h option to read about command line parameters.");
401 #if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
402  QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
403  message + QLatin1Char('\n') + help, QMessageBox::Ok);
404  msgBox.show(); // Need to be shown or to moveToCenter does not work
405  msgBox.move(Utils::Gui::screenCenter(&msgBox));
406  msgBox.exec();
407 #else
408  const QString errMsg = QObject::tr("Bad command line: ") + '\n'
409  + message + '\n'
410  + help + '\n';
411  fprintf(stderr, "%s", qUtf8Printable(errMsg));
412 #endif
413 }
414 
416 {
417  Preferences *const pref = Preferences::instance();
418  Q_ASSERT(!pref->getAcceptedLegal());
419 
420 #ifdef DISABLE_GUI
421  const QString eula = QString::fromLatin1("\n*** %1 ***\n").arg(QObject::tr("Legal Notice"))
422  + QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + "\n\n"
423  + QObject::tr("No further notices will be issued.") + "\n\n"
424  + QObject::tr("Press %1 key to accept and continue...").arg("'y'") + '\n';
425  printf("%s", qUtf8Printable(eula));
426 
427  const char ret = getchar(); // Read pressed key
428  if ((ret == 'y') || (ret == 'Y'))
429  {
430  // Save the answer
431  pref->setAcceptedLegal(true);
432  return true;
433  }
434 #else
435  QMessageBox msgBox;
436  msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
437  msgBox.setWindowTitle(QObject::tr("Legal notice"));
438  msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
439  const QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
440  msgBox.show(); // Need to be shown or to moveToCenter does not work
441  msgBox.move(Utils::Gui::screenCenter(&msgBox));
442  msgBox.exec();
443  if (msgBox.clickedButton() == agreeButton)
444  {
445  // Save the answer
446  pref->setAcceptedLegal(true);
447  return true;
448  }
449 #endif // DISABLE_GUI
450 
451  return false;
452 }
453 
454 #if defined(Q_OS_UNIX)
455 void adjustFileDescriptorLimit()
456 {
457  rlimit limit {};
458 
459  if (getrlimit(RLIMIT_NOFILE, &limit) != 0)
460  return;
461 
462  limit.rlim_cur = limit.rlim_max;
463  setrlimit(RLIMIT_NOFILE, &limit);
464 }
465 #endif
QString message() const noexcept
Definition: exceptions.cpp:36
static Preferences * instance()
bool isSplashScreenDisabled() const
void setAcceptedLegal(bool accepted)
bool getAcceptedLegal() const
void setStacktraceString(const QString &sigName, const QString &trace)
void displayUsage(const QString &prgName)
Definition: cmdoptions.cpp:582
int main(int argc, char *argv[])
Definition: main.cpp:128
bool userAgreesWithLegalNotice()
Definition: main.cpp:415
void displayVersion()
Definition: main.cpp:393
void showSplashScreen()
Definition: main.cpp:378
void sigNormalHandler(int signum)
Definition: main.cpp:338
void displayBadArgMessage(const QString &message)
Definition: main.cpp:398
void reportToUser(const char *str)
Definition: main.cpp:321
const char *const sysSigName[]
Definition: main.cpp:96
QPoint screenCenter(const QWidget *w)
Definition: utils.cpp:119
const QString getBacktrace()
help
Definition: tstool.py:145
static void print_stacktrace(FILE *out=stderr, unsigned int max_frames=63)
Definition: stacktrace.h:15
QStringList paramList() const
Definition: cmdoptions.cpp:363
bool upgrade(const bool)
Definition: upgrade.cpp:331
void handleChangedDefaults(const DefaultPreferencesMode mode)
Definition: upgrade.cpp:360
void setCurrentMigrationVersion()
Definition: upgrade.cpp:355