updater_osx.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #import <Cocoa/Cocoa.h>
  8. #include <sys/xattr.h>
  9. NSString *appName = @"Telegram.app";
  10. NSString *appDir = nil;
  11. NSString *workDir = nil;
  12. #ifdef _DEBUG
  13. BOOL _debug = YES;
  14. #else
  15. BOOL _debug = NO;
  16. #endif
  17. NSFileHandle *_logFile = nil;
  18. void openLog() {
  19. if (!_debug || _logFile) return;
  20. NSString *logDir = [workDir stringByAppendingString:@"DebugLogs"];
  21. if (![[NSFileManager defaultManager] createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:nil]) {
  22. return;
  23. }
  24. NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
  25. [fmt setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
  26. [fmt setDateFormat:@"'DebugLogs/'yyyyMMdd'_'HHmmss'_update.txt'"];
  27. NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]];
  28. [[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
  29. _logFile = [NSFileHandle fileHandleForWritingAtPath:logPath];
  30. }
  31. void closeLog() {
  32. if (!_logFile) return;
  33. [_logFile closeFile];
  34. }
  35. void writeLog(NSString *msg) {
  36. if (!_logFile) return;
  37. [_logFile writeData:[[msg stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
  38. [_logFile synchronizeFile];
  39. }
  40. void RemoveQuarantineAttribute(NSString *path) {
  41. const char *kQuarantineAttribute = "com.apple.quarantine";
  42. writeLog([@"Removing quarantine: " stringByAppendingString:path]);
  43. removexattr([path fileSystemRepresentation], kQuarantineAttribute, 0);
  44. }
  45. void RemoveQuarantineFromBundle(NSString *path) {
  46. RemoveQuarantineAttribute(path);
  47. RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/MacOS/Telegram"]);
  48. RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Helpers/crashpad_handler"]);
  49. RemoveQuarantineAttribute([path stringByAppendingString:@"/Contents/Frameworks/Updater"]);
  50. }
  51. void delFolder() {
  52. writeLog([@"Fully clearing old path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/ready"]]);
  53. if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]) {
  54. writeLog(@"Failed to clear old path! :( New path was used?..");
  55. }
  56. writeLog([@"Fully clearing new path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/temp"]]);
  57. if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]) {
  58. writeLog(@"Error: failed to clear new path! :(");
  59. }
  60. rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]);
  61. }
  62. int main(int argc, const char * argv[]) {
  63. NSString *path = [[NSBundle mainBundle] bundlePath];
  64. if (!path) {
  65. return -1;
  66. }
  67. NSRange range = [path rangeOfString:@".app/" options:NSBackwardsSearch];
  68. if (range.location == NSNotFound) {
  69. return -1;
  70. }
  71. path = [path substringToIndex:range.location > 0 ? range.location : 0];
  72. range = [path rangeOfString:@"/" options:NSBackwardsSearch];
  73. NSString *appRealName = (range.location == NSNotFound) ? path : [path substringFromIndex:range.location + 1];
  74. appRealName = [[NSArray arrayWithObjects:appRealName, @".app", nil] componentsJoinedByString:@""];
  75. appDir = (range.location == NSNotFound) ? @"" : [path substringToIndex:range.location + 1];
  76. NSString *appDirFull = [appDir stringByAppendingString:appRealName];
  77. openLog();
  78. pid_t procId = 0;
  79. BOOL update = YES, toSettings = NO, autoStart = NO, startInTray = NO;
  80. BOOL customWorkingDir = NO;
  81. NSString *key = nil;
  82. for (int i = 0; i < argc; ++i) {
  83. if ([@"-workpath" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  84. if (++i < argc) {
  85. workDir = [NSString stringWithUTF8String:argv[i]];
  86. }
  87. } else if ([@"-procid" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  88. if (++i < argc) {
  89. NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
  90. [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
  91. procId = [[formatter numberFromString:[NSString stringWithUTF8String:argv[i]]] intValue];
  92. }
  93. } else if ([@"-noupdate" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  94. update = NO;
  95. } else if ([@"-tosettings" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  96. toSettings = YES;
  97. } else if ([@"-autostart" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  98. autoStart = YES;
  99. } else if ([@"-debug" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  100. _debug = YES;
  101. } else if ([@"-startintray" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  102. startInTray = YES;
  103. } else if ([@"-workdir_custom" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  104. customWorkingDir = YES;
  105. } else if ([@"-key" isEqualToString:[NSString stringWithUTF8String:argv[i]]]) {
  106. if (++i < argc) key = [NSString stringWithUTF8String:argv[i]];
  107. }
  108. }
  109. if (!workDir) {
  110. workDir = appDir;
  111. customWorkingDir = NO;
  112. }
  113. openLog();
  114. NSMutableArray *argsArr = [[NSMutableArray alloc] initWithCapacity:argc];
  115. for (int i = 0; i < argc; ++i) {
  116. [argsArr addObject:[NSString stringWithUTF8String:argv[i]]];
  117. }
  118. writeLog([[NSArray arrayWithObjects:@"Arguments: '", [argsArr componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
  119. if (key) writeLog([@"Key: " stringByAppendingString:key]);
  120. if (toSettings) writeLog(@"To Settings!");
  121. if (procId) {
  122. NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
  123. for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) {
  124. usleep(200000);
  125. app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
  126. }
  127. if (app) [app forceTerminate];
  128. app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
  129. for (int i = 0; i < 5 && app != nil && ![app isTerminated]; ++i) {
  130. usleep(200000);
  131. app = [NSRunningApplication runningApplicationWithProcessIdentifier:procId];
  132. }
  133. }
  134. if (update) {
  135. NSFileManager *fileManager = [NSFileManager defaultManager];
  136. NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"];
  137. NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"];
  138. if ([fileManager fileExistsAtPath:readyFilePath]) {
  139. writeLog([@"Ready file found! Using new path: " stringByAppendingString: srcEnum]);
  140. } else {
  141. srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old
  142. srcEnum = [workDir stringByAppendingString:@"tupdates/ready"];
  143. writeLog([@"Ready file not found! Using old path: " stringByAppendingString: srcEnum]);
  144. }
  145. writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]);
  146. // Take the Updater (this currently running binary) from the place where it was placed by Telegram
  147. // and copy it to the folder with the new version of the app (ready),
  148. // so it won't be deleted when we will clear the "Telegram.app/Contents" folder.
  149. NSString *oldVersionUpdaterPath = [appDirFull stringByAppendingString: @"/Contents/Frameworks/Updater" ];
  150. NSString *newVersionUpdaterPath = [srcEnum stringByAppendingString:[[NSArray arrayWithObjects:@"/", appName, @"/Contents/Frameworks/Updater", nil] componentsJoinedByString:@""]];
  151. writeLog([[NSArray arrayWithObjects: @"Copying Updater from old path ", oldVersionUpdaterPath, @" to new path ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]);
  152. if (![fileManager fileExistsAtPath:newVersionUpdaterPath]) {
  153. if (![fileManager copyItemAtPath:oldVersionUpdaterPath toPath:newVersionUpdaterPath error:nil]) {
  154. writeLog([[NSArray arrayWithObjects: @"Failed to copy file from ", oldVersionUpdaterPath, @" to ", newVersionUpdaterPath, nil] componentsJoinedByString:@""]);
  155. delFolder();
  156. return -1;
  157. }
  158. }
  159. NSString *contentsPath = [appDirFull stringByAppendingString: @"/Contents"];
  160. writeLog([[NSArray arrayWithObjects: @"Clearing dir ", contentsPath, nil] componentsJoinedByString:@""]);
  161. if (![fileManager removeItemAtPath:contentsPath error:nil]) {
  162. writeLog([@"Failed to clear path for directory " stringByAppendingString:contentsPath]);
  163. delFolder();
  164. return -1;
  165. }
  166. NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
  167. NSDirectoryEnumerator *enumerator = [fileManager
  168. enumeratorAtURL:[NSURL fileURLWithPath:srcEnum]
  169. includingPropertiesForKeys:keys
  170. options:0
  171. errorHandler:^(NSURL *url, NSError *error) {
  172. writeLog([[[@"Error in enumerating " stringByAppendingString:[url absoluteString]] stringByAppendingString: @" error is: "] stringByAppendingString: [error description]]);
  173. return NO;
  174. }];
  175. for (NSURL *url in enumerator) {
  176. NSString *srcPath = [url path];
  177. writeLog([@"Handling file " stringByAppendingString:srcPath]);
  178. NSRange r = [srcPath rangeOfString:srcDir];
  179. if (r.location != 0) {
  180. writeLog([@"Bad file found, no base path " stringByAppendingString:srcPath]);
  181. delFolder();
  182. break;
  183. }
  184. NSString *pathPart = [srcPath substringFromIndex:r.length];
  185. r = [pathPart rangeOfString:appName];
  186. if (r.location != 0) {
  187. writeLog([@"Skipping not app file " stringByAppendingString:srcPath]);
  188. continue;
  189. }
  190. NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]];
  191. NSError *error;
  192. NSNumber *isDirectory = nil;
  193. if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
  194. writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]);
  195. delFolder();
  196. break;
  197. }
  198. if ([isDirectory boolValue]) {
  199. writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
  200. if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) {
  201. writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]);
  202. delFolder();
  203. break;
  204. }
  205. } else if ([srcPath isEqualToString:readyFilePath]) {
  206. writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]);
  207. } else if ([fileManager fileExistsAtPath:dstPath]) {
  208. if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) {
  209. writeLog([@"Failed to edit file " stringByAppendingString:dstPath]);
  210. delFolder();
  211. break;
  212. }
  213. } else {
  214. if (![fileManager copyItemAtPath:srcPath toPath:dstPath error:nil]) {
  215. writeLog([@"Failed to copy file to " stringByAppendingString:dstPath]);
  216. delFolder();
  217. break;
  218. }
  219. }
  220. }
  221. delFolder();
  222. }
  223. NSString *appPath = [[NSArray arrayWithObjects:appDir, appRealName, nil] componentsJoinedByString:@""];
  224. RemoveQuarantineFromBundle(appPath);
  225. NSMutableArray *args = [[NSMutableArray alloc] initWithObjects: @"-noupdate", nil];
  226. if (toSettings) [args addObject:@"-tosettings"];
  227. if (_debug) [args addObject:@"-debug"];
  228. if (startInTray) [args addObject:@"-startintray"];
  229. if (autoStart) [args addObject:@"-autostart"];
  230. if (key) {
  231. [args addObject:@"-key"];
  232. [args addObject:key];
  233. }
  234. if (customWorkingDir) {
  235. [args addObject:@"-workdir"];
  236. [args addObject:workDir];
  237. }
  238. writeLog([[NSArray arrayWithObjects:@"Running application '", appPath, @"' with args '", [args componentsJoinedByString:@"' '"], @"'..", nil] componentsJoinedByString:@""]);
  239. for (int i = 0; i < 5; ++i) {
  240. NSError *error = nil;
  241. NSRunningApplication *result = [[NSWorkspace sharedWorkspace]
  242. launchApplicationAtURL:[NSURL fileURLWithPath:appPath]
  243. options:NSWorkspaceLaunchDefault
  244. configuration:[NSDictionary
  245. dictionaryWithObject:args
  246. forKey:NSWorkspaceLaunchConfigurationArguments]
  247. error:&error];
  248. if (result) {
  249. closeLog();
  250. return 0;
  251. }
  252. writeLog([[NSString stringWithFormat:@"Could not run application, error %ld: ", (long)[error code]] stringByAppendingString: error ? [error localizedDescription] : @"(nil)"]);
  253. usleep(200000);
  254. }
  255. closeLog();
  256. return -1;
  257. }