File indexing completed on 2024-05-05 05:48:31
0001 /* 0002 CT Task Implementation 0003 -------------------------------------------------------------------- 0004 SPDX-FileCopyrightText: 1999 Gary Meyer <gary@meyer.net> 0005 -------------------------------------------------------------------- 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "cttask.h" 0010 0011 #include <KLocalizedString> 0012 0013 #include <QMimeDatabase> 0014 #include <QRegularExpression> 0015 #include <QUrl> 0016 0017 #include "ctHelper.h" 0018 0019 CTTask::CTTask(const QString &tokenString, const QString &_comment, const QString &_userLogin, bool _systemCrontab) 0020 : mSystemCrontab(_systemCrontab) 0021 { 0022 QString tokStr = tokenString; 0023 if (tokStr.mid(0, 2) == QLatin1String("#\\")) { 0024 tokStr = tokStr.mid(2, tokStr.length() - 2); 0025 enabled = false; 0026 } else if (tokStr.mid(0, 1) == QLatin1String("#")) { 0027 tokStr = tokStr.mid(1, tokStr.length() - 1); 0028 enabled = false; 0029 } else { 0030 enabled = true; 0031 } 0032 0033 // Skip over 'silence' if found... old option in vixie cron 0034 if (tokStr.mid(0, 1) == QLatin1String("-")) { 0035 tokStr = tokStr.mid(1, tokStr.length() - 1); 0036 } 0037 0038 reboot = false; 0039 if (tokStr.mid(0, 1) == QLatin1String("@")) { 0040 if (tokStr.mid(1, 6) == QLatin1String("yearly")) { 0041 tokStr = QLatin1String("0 0 1 1 *") + tokStr.mid(7, tokStr.length() - 1); 0042 } else if (tokStr.mid(1, 8) == QLatin1String("annually")) { 0043 tokStr = QLatin1String("0 0 1 1 *") + tokStr.mid(9, tokStr.length() - 1); 0044 } else if (tokStr.mid(1, 7) == QLatin1String("monthly")) { 0045 tokStr = QLatin1String("0 0 1 * *") + tokStr.mid(8, tokStr.length() - 1); 0046 } else if (tokStr.mid(1, 6) == QLatin1String("weekly")) { 0047 tokStr = QLatin1String("0 0 * * 0") + tokStr.mid(7, tokStr.length() - 1); 0048 } else if (tokStr.mid(1, 5) == QLatin1String("daily")) { 0049 tokStr = QLatin1String("0 0 * * *") + tokStr.mid(6, tokStr.length() - 1); 0050 } else if (tokStr.mid(1, 6) == QLatin1String("hourly")) { 0051 tokStr = QLatin1String("0 * * * *") + tokStr.mid(7, tokStr.length() - 1); 0052 } else if (tokStr.mid(1, 6) == QLatin1String("reboot")) { 0053 tokStr = tokStr.mid(7, tokStr.length() - 1); 0054 reboot = true; 0055 } 0056 } 0057 0058 int spacePos(tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]")))); 0059 // If reboot bypass initialize functions so no keys selected in modify task 0060 if (!reboot) { 0061 minute.initialize(tokStr.mid(0, spacePos)); 0062 0063 while (isSpaceAt(tokStr, spacePos + 1)) { 0064 spacePos++; 0065 } 0066 tokStr = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0067 spacePos = tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]"))); 0068 hour.initialize(tokStr.mid(0, spacePos)); 0069 0070 while (isSpaceAt(tokStr, spacePos + 1)) { 0071 spacePos++; 0072 } 0073 tokStr = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0074 spacePos = tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]"))); 0075 dayOfMonth.initialize(tokStr.mid(0, spacePos)); 0076 0077 while (isSpaceAt(tokStr, spacePos + 1)) { 0078 spacePos++; 0079 } 0080 tokStr = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0081 spacePos = tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]"))); 0082 month.initialize(tokStr.mid(0, spacePos)); 0083 0084 while (isSpaceAt(tokStr, spacePos + 1)) { 0085 spacePos++; 0086 } 0087 tokStr = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0088 spacePos = tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]"))); 0089 dayOfWeek.initialize(tokStr.mid(0, spacePos)); 0090 } 0091 0092 // Since it's a multiuser(system) task, the token contains the user login, 0093 // and the command, separated by a tab (\t). 0094 // The two need to subsequently be separated again. 0095 // E.g. "root\tmy_test_script.sh" 0096 if (mSystemCrontab) { 0097 while (isSpaceAt(tokStr, spacePos + 1)) { 0098 spacePos++; 0099 } 0100 tokStr = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0101 spacePos = tokStr.indexOf(QRegularExpression(QLatin1String("[ \t]"))); 0102 userLogin = tokStr.mid(0, spacePos); 0103 } else { 0104 userLogin = _userLogin; 0105 } 0106 command = tokStr.mid(spacePos + 1, tokStr.length() - 1); 0107 // remove leading whitespace 0108 while (command.indexOf(QRegularExpression(QLatin1String("[ \t]"))) == 0) { 0109 command = command.mid(1, command.length() - 1); 0110 } 0111 comment = _comment; 0112 0113 mInitialUserLogin = userLogin; 0114 mInitialCommand = command; 0115 mInitialComment = comment; 0116 mInitialEnabled = enabled; 0117 mInitialReboot = reboot; 0118 } 0119 0120 CTTask::CTTask(const CTTask &source) 0121 : month(source.month) 0122 , dayOfMonth(source.dayOfMonth) 0123 , dayOfWeek(source.dayOfWeek) 0124 , hour(source.hour) 0125 , minute(source.minute) 0126 , userLogin(source.userLogin) 0127 , command(source.command) 0128 , comment(source.comment) 0129 , enabled(source.enabled) 0130 , reboot(source.reboot) 0131 , mInitialUserLogin(QLatin1String("")) 0132 , mInitialCommand(QLatin1String("")) 0133 , mInitialComment(QLatin1String("")) 0134 , mInitialEnabled(true) 0135 , mInitialReboot(false) 0136 { 0137 } 0138 0139 CTTask &CTTask::operator=(const CTTask &source) 0140 { 0141 if (this == &source) { 0142 return *this; 0143 } 0144 0145 month = source.month; 0146 dayOfMonth = source.dayOfMonth; 0147 dayOfWeek = source.dayOfWeek; 0148 hour = source.hour; 0149 minute = source.minute; 0150 userLogin = source.userLogin; 0151 command = source.command; 0152 comment = source.comment; 0153 enabled = source.enabled; 0154 reboot = source.reboot; 0155 mInitialUserLogin = QLatin1String(""); 0156 mInitialCommand = QLatin1String(""); 0157 mInitialComment = QLatin1String(""); 0158 mInitialEnabled = true; 0159 mInitialReboot = false; 0160 0161 return *this; 0162 } 0163 0164 QString CTTask::exportTask() 0165 { 0166 QString exportTask; 0167 0168 exportTask += CTHelper::exportComment(comment); 0169 0170 if (!enabled) { 0171 exportTask += QLatin1String("#\\"); 0172 } 0173 0174 exportTask += schedulingCronFormat(); 0175 exportTask += QLatin1String("\t"); 0176 0177 if (isSystemCrontab()) { 0178 exportTask += userLogin + QLatin1String("\t"); 0179 } 0180 0181 exportTask += command + QLatin1String("\n"); 0182 0183 return exportTask; 0184 } 0185 0186 void CTTask::apply() 0187 { 0188 month.apply(); 0189 dayOfMonth.apply(); 0190 dayOfWeek.apply(); 0191 hour.apply(); 0192 minute.apply(); 0193 0194 mInitialUserLogin = userLogin; 0195 mInitialCommand = command; 0196 mInitialComment = comment; 0197 mInitialEnabled = enabled; 0198 mInitialReboot = reboot; 0199 } 0200 0201 void CTTask::cancel() 0202 { 0203 month.cancel(); 0204 dayOfMonth.cancel(); 0205 dayOfWeek.cancel(); 0206 hour.cancel(); 0207 minute.cancel(); 0208 0209 userLogin = mInitialUserLogin; 0210 command = mInitialCommand; 0211 comment = mInitialComment; 0212 enabled = mInitialEnabled; 0213 reboot = mInitialReboot; 0214 } 0215 0216 bool CTTask::dirty() const 0217 { 0218 return month.isDirty() || dayOfMonth.isDirty() || dayOfWeek.isDirty() || hour.isDirty() || minute.isDirty() || (userLogin != mInitialUserLogin) 0219 || (command != mInitialCommand) || (comment != mInitialComment) || (enabled != mInitialEnabled) || (reboot != mInitialReboot); 0220 } 0221 0222 QString CTTask::schedulingCronFormat() const 0223 { 0224 if (reboot) { 0225 return QStringLiteral("@reboot"); 0226 } 0227 0228 QString scheduling = minute.exportUnit() + QLatin1Char(' '); 0229 scheduling += hour.exportUnit() + QLatin1Char(' '); 0230 scheduling += dayOfMonth.exportUnit() + QLatin1Char(' '); 0231 scheduling += month.exportUnit() + QLatin1Char(' '); 0232 scheduling += dayOfWeek.exportUnit(); 0233 0234 return scheduling; 0235 } 0236 0237 /** 0238 * Of the whole program, this method is probably the trickiest. 0239 * 0240 * This method creates the natural language description, such as 0241 * "At 1:00am, every Sun". 0242 * 0243 * First, I declare some strings for holding what can be internationalized. 0244 * Note the tokens such as "MONTHS". Translators should reuse these 0245 * tokens in their translations. See README.translators for more 0246 * information. 0247 * 0248 * Second, I get the descriptions from the component parts such as 0249 * days of the month. 0250 * 0251 * Third, I get hour/minute time combinations. Although a little bit 0252 * awkward, I use the tm struct and strftime from <time.h>. This 0253 * way this code is portable across all Unixes. 0254 * 0255 * Fourth, I know that "every day of the week" and "every day of the 0256 * month" simply makes "every day". 0257 * 0258 * Fifth and finally I do tag substitution to create the natural language 0259 * description. 0260 * 0261 */ 0262 QString CTTask::describe() const 0263 { 0264 if (reboot) { 0265 return i18n("At system startup"); 0266 } 0267 0268 QString dateFormat = createDateFormat(); 0269 0270 QString timeFormat = createTimeFormat(); 0271 0272 return i18nc("1:Time Description, 2:Date Description", "%1, %2", timeFormat, dateFormat); 0273 } 0274 0275 QString CTTask::describeDayOfWeek() const 0276 { 0277 return i18nc("Every 'days of week'", "every %1", dayOfWeek.describe()); 0278 } 0279 0280 QString CTTask::describeDayOfMonth() const 0281 { 0282 return i18nc("'Days of month' of 'Months'", "%1 of %2", dayOfMonth.describe(), month.describe()); 0283 } 0284 0285 QString CTTask::createDateFormat() const 0286 { 0287 /* 0288 * "* * *" means truly every day. 0289 * Note: Languages may use different phrases to indicate 0290 * every day of month versus every day of week. 0291 */ 0292 QString dateFormat; 0293 if ((dayOfMonth.enabledCount() == CTDayOfMonth::MAXIMUM) && (dayOfWeek.enabledCount() == CTDayOfWeek::MAXIMUM)) { 0294 dateFormat = i18n("every day "); 0295 } 0296 // Day of month not specified, so use day of week. 0297 else if (dayOfMonth.enabledCount() == CTDayOfMonth::MAXIMUM) { 0298 dateFormat = describeDayOfWeek(); 0299 } 0300 // Day of week not specified, so use day of month. 0301 else if (dayOfWeek.enabledCount() == CTDayOfWeek::MAXIMUM) { 0302 dateFormat = describeDayOfMonth(); 0303 } else { 0304 dateFormat = i18nc("1:Day of month, 2:Day of week", "%1 as well as %2", describeDayOfMonth(), describeDayOfWeek()); 0305 } 0306 0307 return dateFormat; 0308 } 0309 0310 QString CTTask::describeDateAndHours() const 0311 { 0312 // Create time description. 0313 int total = minute.enabledCount() * hour.enabledCount(); 0314 0315 QString timeDesc; 0316 int count = 0; 0317 0318 for (int h = 0; h <= 23; h++) { 0319 if (hour.isEnabled(h)) { 0320 for (int m = 0; m <= 59; m++) { 0321 if (minute.isEnabled(m)) { 0322 QString hourString; 0323 if (h < 10) { 0324 hourString = QLatin1String("0") + QString::number(h); 0325 } else { 0326 hourString = QString::number(h); 0327 } 0328 0329 QString minuteString; 0330 if (m < 10) { 0331 minuteString = QLatin1String("0") + QString::number(m); 0332 } else { 0333 minuteString = QString::number(m); 0334 } 0335 0336 QString tmpStr = i18nc("1:Hour, 2:Minute", "%1:%2", hourString, minuteString); 0337 0338 timeDesc += tmpStr; 0339 count++; 0340 switch (total - count) { 0341 case 0: 0342 break; 0343 case 1: 0344 if (total > 2) { 0345 timeDesc += i18n(", and "); 0346 } else { 0347 timeDesc += i18n(" and "); 0348 } 0349 break; 0350 default: 0351 timeDesc += i18n(", "); 0352 } 0353 } 0354 } 0355 } 0356 } 0357 0358 return i18nc("Hour::Minute list", "At %1", timeDesc); 0359 } 0360 0361 QString CTTask::createTimeFormat() const 0362 { 0363 if (hour.isAllEnabled()) { 0364 int minutePeriod = minute.findPeriod(); 0365 if (minutePeriod != 0) { 0366 return i18np("Every minute", "Every %1 minutes", minutePeriod); 0367 } 0368 } 0369 0370 return describeDateAndHours(); 0371 } 0372 0373 bool CTTask::isSystemCrontab() const 0374 { 0375 return mSystemCrontab; 0376 } 0377 0378 void CTTask::setSystemCrontab(bool _systemCrontab) 0379 { 0380 mSystemCrontab = _systemCrontab; 0381 } 0382 0383 QIcon CTTask::commandIcon() const 0384 { 0385 QUrl commandPath = QUrl::fromLocalFile(completeCommandPath()); 0386 0387 QMimeType mimeType = QMimeDatabase().mimeTypeForUrl(commandPath); 0388 // qCDebug(KCM_CRON_LOG) << mimeType->name(); 0389 if (mimeType.name() == QLatin1String("application/x-executable") || mimeType.name() == QLatin1String("application/octet-stream")) { 0390 return QIcon::fromTheme(commandPath.fileName(), QIcon::fromTheme(QLatin1String("system-run"))); 0391 } 0392 0393 return QIcon::fromTheme(mimeType.iconName()); 0394 } 0395 0396 QPair<QString, bool> CTTask::unQuoteCommand() const 0397 { 0398 QString fullCommand = command; 0399 fullCommand = fullCommand.trimmed(); 0400 0401 const QStringList quotes{QStringLiteral("\""), QStringLiteral("'")}; 0402 0403 for (const QString "e : quotes) { 0404 if (fullCommand.indexOf(quote) == 0) { 0405 int nextQuote = fullCommand.indexOf(quote, 1); 0406 if (nextQuote == -1) { 0407 return QPair<QString, bool>(QLatin1String(""), false); 0408 } 0409 0410 return QPair<QString, bool>(fullCommand.mid(1, nextQuote - 1), true); 0411 } 0412 } 0413 0414 return QPair<QString, bool>(fullCommand, false); 0415 } 0416 0417 QString CTTask::decryptBinaryCommand(const QString &command) const 0418 { 0419 QString fullCommand; 0420 0421 bool found = false; 0422 for (int i = 0; i < command.length(); ++i) { 0423 if (command.at(i) == QLatin1Char(' ') && command.at(i - 1) != QLatin1Char('\\')) { 0424 fullCommand = command.left(i); 0425 found = true; 0426 break; 0427 } 0428 } 0429 0430 if (!found) { 0431 fullCommand = command; 0432 } 0433 0434 fullCommand.remove(QLatin1Char('\\')); 0435 0436 return fullCommand; 0437 } 0438 0439 QStringList CTTask::separatePathCommand(const QString &command, bool quoted) const 0440 { 0441 QStringList pathCommand; 0442 0443 if (command.at(0) == QLatin1Char('/')) { 0444 QString fullCommand; 0445 if (quoted) { 0446 fullCommand = command; 0447 } else { 0448 fullCommand = decryptBinaryCommand(command); 0449 } 0450 0451 if (fullCommand.isEmpty()) { 0452 return QStringList(); 0453 } 0454 0455 QString path = fullCommand.section(QLatin1Char('/'), 0, -2); 0456 QString commandBinary = fullCommand.section(QLatin1Char('/'), -1); 0457 0458 pathCommand << path << commandBinary; 0459 } else { 0460 QString fullCommand; 0461 if (quoted) { 0462 fullCommand = command; 0463 } else { 0464 fullCommand = decryptBinaryCommand(command); 0465 } 0466 0467 // relying on $PATH 0468 pathCommand << QString() << fullCommand; 0469 } 0470 0471 return pathCommand; 0472 } 0473 0474 QString CTTask::completeCommandPath() const 0475 { 0476 QPair<QString, bool> commandQuoted = unQuoteCommand(); 0477 if (commandQuoted.first.isEmpty()) { 0478 return QLatin1String(""); 0479 } 0480 0481 QStringList pathCommand = separatePathCommand(commandQuoted.first, commandQuoted.second); 0482 if (pathCommand.isEmpty()) { 0483 return QLatin1String(""); 0484 } 0485 0486 return pathCommand.join(QLatin1String("/")); 0487 }